From 21bcca798b8c4b6110cda4d1bbe4026e2dae5b70 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 19 Jun 2026 02:21:41 +0800 Subject: [PATCH 001/169] fix: csp (#38162) ref: https://github.com/go-gitea/gitea/issues/8707#issuecomment-4741577316 --- services/context/context_template.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/context/context_template.go b/services/context/context_template.go index c458912e69..87877095ab 100644 --- a/services/context/context_template.go +++ b/services/context/context_template.go @@ -132,8 +132,9 @@ func (c TemplateContext) HeadMetaContentSecurityPolicy() template.HTML { // * Maybe this approach should be avoided, don't make the config system too complex, just let users use A return template.HTML(` Date: Fri, 19 Jun 2026 05:48:27 +0900 Subject: [PATCH 002/169] docs: fix duplicated word in foreachref doc comment (#38161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Format doc comment read "See See git-for-each-ref(1)" — removed the duplicated "See" (the sibling field comments use a single "See"). Signed-off-by: s3onghyun --- modules/git/foreachref/format.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go index 87c1c9a4ff..64c9a37a30 100644 --- a/modules/git/foreachref/format.go +++ b/modules/git/foreachref/format.go @@ -16,7 +16,7 @@ var ( ) // Format supports specifying and parsing an output format for 'git -// for-each-ref'. See See git-for-each-ref(1) for available fields. +// for-each-ref'. See git-for-each-ref(1) for available fields. type Format struct { // fieldNames hold %(fieldname)s to be passed to the '--format' flag of // for-each-ref. See git-for-each-ref(1) for available fields. -- 2.54.0 From 645b10087d6794557f6a038af7f3824a3b2bc5e1 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Fri, 19 Jun 2026 23:18:17 +0200 Subject: [PATCH 003/169] fix(hostmacher): patch incorrect private list (#38170) regression from #38039 --- modules/hostmatcher/hostmatcher.go | 21 +++++++++++---------- modules/hostmatcher/hostmatcher_test.go | 4 +++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go index 7c17bc95da..c82a57b7b9 100644 --- a/modules/hostmatcher/hostmatcher.go +++ b/modules/hostmatcher/hostmatcher.go @@ -65,12 +65,9 @@ var reservedIPNets = sync.OnceValue(func() []*net.IPNet { return nets }) -// isPrivateIP reports whether ip falls in a private (net.IP.IsPrivate) or reserved special-purpose +// isReservedIP reports whether ip falls in reserved special-purpose // range (see reservedIPNets) that must not be considered a public/external destination. -func isPrivateIP(ip net.IP) bool { - if ip.IsPrivate() { - return true - } +func isReservedIP(ip net.IP) bool { for _, ipNet := range reservedIPNets() { if ipNet.Contains(ip) { return true @@ -148,18 +145,22 @@ func (hl *HostMatchList) checkPattern(host string) bool { return false } -func (hl *HostMatchList) checkIP(ip net.IP) bool { +// matchesIP determines if the given IP matches any of the configured rules +func (hl *HostMatchList) matchesIP(ip net.IP) bool { if slices.Contains(hl.patterns, "*") { return true } for _, builtin := range hl.builtins { switch builtin { case MatchBuiltinExternal: - if ip.IsGlobalUnicast() && !isPrivateIP(ip) { + // External address must be a global unicast, must not be in reserved range and must not be in private range + if ip.IsGlobalUnicast() && !isReservedIP(ip) && !ip.IsPrivate() { return true } case MatchBuiltinPrivate: - if isPrivateIP(ip) { + // Private address must be global unicast, must not be in range we explicitly exclude for security reasons + // and must be in private range + if ip.IsGlobalUnicast() && !isReservedIP(ip) && ip.IsPrivate() { return true } case MatchBuiltinLoopback: @@ -190,7 +191,7 @@ func (hl *HostMatchList) MatchHostName(host string) bool { return true } if ip := net.ParseIP(hostname); ip != nil { - return hl.checkIP(ip) + return hl.matchesIP(ip) } return false } @@ -201,7 +202,7 @@ func (hl *HostMatchList) MatchIPAddr(ip net.IP) bool { return false } host := ip.String() // nil-safe, we will get "" if ip is nil - return hl.checkPattern(host) || hl.checkIP(ip) + return hl.checkPattern(host) || hl.matchesIP(ip) } // MatchHostOrIP checks if the host or IP matches an allow/deny(block) list diff --git a/modules/hostmatcher/hostmatcher_test.go b/modules/hostmatcher/hostmatcher_test.go index 61582f28d3..464354ff41 100644 --- a/modules/hostmatcher/hostmatcher_test.go +++ b/modules/hostmatcher/hostmatcher_test.go @@ -202,15 +202,17 @@ func TestReservedRanges(t *testing.T) { "198.18.0.1", // benchmarking "198.51.100.1", // TEST-NET-2 "203.0.113.1", // TEST-NET-3 + "169.254.169.254", // Cloud metadata "192.88.99.1", // 6to4 relay anycast "64:ff9b::1", // NAT64 "64:ff9b::a9fe:a9fe", // NAT64 embedding 169.254.169.254 "2001::1", // Teredo "2002::1", // 6to4 "2001:db8::1", // documentation + "fe80::1", // link local address } { addr := net.ParseIP(ip) assert.Falsef(t, external.MatchIPAddr(addr), "reserved ip %s must not be external", ip) - assert.Truef(t, private.MatchIPAddr(addr), "reserved ip %s should match private block-list", ip) + assert.Falsef(t, private.MatchIPAddr(addr), "reserved ip %s should match private block-list", ip) } } -- 2.54.0 From 5368542f8e8066a3eac40b750d2b47ab8427fcaa Mon Sep 17 00:00:00 2001 From: bircni Date: Sat, 20 Jun 2026 22:26:22 +0200 Subject: [PATCH 004/169] fix(cli): default must-change-password to false for bot users (#38175) --- cmd/admin_user_create.go | 3 ++- cmd/admin_user_create_test.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 340ea072d5..81b6f87a31 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -158,7 +158,8 @@ func runCreateUser(ctx context.Context, c *cli.Command) error { } isAdmin := c.Bool("admin") - mustChangePassword := true // always default to true + // Only local, existing, regular users should be forced to update their password. Bot users for example are non-interactive + mustChangePassword := userType == user_model.UserTypeIndividual if c.IsSet("must-change-password") { if userType != user_model.UserTypeIndividual { return errors.New("must-change-password flag can only be set for individual users") diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index f3794c5dff..ece2e8869d 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -63,6 +63,7 @@ func TestAdminUserCreate(t *testing.T) { u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"}) assert.Equal(t, user_model.UserTypeBot, u.Type) assert.Empty(t, u.Passwd) + assert.False(t, u.MustChangePassword, "bot users should not be forced to change password") }) t.Run("AccessToken", func(t *testing.T) { -- 2.54.0 From 804b9bf1206ac99b77f501d1021968d8c691bc80 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 21 Jun 2026 08:07:13 +0200 Subject: [PATCH 005/169] chore: upgrade eslint plugins, remove `eslint-plugin-github` (#38046) - Bump `eslint`, `typescript-eslint` and `eslint-plugin-unicorn` (to v68), and configure the rules added in unicorn v66/v67/v68. - Remove `eslint-plugin-github` and its workarounds (rules, type stub, pnpm peer override, in-code `eslint-disable` comments); the rules worth keeping are covered by `unicorn` equivalents. - Apply the resulting fixes and autofixes across the JS codebase. _Prepared with Claude (Opus 4.8)._ --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- eslint.config.ts | 235 ++++- package.json | 9 +- pnpm-lock.yaml | 958 ++++++------------ pnpm-workspace.yaml | 4 - tailwind.config.ts | 2 +- tools/ci-tools.ts | 2 +- tools/eslint-rules/unescaped-html-literal.ts | 40 +- tools/generate-images.ts | 2 +- tools/generate-svg.ts | 2 +- types.d.ts | 12 - vite.config.ts | 2 +- web_src/js/components/ActionRunView.ts | 4 +- web_src/js/components/ViewFileTreeStore.ts | 4 +- web_src/js/components/WorkflowGraph.utils.ts | 2 +- web_src/js/features/admin/config.test.ts | 4 +- web_src/js/features/admin/config.ts | 10 +- web_src/js/features/admin/users.ts | 4 +- web_src/js/features/common-button.ts | 6 +- web_src/js/features/common-fetch-action.ts | 2 +- web_src/js/features/common-form.ts | 16 +- web_src/js/features/common-issue-list.ts | 2 +- web_src/js/features/dropzone.ts | 4 +- web_src/js/features/heatmap.ts | 4 +- web_src/js/features/imagediff.ts | 2 +- web_src/js/features/install.ts | 2 +- web_src/js/features/notification.ts | 4 +- web_src/js/features/repo-code.ts | 4 +- web_src/js/features/repo-commit.ts | 4 +- web_src/js/features/repo-common.ts | 2 +- web_src/js/features/repo-diff.ts | 4 +- web_src/js/features/repo-graph.ts | 4 +- web_src/js/features/repo-home.ts | 5 +- web_src/js/features/repo-issue-content.ts | 2 +- web_src/js/features/repo-issue-list.ts | 9 +- web_src/js/features/repo-issue.ts | 6 +- web_src/js/features/repo-projects.ts | 4 +- web_src/js/features/repo-release.ts | 2 +- web_src/js/features/user-auth-webauthn.ts | 6 +- web_src/js/markup/html2markdown.ts | 20 +- web_src/js/markup/render-iframe.test.ts | 1 - web_src/js/markup/render-iframe.ts | 2 +- web_src/js/modules/codeeditor/main.ts | 4 +- web_src/js/modules/errors.ts | 7 +- web_src/js/modules/fomantic/dropdown.ts | 10 +- web_src/js/modules/fomantic/tab.ts | 2 +- web_src/js/modules/tippy.ts | 2 +- web_src/js/modules/toast.ts | 2 +- web_src/js/modules/worker.ts | 2 +- .../js/render/plugins/inplace-pdf-viewer.ts | 4 +- web_src/js/utils/dom.ts | 4 +- web_src/js/utils/match.ts | 2 +- web_src/js/utils/testhelper.ts | 4 +- web_src/js/utils/time.ts | 2 +- web_src/js/webcomponents/overflow-menu.ts | 4 +- 54 files changed, 622 insertions(+), 840 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index a31b6c1fc7..9083ae9814 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,7 +1,6 @@ import arrayFunc from 'eslint-plugin-array-func'; import comments from '@eslint-community/eslint-plugin-eslint-comments'; import deMorgan from 'eslint-plugin-de-morgan'; -import github from 'eslint-plugin-github'; import globals from 'globals'; import importPlugin from 'eslint-plugin-import-x'; import playwright from 'eslint-plugin-playwright'; @@ -72,7 +71,6 @@ export default defineConfig([ regexp, sonarjs, unicorn, - github, wc, }, settings: { @@ -86,7 +84,6 @@ export default defineConfig([ '@eslint-community/eslint-comments/no-duplicate-disable': [2], '@eslint-community/eslint-comments/no-restricted-disable': [0], '@eslint-community/eslint-comments/no-unlimited-disable': [2], - '@eslint-community/eslint-comments/no-unused-disable': [2], '@eslint-community/eslint-comments/no-unused-enable': [2], '@eslint-community/eslint-comments/no-use': [0], '@eslint-community/eslint-comments/require-description': [0], @@ -193,7 +190,6 @@ export default defineConfig([ '@typescript-eslint/no-duplicate-type-constituents': [2, {ignoreUnions: true}], '@typescript-eslint/no-dynamic-delete': [0], '@typescript-eslint/no-empty-function': [0], - '@typescript-eslint/no-empty-interface': [0], '@typescript-eslint/no-empty-object-type': [2], '@typescript-eslint/no-explicit-any': [0], '@typescript-eslint/no-extra-non-null-assertion': [2], @@ -206,7 +202,6 @@ export default defineConfig([ '@typescript-eslint/no-invalid-this': [0], '@typescript-eslint/no-invalid-void-type': [0], '@typescript-eslint/no-loop-func': [0], - '@typescript-eslint/no-loss-of-precision': [0], '@typescript-eslint/no-magic-numbers': [0], '@typescript-eslint/no-meaningless-void-operator': [0], '@typescript-eslint/no-misused-new': [2], @@ -235,7 +230,7 @@ export default defineConfig([ '@typescript-eslint/no-unsafe-assignment': [0], '@typescript-eslint/no-unsafe-call': [0], '@typescript-eslint/no-unsafe-declaration-merging': [2], - '@typescript-eslint/no-unsafe-enum-comparison': [2], + '@typescript-eslint/no-unsafe-enum-comparison': [0], '@typescript-eslint/no-unsafe-function-type': [2], '@typescript-eslint/no-unsafe-member-access': [0], '@typescript-eslint/no-unsafe-return': [0], @@ -279,7 +274,6 @@ export default defineConfig([ '@typescript-eslint/strict-void-return': [0], '@typescript-eslint/switch-exhaustiveness-check': [0], '@typescript-eslint/triple-slash-reference': [2], - '@typescript-eslint/typedef': [0], '@typescript-eslint/unbound-method': [0], // too many false-positives '@typescript-eslint/unified-signatures': [2], 'accessor-pairs': [2], @@ -312,32 +306,9 @@ export default defineConfig([ 'func-names': [0], 'func-style': [0], 'getter-return': [2], - 'github/a11y-aria-label-is-well-formatted': [0], - 'github/a11y-no-title-attribute': [0], - 'github/a11y-no-visually-hidden-interactive-element': [0], - 'github/a11y-role-supports-aria-props': [0], - 'github/a11y-svg-has-accessible-name': [0], - 'github/array-foreach': [0], - 'github/async-currenttarget': [2], - 'github/async-preventdefault': [0], // https://github.com/github/eslint-plugin-github/issues/599 - 'github/authenticity-token': [0], - 'github/get-attribute': [0], - 'github/js-class-name': [0], - 'github/no-blur': [0], - 'github/no-d-none': [0], - 'github/no-dataset': [2], - 'github/no-dynamic-script-tag': [2], - 'github/no-implicit-buggy-globals': [2], - 'github/no-inner-html': [0], - 'github/no-innerText': [2], - 'github/no-then': [2], - 'github/no-useless-passive': [2], - 'github/prefer-observers': [0], - 'github/require-passive-events': [2], - 'gitea/unescaped-html-literal': [2], 'grouped-accessor-pairs': [2], 'guard-for-in': [0], - 'id-blacklist': [0], + 'id-denylist': [0], 'id-length': [0], 'id-match': [0], 'import-x/consistent-type-specifier-style': [0], @@ -384,7 +355,6 @@ export default defineConfig([ 'import-x/prefer-default-export': [0], 'import-x/unambiguous': [0], 'init-declarations': [0], - 'line-comment-position': [0], 'logical-assignment-operators': [0], 'max-classes-per-file': [0], 'max-depth': [0], @@ -393,14 +363,12 @@ export default defineConfig([ 'max-nested-callbacks': [0], 'max-params': [0], 'max-statements': [0], - 'multiline-comment-style': [0], 'new-cap': [0], 'no-alert': [0], 'no-array-constructor': [0], // handled by @typescript-eslint/no-array-constructor 'no-async-promise-executor': [0], 'no-await-in-loop': [0], 'no-bitwise': [0], - 'no-buffer-constructor': [0], 'no-caller': [2], 'no-case-declarations': [2], 'no-class-assign': [2], @@ -557,12 +525,11 @@ export default defineConfig([ 'no-nested-ternary': [0], 'no-new-func': [0], // handled by @typescript-eslint/no-implied-eval 'no-new-native-nonconstructor': [2], - 'no-new-object': [2], - 'no-new-symbol': [0], // handled by no-new-native-nonconstructor 'no-new-wrappers': [2], 'no-new': [0], 'no-nonoctal-decimal-escape': [2], 'no-obj-calls': [2], + 'no-object-constructor': [2], 'no-octal-escape': [2], 'no-octal': [2], 'no-param-reassign': [0], @@ -622,10 +589,8 @@ export default defineConfig([ 'no-warning-comments': [0], 'no-with': [0], // handled by no-restricted-syntax 'object-shorthand': [2, 'always'], - 'one-var-declaration-per-line': [0], 'one-var': [0], 'operator-assignment': [2, 'always'], - 'operator-linebreak': [0], // handled by @stylistic/operator-linebreak 'prefer-arrow-callback': [2, {allowNamedFunctions: true, allowUnboundThis: true}], 'prefer-const': [2, {destructuring: 'all', ignoreReadBeforeAssign: true}], 'prefer-destructuring': [0], @@ -761,139 +726,309 @@ export default defineConfig([ 'strict': [0], 'symbol-description': [2], 'unicode-bom': [2, 'never'], - 'unicorn/better-regex': [0], + 'unicorn/better-dom-traversing': [2], 'unicorn/catch-error-name': [0], + 'unicorn/class-reference-in-static-methods': [2], + 'unicorn/comment-content': [0], + 'unicorn/consistent-assert': [0], + 'unicorn/consistent-boolean-name': [0], + 'unicorn/consistent-class-member-order': [0], + 'unicorn/consistent-compound-words': [0], // too opinionated + 'unicorn/consistent-conditional-object-spread': [2], + 'unicorn/consistent-date-clone': [2], 'unicorn/consistent-destructuring': [2], 'unicorn/consistent-empty-array-spread': [2], - 'unicorn/consistent-template-literal-escape': [2], 'unicorn/consistent-existence-index-check': [0], + 'unicorn/consistent-export-decorator-position': [2], 'unicorn/consistent-function-scoping': [0], + 'unicorn/consistent-function-style': [2], + 'unicorn/consistent-json-file-read': [2], + 'unicorn/consistent-optional-chaining': [2], + 'unicorn/consistent-template-literal-escape': [2], 'unicorn/custom-error-definition': [0], + 'unicorn/default-export-style': [2], + 'unicorn/dom-node-dataset': [2, {preferAttributes: true}], 'unicorn/empty-brace-spaces': [2], 'unicorn/error-message': [0], 'unicorn/escape-case': [0], 'unicorn/expiring-todo-comments': [0], 'unicorn/explicit-length-check': [0], + 'unicorn/explicit-timer-delay': [2], 'unicorn/filename-case': [0], - 'unicorn/import-index': [0], + 'unicorn/id-match': [2], 'unicorn/import-style': [0], 'unicorn/isolated-functions': [2, {functions: []}], + 'unicorn/logical-assignment-operators': [0], + 'unicorn/max-nested-calls': [0], + 'unicorn/name-replacements': [0], 'unicorn/new-for-builtins': [2], 'unicorn/no-abusive-eslint-disable': [0], + 'unicorn/no-accessor-recursion': [2], + 'unicorn/no-accidental-bitwise-operator': [2], 'unicorn/no-anonymous-default-export': [0], 'unicorn/no-array-callback-reference': [0], - 'unicorn/no-array-for-each': [2], + 'unicorn/no-array-concat-in-loop': [2], + 'unicorn/no-array-fill-with-reference-type': [2], + 'unicorn/no-array-from-fill': [2], + 'unicorn/no-array-front-mutation': [0], 'unicorn/no-array-method-this-argument': [2], - 'unicorn/no-array-push-push': [2], 'unicorn/no-array-reduce': [2], + 'unicorn/no-array-reverse': [0], + 'unicorn/no-array-sort': [0], + 'unicorn/no-array-sort-for-min-max': [2], + 'unicorn/no-array-splice': [0], + 'unicorn/no-asterisk-prefix-in-documentation-comments': [0], 'unicorn/no-await-expression-member': [0], 'unicorn/no-await-in-promise-methods': [2], + 'unicorn/no-blob-to-file': [2], + 'unicorn/no-boolean-sort-comparator': [2], + 'unicorn/no-break-in-nested-loop': [0], + 'unicorn/no-canvas-to-image': [2], + 'unicorn/no-chained-comparison': [2], + 'unicorn/no-collection-bracket-access': [2], + 'unicorn/no-computed-property-existence-check': [0], + 'unicorn/no-confusing-array-splice': [2], + 'unicorn/no-confusing-array-with': [2], 'unicorn/no-console-spaces': [0], + 'unicorn/no-constant-zero-expression': [2], + 'unicorn/no-declarations-before-early-exit': [0], 'unicorn/no-document-cookie': [2], + 'unicorn/no-double-comparison': [2], + 'unicorn/no-duplicate-if-branches': [2], + 'unicorn/no-duplicate-logical-operands': [2], + 'unicorn/no-duplicate-loops': [0], + 'unicorn/no-duplicate-set-values': [2], 'unicorn/no-empty-file': [2], + 'unicorn/no-error-property-assignment': [2], + 'unicorn/no-exports-in-scripts': [2], + 'unicorn/no-for-each': [2], 'unicorn/no-for-loop': [0], - 'unicorn/no-hex-escape': [0], + 'unicorn/no-global-object-property-assignment': [0], 'unicorn/no-immediate-mutation': [0], - 'unicorn/no-instanceof-array': [0], + 'unicorn/no-impossible-length-comparison': [2], + 'unicorn/no-incorrect-query-selector': [2], + 'unicorn/no-incorrect-template-string-interpolation': [0], + 'unicorn/no-instanceof-builtins': [2], + 'unicorn/no-invalid-argument-count': [0], + 'unicorn/no-invalid-character-comparison': [2], 'unicorn/no-invalid-fetch-options': [2], + 'unicorn/no-invalid-file-input-accept': [2], 'unicorn/no-invalid-remove-event-listener': [2], 'unicorn/no-keyword-prefix': [0], - 'unicorn/no-length-as-slice-end': [2], + 'unicorn/no-late-current-target-access': [2], 'unicorn/no-lonely-if': [2], + 'unicorn/no-loop-iterable-mutation': [2], 'unicorn/no-magic-array-flat-depth': [0], + 'unicorn/no-manually-wrapped-comments': [0], // too opinionated + 'unicorn/no-mismatched-map-key': [2], + 'unicorn/no-misrefactored-assignment': [2], + 'unicorn/no-named-default': [2], + 'unicorn/no-negated-array-predicate': [2], + 'unicorn/no-negated-comparison': [2], 'unicorn/no-negated-condition': [0], 'unicorn/no-negation-in-equality-check': [2], 'unicorn/no-nested-ternary': [0], 'unicorn/no-new-array': [0], 'unicorn/no-new-buffer': [0], + 'unicorn/no-non-function-verb-prefix': [0], + 'unicorn/no-nonstandard-builtin-properties': [2], 'unicorn/no-null': [0], 'unicorn/no-object-as-default-parameter': [0], + 'unicorn/no-object-methods-with-collections': [2], + 'unicorn/no-optional-chaining-on-undeclared-variable': [2], 'unicorn/no-process-exit': [0], + 'unicorn/no-redundant-comparison': [2], + 'unicorn/no-return-array-push': [2], + 'unicorn/no-selector-as-dom-name': [2], 'unicorn/no-single-promise-in-promise-methods': [2], 'unicorn/no-static-only-class': [2], + 'unicorn/no-subtraction-comparison': [2], 'unicorn/no-thenable': [2], 'unicorn/no-this-assignment': [2], + 'unicorn/no-this-outside-of-class': [0], // gitea uses `this` in non-class functions + 'unicorn/no-top-level-assignment-in-function': [0], + 'unicorn/no-top-level-side-effects': [0], 'unicorn/no-typeof-undefined': [2], + 'unicorn/no-uncalled-method': [2], + 'unicorn/no-undeclared-class-members': [2], + 'unicorn/no-unnecessary-array-flat-depth': [2], + 'unicorn/no-unnecessary-array-splice-count': [2], 'unicorn/no-unnecessary-await': [2], + 'unicorn/no-unnecessary-boolean-comparison': [2], + 'unicorn/no-unnecessary-global-this': [0], + 'unicorn/no-unnecessary-nested-ternary': [2], 'unicorn/no-unnecessary-polyfills': [2], + 'unicorn/no-unnecessary-slice-end': [2], + 'unicorn/no-unnecessary-splice': [2], 'unicorn/no-unreadable-array-destructuring': [0], + 'unicorn/no-unreadable-for-of-expression': [0], 'unicorn/no-unreadable-iife': [0], + 'unicorn/no-unreadable-new-expression': [0], + 'unicorn/no-unreadable-object-destructuring': [0], + 'unicorn/no-unsafe-buffer-conversion': [2], + 'unicorn/no-unsafe-dom-html': [0], + 'unicorn/no-unsafe-property-key': [0], + 'unicorn/no-unsafe-string-replacement': [0], + 'unicorn/no-unused-array-method-return': [2], 'unicorn/no-unused-properties': [2], + 'unicorn/no-useless-boolean-cast': [2], + 'unicorn/no-useless-coercion': [2], 'unicorn/no-useless-collection-argument': [2], + 'unicorn/no-useless-compound-assignment': [2], + 'unicorn/no-useless-concat': [2], + 'unicorn/no-useless-continue': [2], + 'unicorn/no-useless-delete-check': [2], + 'unicorn/no-useless-else': [0], + 'unicorn/no-useless-error-capture-stack-trace': [2], 'unicorn/no-useless-fallback-in-spread': [2], 'unicorn/no-useless-iterator-to-array': [2], 'unicorn/no-useless-length-check': [2], + 'unicorn/no-useless-logical-operand': [2], + 'unicorn/no-useless-override': [2], 'unicorn/no-useless-promise-resolve-reject': [2], + 'unicorn/no-useless-recursion': [0], 'unicorn/no-useless-spread': [2], 'unicorn/no-useless-switch-case': [2], + 'unicorn/no-useless-template-literals': [2], 'unicorn/no-useless-undefined': [0], + 'unicorn/no-xor-as-exponentiation': [2], 'unicorn/no-zero-fractions': [2], 'unicorn/number-literal-case': [0], 'unicorn/numeric-separators-style': [0], + 'unicorn/operator-assignment': [2], 'unicorn/prefer-add-event-listener': [2], + 'unicorn/prefer-add-event-listener-options': [2], 'unicorn/prefer-array-find': [0], // handled by @typescript-eslint/prefer-find 'unicorn/prefer-array-flat': [2], 'unicorn/prefer-array-flat-map': [2], + 'unicorn/prefer-array-from-async': [2], + 'unicorn/prefer-array-from-map': [2], 'unicorn/prefer-array-index-of': [2], + 'unicorn/prefer-array-iterable-methods': [2], + 'unicorn/prefer-array-last-methods': [2], + 'unicorn/prefer-array-slice': [2], 'unicorn/prefer-array-some': [2], 'unicorn/prefer-at': [0], + 'unicorn/prefer-await': [2], + 'unicorn/prefer-bigint-literals': [2], 'unicorn/prefer-blob-reading-methods': [2], + 'unicorn/prefer-boolean-return': [2], + 'unicorn/prefer-class-fields': [2], + 'unicorn/prefer-classlist-toggle': [2], 'unicorn/prefer-code-point': [0], + 'unicorn/prefer-continue': [0], 'unicorn/prefer-date-now': [2], 'unicorn/prefer-default-parameters': [0], + 'unicorn/prefer-direct-iteration': [2], + 'unicorn/prefer-dispose': [2], 'unicorn/prefer-dom-node-append': [2], - 'unicorn/prefer-dom-node-dataset': [0], + 'unicorn/prefer-dom-node-html-methods': [0], 'unicorn/prefer-dom-node-remove': [2], 'unicorn/prefer-dom-node-text-content': [2], + 'unicorn/prefer-early-return': [0], + 'unicorn/prefer-else-if': [2], 'unicorn/prefer-event-target': [2], 'unicorn/prefer-export-from': [0], + 'unicorn/prefer-flat-math-min-max': [2], + 'unicorn/prefer-get-or-insert-computed': [2], + 'unicorn/prefer-global-number-constants': [2], 'unicorn/prefer-global-this': [0], + 'unicorn/prefer-has-check': [2], + 'unicorn/prefer-hoisting-branch-code': [2], + 'unicorn/prefer-https': [0], // false-positives on namespace and schema URIs + 'unicorn/prefer-identifier-import-export-specifiers': [2], + 'unicorn/prefer-import-meta-properties': [2], 'unicorn/prefer-includes': [0], // handled by @typescript-eslint/prefer-includes - 'unicorn/prefer-json-parse-buffer': [0], + 'unicorn/prefer-includes-over-repeated-comparisons': [0], // too opinionated + 'unicorn/prefer-iterable-in-constructor': [2], + 'unicorn/prefer-iterator-concat': [0], // too opinionated + 'unicorn/prefer-iterator-to-array': [2], + 'unicorn/prefer-iterator-to-array-at-end': [2], 'unicorn/prefer-keyboard-event-key': [2], + 'unicorn/prefer-location-assign': [2], 'unicorn/prefer-logical-operator-over-ternary': [2], + 'unicorn/prefer-map-from-entries': [0], + 'unicorn/prefer-math-abs': [2], + 'unicorn/prefer-math-constants': [2], 'unicorn/prefer-math-min-max': [2], 'unicorn/prefer-math-trunc': [2], + 'unicorn/prefer-minimal-ternary': [0], 'unicorn/prefer-modern-dom-apis': [0], 'unicorn/prefer-modern-math-apis': [2], 'unicorn/prefer-module': [2], 'unicorn/prefer-native-coercion-functions': [2], 'unicorn/prefer-negative-index': [2], 'unicorn/prefer-node-protocol': [2], + 'unicorn/prefer-number-coercion': [0], + 'unicorn/prefer-number-is-safe-integer': [0], 'unicorn/prefer-number-properties': [0], + 'unicorn/prefer-object-define-properties': [2], + 'unicorn/prefer-object-destructuring-defaults': [2], 'unicorn/prefer-object-from-entries': [2], - 'unicorn/prefer-object-has-own': [0], + 'unicorn/prefer-object-iterable-methods': [2], 'unicorn/prefer-optional-catch-binding': [2], + 'unicorn/prefer-path2d': [2], + 'unicorn/prefer-private-class-fields': [0], + 'unicorn/prefer-promise-with-resolvers': [2], 'unicorn/prefer-prototype-methods': [0], 'unicorn/prefer-query-selector': [2], + 'unicorn/prefer-queue-microtask': [2], 'unicorn/prefer-reflect-apply': [0], + 'unicorn/prefer-regexp-escape': [0], 'unicorn/prefer-regexp-test': [2], 'unicorn/prefer-response-static-json': [2], + 'unicorn/prefer-scoped-selector': [0], 'unicorn/prefer-set-has': [0], 'unicorn/prefer-set-size': [2], + 'unicorn/prefer-short-arrow-method': [2], 'unicorn/prefer-simple-condition-first': [0], + 'unicorn/prefer-simple-sort-comparator': [2], + 'unicorn/prefer-single-array-predicate': [2], + 'unicorn/prefer-single-call': [2], + 'unicorn/prefer-single-object-destructuring': [2], + 'unicorn/prefer-single-replace': [2], + 'unicorn/prefer-smaller-scope': [2], + 'unicorn/prefer-split-limit': [0], // too opinionated 'unicorn/prefer-spread': [0], + 'unicorn/prefer-string-match-all': [2], + 'unicorn/prefer-string-pad-start-end': [2], 'unicorn/prefer-string-raw': [0], + 'unicorn/prefer-string-repeat': [2], 'unicorn/prefer-string-replace-all': [0], 'unicorn/prefer-string-slice': [0], 'unicorn/prefer-string-starts-ends-with': [0], // handled by @typescript-eslint/prefer-string-starts-ends-with 'unicorn/prefer-string-trim-start-end': [2], 'unicorn/prefer-structured-clone': [2], 'unicorn/prefer-switch': [0], + 'unicorn/prefer-temporal': [0], 'unicorn/prefer-ternary': [0], 'unicorn/prefer-top-level-await': [0], 'unicorn/prefer-type-error': [0], + 'unicorn/prefer-type-literal-last': [0], + 'unicorn/prefer-uint8array-base64': [0], + 'unicorn/prefer-unary-minus': [2], + 'unicorn/prefer-unicode-code-point-escapes': [0], + 'unicorn/prefer-url-can-parse': [2], + 'unicorn/prefer-url-href': [2], + 'unicorn/prefer-while-loop-condition': [2], 'unicorn/prevent-abbreviations': [0], 'unicorn/relative-url-style': [2], 'unicorn/require-array-join-separator': [2], + 'unicorn/require-array-sort-compare': [0], + 'unicorn/require-css-escape': [2], + 'unicorn/require-module-attributes': [2], + 'unicorn/require-module-specifiers': [0], 'unicorn/require-number-to-fixed-digits-argument': [2], + 'unicorn/require-passive-events': [2], 'unicorn/require-post-message-target-origin': [0], + 'unicorn/require-proxy-trap-boolean-return': [2], '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], + 'unicorn/try-complexity': [0], 'use-isnan': [2], 'valid-typeof': [2, {requireStringLiterals: true}], 'vars-on-top': [0], @@ -956,6 +1091,7 @@ export default defineConfig([ languageOptions: {globals: globals.vitest}, rules: { 'gitea/unescaped-html-literal': [0], + 'unicorn/no-error-property-assignment': [0], 'vitest/consistent-test-filename': [0], 'vitest/consistent-test-it': [0], 'vitest/expect-expect': [0], @@ -967,7 +1103,6 @@ export default defineConfig([ 'vitest/no-conditional-in-test': [0], 'vitest/no-conditional-tests': [0], 'vitest/no-disabled-tests': [0], - 'vitest/no-done-callback': [0], 'vitest/no-duplicate-hooks': [0], 'vitest/no-focused-tests': [2], 'vitest/no-hooks': [0], diff --git a/package.json b/package.json index 137d5a0783..e170b7850f 100644 --- a/package.json +++ b/package.json @@ -89,19 +89,18 @@ "@types/swagger-ui-dist": "3.30.6", "@types/throttle-debounce": "5.0.2", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.61.0", + "@typescript-eslint/parser": "8.61.1", "@vitejs/plugin-vue": "6.0.7", "@vitest/eslint-plugin": "1.6.20", - "eslint": "10.4.1", + "eslint": "10.5.0", "eslint-import-resolver-typescript": "4.4.5", "eslint-plugin-array-func": "5.1.1", "eslint-plugin-de-morgan": "2.1.2", - "eslint-plugin-github": "6.0.0", "eslint-plugin-import-x": "4.16.2", "eslint-plugin-playwright": "2.10.4", "eslint-plugin-regexp": "3.1.0", "eslint-plugin-sonarjs": "4.0.3", - "eslint-plugin-unicorn": "64.0.0", + "eslint-plugin-unicorn": "68.0.0", "eslint-plugin-vue": "10.9.2", "eslint-plugin-vue-scoped-css": "3.1.1", "eslint-plugin-wc": "3.1.0", @@ -119,7 +118,7 @@ "stylelint-value-no-unknown-custom-properties": "6.1.1", "svgo": "4.0.1", "typescript": "6.0.3", - "typescript-eslint": "8.61.0", + "typescript-eslint": "8.61.1", "updates": "17.18.0", "vitest": "4.1.8", "vue-tsc": "3.3.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90b34abce4..0493673957 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,7 +209,7 @@ importers: devDependencies: '@eslint-community/eslint-plugin-eslint-comments': specifier: 4.7.2 - version: 4.7.2(eslint@10.4.1(jiti@2.7.0)) + version: 4.7.2(eslint@10.5.0(jiti@2.7.0)) '@eslint/json': specifier: 2.0.0 version: 2.0.0 @@ -218,7 +218,7 @@ importers: version: 1.60.0 '@stylistic/eslint-plugin': specifier: 5.10.0 - version: 5.10.0(eslint@10.4.1(jiti@2.7.0)) + version: 5.10.0(eslint@10.5.0(jiti@2.7.0)) '@stylistic/stylelint-plugin': specifier: 5.2.0 version: 5.2.0(stylelint@17.13.0(typescript@6.0.3)) @@ -253,50 +253,47 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.61.0 - version: 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.61.1 + version: 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) '@vitest/eslint-plugin': specifier: 1.6.20 - version: 1.6.20(@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.3)(happy-dom@20.10.2)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0))) + version: 1.6.20(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.3)(happy-dom@20.10.2)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0))) eslint: - specifier: 10.4.1 - version: 10.4.1(jiti@2.7.0) + specifier: 10.5.0 + version: 10.5.0(jiti@2.7.0) eslint-import-resolver-typescript: specifier: 4.4.5 - version: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) + version: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-array-func: specifier: 5.1.1 - version: 5.1.1(eslint@10.4.1(jiti@2.7.0)) + version: 5.1.1(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-de-morgan: specifier: 2.1.2 - version: 2.1.2(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-github: - specifier: 6.0.0 - version: 6.0.0(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) + version: 2.1.2(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-import-x: specifier: 4.16.2 - version: 4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)) + version: 4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-playwright: specifier: 2.10.4 - version: 2.10.4(eslint@10.4.1(jiti@2.7.0)) + version: 2.10.4(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-regexp: specifier: 3.1.0 - version: 3.1.0(eslint@10.4.1(jiti@2.7.0)) + version: 3.1.0(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-sonarjs: specifier: 4.0.3 - version: 4.0.3(eslint@10.4.1(jiti@2.7.0)) + version: 4.0.3(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-unicorn: - specifier: 64.0.0 - version: 64.0.0(eslint@10.4.1(jiti@2.7.0)) + specifier: 68.0.0 + version: 68.0.0(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-vue: specifier: 10.9.2 - version: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) + version: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))) eslint-plugin-vue-scoped-css: specifier: 3.1.1 - version: 3.1.1(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))) + version: 3.1.1(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))) eslint-plugin-wc: specifier: 3.1.0 - version: 3.1.0(eslint@10.4.1(jiti@2.7.0)) + version: 3.1.0(eslint@10.5.0(jiti@2.7.0)) globals: specifier: 17.6.0 version: 17.6.0 @@ -340,8 +337,8 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.61.0 - version: 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.61.1 + version: 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) updates: specifier: 17.18.0 version: 17.18.0 @@ -764,15 +761,6 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@1.4.1': - resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.40 || 9 - peerDependenciesMeta: - eslint: - optional: true - '@eslint/config-array@0.23.5': resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -781,22 +769,10 @@ packages: resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.2.1': resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.5': - resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.39.4': - resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/json@2.0.0': resolution: {integrity: sha512-P32ZJMIopNWQd1SFhd0tgjfA/hgzUuVSqHmMi2273QaLWHWimXq6V+qL4DNKnjGzO/aNECtYW+rEJ/pWB6uP+w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -809,9 +785,6 @@ packages: resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@github/browserslist-config@1.0.0': - resolution: {integrity: sha512-gIhjdJp/c2beaIWWIlsXdqXVRUz3r2BxBCpfz/F3JXHvSAQ1paMYjLH+maEATtENg+k5eLV7gA+9yPp762ieuw==} - '@github/combobox-nav@2.3.1': resolution: {integrity: sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A==} @@ -987,10 +960,6 @@ packages: '@package-json/types@0.0.12': resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} - '@pkgr/core@0.3.6': - resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} - engines: {node: ^14.18.0 || >=16.0.0} - '@playwright/test@1.60.0': resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} engines: {node: '>=18'} @@ -1404,16 +1373,16 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.61.0': - resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} + '@typescript-eslint/eslint-plugin@8.61.1': + resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.61.0 + '@typescript-eslint/parser': ^8.61.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.61.0': - resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==} + '@typescript-eslint/parser@8.61.1': + resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1425,18 +1394,34 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.61.1': + resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.61.0': resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.61.1': + resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.61.0': resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.61.0': - resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==} + '@typescript-eslint/tsconfig-utils@8.61.1': + resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.61.1': + resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1446,12 +1431,22 @@ packages: resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.61.1': + resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.61.0': resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.61.1': + resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.61.0': resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1459,10 +1454,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.61.1': + resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.61.0': resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.61.1': + resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] @@ -1757,10 +1763,6 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -1799,9 +1801,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -1823,14 +1822,6 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - axe-core@4.12.0: - resolution: {integrity: sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg==} - engines: {node: '>=4'} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1985,10 +1976,6 @@ packages: citeproc@2.4.63: resolution: {integrity: sha512-68F95Bp4UbgZU/DBUGQn0qV3HDZLCdI9+Bb2ByrTaNJDL5VEm9LqaiNaxljsvoaExSLEXe1/r6n2Z06SCzW3/Q==} - clean-regexp@1.0.0: - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} - engines: {node: '>=4'} - cli-cursor@1.0.2: resolution: {integrity: sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==} engines: {node: '>=0.10.0'} @@ -2297,9 +2284,6 @@ packages: dagre-d3-es@7.0.14: resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -2376,6 +2360,10 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-indent@7.0.2: + resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==} + engines: {node: '>=12.20'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2442,9 +2430,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -2524,12 +2509,6 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - eslint-import-context@0.1.9: resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -2588,33 +2567,6 @@ packages: peerDependencies: eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-escompat@3.11.4: - resolution: {integrity: sha512-j0ywwNnIufshOzgAu+PfIig1c7VRClKSNKzpniMT2vXQ4leL5q+e/SpMFQU0nrdL2WFFM44XmhSuwmxb3G0CJg==} - peerDependencies: - eslint: '>=5.14.1' - - eslint-plugin-eslint-comments@3.2.0: - resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} - engines: {node: '>=6.5.0'} - peerDependencies: - eslint: '>=4.19.1' - - eslint-plugin-filenames@1.3.2: - resolution: {integrity: sha512-tqxJTiEM5a0JmRCUYQmxw23vtTxrb2+a3Q2mMOPhFxvt7ZQQJmdiuMby9B/vUAuVMghyP7oET+nIf6EO6CBd/w==} - peerDependencies: - eslint: '*' - - eslint-plugin-github@6.0.0: - resolution: {integrity: sha512-J8MvUoiR/TU/Y9NnEmg1AnbvMUj9R6IO260z47zymMLLvso7B4c80IKjd8diqmqtSmeXXlbIus4i0SvK84flag==} - hasBin: true - peerDependencies: - eslint: ^8 || ^9 - - eslint-plugin-i18n-text@1.0.1: - resolution: {integrity: sha512-3G3UetST6rdqhqW9SfcfzNYMpQXS7wNkJvp6dsXnjzGiku6Iu5hl3B0kmk6lIcFPwYjhQIY+tXVRtK9TlGT7RA==} - peerDependencies: - eslint: '>=5.0.0' - eslint-plugin-import-x@4.16.2: resolution: {integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2638,36 +2590,12 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-jsx-a11y@6.10.2: - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - - eslint-plugin-no-only-tests@3.4.0: - resolution: {integrity: sha512-4S3/9Nb7A2tiMcpzEQE9bQSlpeOz6WJkgryBuou/SA8W2x2c8Zf4j0NvTKBjv6qNhF9T79tmkecm/0CHqV0UGg==} - engines: {node: '>=5.0.0'} - eslint-plugin-playwright@2.10.4: resolution: {integrity: sha512-l0V/VxyqfFbtqCTxj5AdRn3Q6S/hIW4nKBnKZVleVbZ24N2My6Usj//ytX3dKKqAoSbvKck9YtSytfdZ5qjLuA==} engines: {node: '>=16.9.0'} peerDependencies: eslint: '>=8.40.0' - eslint-plugin-prettier@5.5.6: - resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - eslint-plugin-regexp@3.1.0: resolution: {integrity: sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2679,11 +2607,11 @@ packages: peerDependencies: eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-unicorn@64.0.0: - resolution: {integrity: sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==} - engines: {node: ^20.10.0 || >=21.0.0} + eslint-plugin-unicorn@68.0.0: + resolution: {integrity: sha512-mHYWvX948Q4H3bGc39bsNMxD/leOuNI+Iws9NVsoSz5VA7EGP86wnz7mZ/SPSvRhefT8L4hd8DHfDuGC+lIoCQ==} + engines: {node: '>=22'} peerDependencies: - eslint: '>=9.38.0' + eslint: '>=10.4' eslint-plugin-vue-scoped-css@3.1.1: resolution: {integrity: sha512-GIskMvLPnDtiu88rWXQHy2b2QZ4j959N5UgghML64jH0sg3Km+HRa9m7nkpcEBGLD4iA4vtMDbBIoLdFcbT8lQ==} @@ -2718,10 +2646,6 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-rule-documentation@1.0.23: - resolution: {integrity: sha512-pWReu3fkohwyvztx/oQWWgld2iad25TfUdi6wvhhaDPIQjHU/pyvlKgXFw1kX31SQK2Nq9MH+vRDWB0ZLy8fYw==} - engines: {node: '>=4.0.0'} - eslint-scope@9.1.2: resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -2738,8 +2662,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.4.1: - resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + eslint@10.5.0: + resolution: {integrity: sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -2808,9 +2732,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -2973,14 +2894,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} - engines: {node: '>=18'} - globals@17.6.0: resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} @@ -3427,10 +3340,6 @@ packages: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - kaiser@0.0.4: resolution: {integrity: sha512-m8ju+rmBqvclZmyrOXgGGhOYSjKJK6RN1NhqEltemY87UqZOxEkizg9TOy1vQSyJ01Wx6SAPuuN0iO2Mgislvw==} @@ -3462,13 +3371,6 @@ packages: klaw@1.3.1: resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -3573,24 +3475,12 @@ packages: lodash-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash.upperfirst@4.3.1: - resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - lodash@3.10.1: resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==} @@ -4058,15 +3948,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-linter-helpers@1.0.1: - resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} - engines: {node: '>=6.0.0'} - - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} - engines: {node: '>=14'} - hasBin: true - pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4124,10 +4005,6 @@ packages: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - regexp-tree@0.1.27: - resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} - hasBin: true - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -4258,6 +4135,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + seroval-plugins@1.5.4: resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} engines: {node: '>=10'} @@ -4391,10 +4273,6 @@ packages: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} - string.prototype.includes@2.0.1: - resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} - engines: {node: '>= 0.4'} - string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -4494,9 +4372,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svg-element-attributes@1.3.1: - resolution: {integrity: sha512-Bh05dSOnJBf3miNMqpsormfNtfidA/GxQVakhtn0T4DECWKeXQRQUceYjJ+OxYiiLdGe4Jo9iFV8wICFapFeIA==} - svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} @@ -4518,10 +4393,6 @@ packages: resolution: {integrity: sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==} engines: {node: '>=14'} - synckit@0.11.13: - resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} - engines: {node: ^14.18.0 || >=16.0.0} - table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -4640,18 +4511,13 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.61.0: - resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==} + typescript-eslint@8.61.1: + resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -5430,25 +5296,19 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.4.1(jiti@2.7.0))': + '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.5.0(jiti@2.7.0))': dependencies: escape-string-regexp: 4.0.0 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) ignore: 7.0.5 - '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.5.0(jiti@2.7.0))': dependencies: - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@1.4.1(eslint@10.4.1(jiti@2.7.0))': - dependencies: - '@eslint/core': 0.17.0 - optionalDependencies: - eslint: 10.4.1(jiti@2.7.0) - '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 @@ -5461,30 +5321,10 @@ snapshots: dependencies: '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5': - dependencies: - ajv: 6.15.0 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.2.0 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.4': {} - '@eslint/json@2.0.0': dependencies: '@eslint/core': 1.2.1 @@ -5499,8 +5339,6 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@github/browserslist-config@1.0.0': {} - '@github/combobox-nav@2.3.1': {} '@github/markdown-toolbar-element@2.2.3': {} @@ -5731,8 +5569,6 @@ snapshots: '@package-json/types@0.0.12': {} - '@pkgr/core@0.3.6': {} - '@playwright/test@1.60.0': dependencies: playwright: 1.60.0 @@ -5836,7 +5672,8 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rtsao/scc@1.1.0': {} + '@rtsao/scc@1.1.0': + optional: true '@scarf/scarf@1.4.0': {} @@ -5872,11 +5709,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) '@typescript-eslint/types': 8.61.0 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -6066,7 +5903,8 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} + '@types/json5@0.0.29': + optional: true '@types/katex@0.16.8': {} @@ -6113,31 +5951,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/type-utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.61.0 - eslint: 10.4.1(jiti@2.7.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/type-utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.0 - eslint: 10.4.1(jiti@2.7.0) + '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.1 + eslint: 10.5.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -6145,39 +5967,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.61.0 + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.61.1 debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.0 - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.61.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) - '@typescript-eslint/types': 8.61.0 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.61.0(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3) @@ -6187,38 +5988,40 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.61.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) + '@typescript-eslint/types': 8.61.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.61.0': dependencies: '@typescript-eslint/types': 8.61.0 '@typescript-eslint/visitor-keys': 8.61.0 - '@typescript-eslint/tsconfig-utils@8.61.0(typescript@5.9.3)': + '@typescript-eslint/scope-manager@8.61.1': dependencies: - typescript: 5.9.3 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/visitor-keys': 8.61.1 '@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + typescript: 6.0.3 - '@typescript-eslint/type-utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -6226,20 +6029,7 @@ snapshots: '@typescript-eslint/types@8.61.0': {} - '@typescript-eslint/typescript-estree@8.61.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/project-service': 8.61.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@5.9.3) - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/visitor-keys': 8.61.0 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.1 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types@8.61.1': {} '@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)': dependencies: @@ -6256,24 +6046,39 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/types': 8.61.0 - '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) - eslint: 10.4.1(jiti@2.7.0) - typescript: 5.9.3 + '@typescript-eslint/project-service': 8.61.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/visitor-keys': 8.61.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.61.0 '@typescript-eslint/types': 8.61.0 '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.61.1 + '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) + eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6283,6 +6088,11 @@ snapshots: '@typescript-eslint/types': 8.61.0 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.61.1': + dependencies: + '@typescript-eslint/types': 8.61.1 + eslint-visitor-keys: 5.0.1 + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true @@ -6364,13 +6174,13 @@ snapshots: vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0) vue: 3.5.37(typescript@6.0.3) - '@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.3)(happy-dom@20.10.2)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)))': + '@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.8(@types/node@25.9.3)(happy-dom@20.10.2)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)))': dependencies: '@typescript-eslint/scope-manager': 8.61.0 - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.1(jiti@2.7.0) + '@typescript-eslint/utils': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.5.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 vitest: 4.1.8(@types/node@25.9.3)(happy-dom@20.10.2)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)) transitivePeerDependencies: @@ -6561,12 +6371,11 @@ snapshots: argparse@2.0.1: {} - aria-query@5.3.2: {} - array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 is-array-buffer: 3.0.5 + optional: true array-includes@3.1.9: dependencies: @@ -6578,6 +6387,7 @@ snapshots: get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 + optional: true array.prototype.findlastindex@1.2.6: dependencies: @@ -6588,6 +6398,7 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 + optional: true array.prototype.flat@1.3.3: dependencies: @@ -6595,6 +6406,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 + optional: true array.prototype.flatmap@1.3.3: dependencies: @@ -6602,6 +6414,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 + optional: true arraybuffer.prototype.slice@1.0.4: dependencies: @@ -6612,6 +6425,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + optional: true asciinema-player@3.15.1: dependencies: @@ -6627,26 +6441,22 @@ snapshots: assertion-error@2.0.1: {} - ast-types-flow@0.0.8: {} - astral-regex@2.0.0: {} - async-function@1.0.0: {} + async-function@1.0.0: + optional: true asynckit@0.4.0: {} available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 + optional: true aws-sign2@0.7.0: {} aws4@1.13.2: {} - axe-core@4.12.0: {} - - axobject-query@4.1.0: {} - balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -6732,11 +6542,13 @@ snapshots: es-define-property: 1.0.1 get-intrinsic: 1.3.0 set-function-length: 1.2.2 + optional: true call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + optional: true callsites@3.1.0: {} @@ -6804,10 +6616,6 @@ snapshots: citeproc@2.4.63: {} - clean-regexp@1.0.0: - dependencies: - escape-string-regexp: 1.0.5 - cli-cursor@1.0.2: dependencies: restore-cursor: 1.0.1 @@ -7120,8 +6928,6 @@ snapshots: d3: 7.9.0 lodash-es: 4.18.1 - damerau-levenshtein@1.0.8: {} - dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -7137,24 +6943,28 @@ snapshots: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + optional: true data-view-byte-length@1.0.2: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + optional: true data-view-byte-offset@1.0.1: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-data-view: 1.0.2 + optional: true dayjs@1.11.21: {} debug@3.2.7: dependencies: ms: 2.1.3 + optional: true debug@4.4.3: dependencies: @@ -7180,12 +6990,14 @@ snapshots: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + optional: true define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 + optional: true delaunator@5.1.0: dependencies: @@ -7195,6 +7007,8 @@ snapshots: dequal@2.0.3: {} + detect-indent@7.0.2: {} + detect-libc@2.1.2: {} devlop@1.1.0: @@ -7208,6 +7022,7 @@ snapshots: doctrine@2.1.0: dependencies: esutils: 2.0.3 + optional: true dom-input-range@2.0.1: {} @@ -7271,8 +7086,6 @@ snapshots: emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - entities@4.5.0: {} entities@6.0.1: {} @@ -7341,6 +7154,7 @@ snapshots: typed-array-length: 1.0.8 unbox-primitive: 1.1.0 which-typed-array: 1.1.21 + optional: true es-define-property@1.0.1: {} @@ -7362,12 +7176,14 @@ snapshots: es-shim-unscopables@1.1.0: dependencies: hasown: 2.0.4 + optional: true es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 + optional: true es-toolkit@1.47.0: {} @@ -7416,10 +7232,6 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)): - dependencies: - eslint: 10.4.1(jiti@2.7.0) - eslint-import-context@0.1.9(unrs-resolver@1.12.2): dependencies: get-tsconfig: 4.14.0 @@ -7434,11 +7246,12 @@ snapshots: resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color + optional: true - eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)): + eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)): dependencies: debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 @@ -7446,104 +7259,38 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) + eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - eslint: 10.4.1(jiti@2.7.0) + '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.5.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.1(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.4.1(jiti@2.7.0)) + eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color optional: true - eslint-plugin-array-func@5.1.1(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-array-func@5.1.1(eslint@10.5.0(jiti@2.7.0)): dependencies: - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) - eslint-plugin-de-morgan@2.1.2(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-de-morgan@2.1.2(eslint@10.5.0(jiti@2.7.0)): dependencies: - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) - eslint-plugin-escompat@3.11.4(eslint@10.4.1(jiti@2.7.0)): - dependencies: - browserslist: 4.28.2 - eslint: 10.4.1(jiti@2.7.0) - - eslint-plugin-eslint-comments@3.2.0(eslint@10.4.1(jiti@2.7.0)): - dependencies: - escape-string-regexp: 1.0.5 - eslint: 10.4.1(jiti@2.7.0) - ignore: 5.3.2 - - eslint-plugin-filenames@1.3.2(eslint@10.4.1(jiti@2.7.0)): - dependencies: - eslint: 10.4.1(jiti@2.7.0) - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - lodash.upperfirst: 4.3.1 - - eslint-plugin-github@6.0.0(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@eslint/compat': 1.4.1(eslint@10.4.1(jiti@2.7.0)) - '@eslint/eslintrc': 3.3.5 - '@eslint/js': 9.39.4 - '@github/browserslist-config': 1.0.0 - '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - aria-query: 5.3.2 - eslint: 10.4.1(jiti@2.7.0) - eslint-config-prettier: 10.1.8(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-escompat: 3.11.4(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-eslint-comments: 3.2.0(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-filenames: 1.3.2(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-i18n-text: 1.0.1(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@10.4.1(jiti@2.7.0)) - eslint-plugin-no-only-tests: 3.4.0 - eslint-plugin-prettier: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3) - eslint-rule-documentation: 1.0.23 - globals: 16.5.0 - jsx-ast-utils: 3.3.5 - prettier: 3.8.3 - svg-element-attributes: 1.3.1 - typescript: 5.9.3 - typescript-eslint: 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - transitivePeerDependencies: - - '@types/eslint' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-i18n-text@1.0.1(eslint@10.4.1(jiti@2.7.0)): - dependencies: - eslint: 10.4.1(jiti@2.7.0) - - eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)): dependencies: '@package-json/types': 0.0.12 '@typescript-eslint/types': 8.61.0 comment-parser: 1.4.7 debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.5 @@ -7551,12 +7298,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7565,9 +7312,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -7579,94 +7326,35 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 10.4.1(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.4.1(jiti@2.7.0)) - hasown: 2.0.4 - is-core-module: 2.16.2 - is-glob: 4.0.3 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color optional: true - eslint-plugin-jsx-a11y@6.10.2(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-playwright@2.10.4(eslint@10.5.0(jiti@2.7.0)): dependencies: - aria-query: 5.3.2 - array-includes: 3.1.9 - array.prototype.flatmap: 1.3.3 - ast-types-flow: 0.0.8 - axe-core: 4.12.0 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 10.4.1(jiti@2.7.0) - hasown: 2.0.4 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - safe-regex-test: 1.1.0 - string.prototype.includes: 2.0.1 - - eslint-plugin-no-only-tests@3.4.0: {} - - eslint-plugin-playwright@2.10.4(eslint@10.4.1(jiti@2.7.0)): - dependencies: - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) globals: 17.6.0 - eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@10.4.1(jiti@2.7.0)))(eslint@10.4.1(jiti@2.7.0))(prettier@3.8.3): + eslint-plugin-regexp@3.1.0(eslint@10.5.0(jiti@2.7.0)): dependencies: - eslint: 10.4.1(jiti@2.7.0) - prettier: 3.8.3 - prettier-linter-helpers: 1.0.1 - synckit: 0.11.13 - optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@10.4.1(jiti@2.7.0)) - - eslint-plugin-regexp@3.1.0(eslint@10.4.1(jiti@2.7.0)): - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.7 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) jsdoc-type-pratt-parser: 7.2.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-sonarjs@4.0.3(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-sonarjs@4.0.3(eslint@10.5.0(jiti@2.7.0)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) functional-red-black-tree: 1.0.1 globals: 17.6.0 jsx-ast-utils-x: 0.1.0 @@ -7677,58 +7365,56 @@ snapshots: ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 - eslint-plugin-unicorn@64.0.0(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-unicorn@68.0.0(eslint@10.5.0(jiti@2.7.0)): dependencies: '@babel/helper-validator-identifier': 7.29.7 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) + browserslist: 4.28.2 change-case: 5.4.4 ci-info: 4.4.0 - clean-regexp: 1.0.0 core-js-compat: 3.49.0 - eslint: 10.4.1(jiti@2.7.0) + detect-indent: 7.0.2 + eslint: 10.5.0(jiti@2.7.0) find-up-simple: 1.0.1 globals: 17.6.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 pluralize: 8.0.0 - regexp-tree: 0.1.27 regjsparser: 0.13.1 - semver: 7.8.1 + semver: 7.8.4 strip-indent: 4.1.1 - eslint-plugin-vue-scoped-css@3.1.1(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): + eslint-plugin-vue-scoped-css@3.1.1(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) es-toolkit: 1.47.0 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) postcss: 8.5.15 postcss-safe-parser: 7.0.1(postcss@8.5.15) postcss-selector-parser: 7.1.1 - vue-eslint-parser: 10.4.0(eslint@10.4.1(jiti@2.7.0)) + vue-eslint-parser: 10.4.0(eslint@10.5.0(jiti@2.7.0)) - eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.4.1(jiti@2.7.0)))(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0))): + eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) - eslint: 10.4.1(jiti@2.7.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) + eslint: 10.5.0(jiti@2.7.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.1 semver: 7.8.1 - vue-eslint-parser: 10.4.0(eslint@10.4.1(jiti@2.7.0)) + vue-eslint-parser: 10.4.0(eslint@10.5.0(jiti@2.7.0)) xml-name-validator: 4.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.4.1(jiti@2.7.0)) - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.5.0(jiti@2.7.0)) + '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - eslint-plugin-wc@3.1.0(eslint@10.4.1(jiti@2.7.0)): + eslint-plugin-wc@3.1.0(eslint@10.5.0(jiti@2.7.0)): dependencies: - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) is-valid-element-name: 1.0.0 js-levenshtein-esm: 2.0.0 - eslint-rule-documentation@1.0.23: {} - eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 @@ -7742,9 +7428,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.4.1(jiti@2.7.0): + eslint@10.5.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -7825,8 +7511,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7899,6 +7583,7 @@ snapshots: for-each@0.3.5: dependencies: is-callable: 1.2.7 + optional: true forever-agent@0.6.1: {} @@ -7949,12 +7634,15 @@ snapshots: functions-have-names: 1.2.3 hasown: 2.0.4 is-callable: 1.2.7 + optional: true functional-red-black-tree@1.0.1: {} - functions-have-names@1.2.3: {} + functions-have-names@1.2.3: + optional: true - generator-function@2.0.1: {} + generator-function@2.0.1: + optional: true get-east-asian-width@1.6.0: {} @@ -7981,6 +7669,7 @@ snapshots: call-bound: 1.0.4 es-errors: 1.3.0 get-intrinsic: 1.3.0 + optional: true get-tsconfig@4.14.0: dependencies: @@ -8017,16 +7706,13 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globals@14.0.0: {} - - globals@16.5.0: {} - globals@17.6.0: {} globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 + optional: true globby@16.2.0: dependencies: @@ -8071,7 +7757,8 @@ snapshots: dependencies: ansi-regex: 2.1.1 - has-bigints@1.1.0: {} + has-bigints@1.1.0: + optional: true has-flag@4.0.0: {} @@ -8080,10 +7767,12 @@ snapshots: has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 + optional: true has-proto@1.2.0: dependencies: dunder-proto: 1.0.1 + optional: true has-symbols@1.1.0: {} @@ -8197,6 +7886,7 @@ snapshots: es-errors: 1.3.0 hasown: 2.0.4 side-channel: 1.1.0 + optional: true internmap@1.0.1: {} @@ -8214,6 +7904,7 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 + optional: true is-arrayish@0.2.1: {} @@ -8224,10 +7915,12 @@ snapshots: get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + optional: true is-bigint@1.1.0: dependencies: has-bigints: 1.1.0 + optional: true is-binary-path@2.1.0: dependencies: @@ -8237,6 +7930,7 @@ snapshots: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + optional: true is-buffer@1.1.6: {} @@ -8248,7 +7942,8 @@ snapshots: dependencies: semver: 7.8.1 - is-callable@1.2.7: {} + is-callable@1.2.7: + optional: true is-core-module@2.16.2: dependencies: @@ -8259,11 +7954,13 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 is-typed-array: 1.1.15 + optional: true is-date-object@1.1.0: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + optional: true is-decimal@2.0.1: {} @@ -8272,6 +7969,7 @@ snapshots: is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 + optional: true is-fullwidth-code-point@1.0.0: dependencies: @@ -8286,6 +7984,7 @@ snapshots: get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 + optional: true is-glob@4.0.3: dependencies: @@ -8293,14 +7992,17 @@ snapshots: is-hexadecimal@2.0.1: {} - is-map@2.0.3: {} + is-map@2.0.3: + optional: true - is-negative-zero@2.0.3: {} + is-negative-zero@2.0.3: + optional: true is-number-object@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + optional: true is-number@7.0.0: {} @@ -8314,27 +8016,33 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.4 + optional: true - is-set@2.0.3: {} + is-set@2.0.3: + optional: true is-shared-array-buffer@1.0.4: dependencies: call-bound: 1.0.4 + optional: true is-string@1.1.1: dependencies: call-bound: 1.0.4 has-tostringtag: 1.0.2 + optional: true is-symbol@1.1.1: dependencies: call-bound: 1.0.4 has-symbols: 1.1.0 safe-regex-test: 1.1.0 + optional: true is-typed-array@1.1.15: dependencies: which-typed-array: 1.1.21 + optional: true is-typedarray@1.0.0: {} @@ -8342,18 +8050,22 @@ snapshots: dependencies: is-potential-custom-element-name: 1.0.1 - is-weakmap@2.0.2: {} + is-weakmap@2.0.2: + optional: true is-weakref@1.1.1: dependencies: call-bound: 1.0.4 + optional: true is-weakset@2.0.4: dependencies: call-bound: 1.0.4 get-intrinsic: 1.3.0 + optional: true - isarray@2.0.5: {} + isarray@2.0.5: + optional: true isexe@2.0.0: {} @@ -8477,6 +8189,7 @@ snapshots: json5@1.0.2: dependencies: minimist: 1.2.8 + optional: true jsonc-parser@3.3.1: {} @@ -8495,13 +8208,6 @@ snapshots: jsx-ast-utils-x@0.1.0: {} - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - kaiser@0.0.4: dependencies: earlgrey-runtime: 0.1.2 @@ -8534,12 +8240,6 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 - layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -8617,18 +8317,10 @@ snapshots: lodash-es@4.18.1: {} - lodash.camelcase@4.3.0: {} - - lodash.kebabcase@4.1.1: {} - lodash.merge@4.6.2: {} - lodash.snakecase@4.1.1: {} - lodash.truncate@4.4.2: {} - lodash.upperfirst@4.3.1: {} - lodash@3.10.1: {} lodash@4.18.1: {} @@ -8948,6 +8640,7 @@ snapshots: es-errors: 1.3.0 object.entries: 1.1.9 semver: 6.3.1 + optional: true node-fetch@2.6.13: dependencies: @@ -8975,9 +8668,11 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.4: {} + object-inspect@1.13.4: + optional: true - object-keys@1.1.1: {} + object-keys@1.1.1: + optional: true object.assign@4.1.7: dependencies: @@ -8987,6 +8682,7 @@ snapshots: es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 + optional: true object.entries@1.1.9: dependencies: @@ -8994,6 +8690,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.2 + optional: true object.fromentries@2.0.8: dependencies: @@ -9001,12 +8698,14 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 es-object-atoms: 1.1.2 + optional: true object.groupby@1.0.3: dependencies: call-bind: 1.0.9 define-properties: 1.2.1 es-abstract: 1.24.2 + optional: true object.values@1.2.1: dependencies: @@ -9014,6 +8713,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.2 + optional: true obug@2.1.1: {} @@ -9045,6 +8745,7 @@ snapshots: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 + optional: true p-limit@3.1.0: dependencies: @@ -9128,7 +8829,8 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 - possible-typed-array-names@1.1.0: {} + possible-typed-array-names@1.1.0: + optional: true postcss-html@1.8.1: dependencies: @@ -9189,12 +8891,6 @@ snapshots: prelude-ls@1.2.1: {} - prettier-linter-helpers@1.0.1: - dependencies: - fast-diff: 1.3.0 - - prettier@3.8.3: {} - pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -9249,6 +8945,7 @@ snapshots: get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 + optional: true regenerator-runtime@0.9.6: {} @@ -9257,8 +8954,6 @@ snapshots: '@eslint-community/regexpp': 4.12.2 refa: 0.12.1 - regexp-tree@0.1.27: {} - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.9 @@ -9267,6 +8962,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 + optional: true regjsparser@0.13.1: dependencies: @@ -9326,6 +9022,7 @@ snapshots: object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + optional: true restore-cursor@1.0.1: dependencies: @@ -9398,6 +9095,7 @@ snapshots: get-intrinsic: 1.3.0 has-symbols: 1.1.0 isarray: 2.0.5 + optional: true safe-buffer@5.2.1: {} @@ -9405,12 +9103,14 @@ snapshots: dependencies: es-errors: 1.3.0 isarray: 2.0.5 + optional: true safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 es-errors: 1.3.0 is-regex: 1.2.1 + optional: true safer-buffer@2.1.2: {} @@ -9426,10 +9126,13 @@ snapshots: refa: 0.12.1 regexp-ast-analysis: 0.7.1 - semver@6.3.1: {} + semver@6.3.1: + optional: true semver@7.8.1: {} + semver@7.8.4: {} + seroval-plugins@1.5.4(seroval@1.5.4): dependencies: seroval: 1.5.4 @@ -9444,6 +9147,7 @@ snapshots: get-intrinsic: 1.3.0 gopd: 1.2.0 has-property-descriptors: 1.0.2 + optional: true set-function-name@2.0.2: dependencies: @@ -9451,12 +9155,14 @@ snapshots: es-errors: 1.3.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + optional: true set-proto@1.0.0: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.1.2 + optional: true shebang-command@2.0.0: dependencies: @@ -9468,6 +9174,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 + optional: true side-channel-map@1.0.1: dependencies: @@ -9475,6 +9182,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 object-inspect: 1.13.4 + optional: true side-channel-weakmap@1.0.2: dependencies: @@ -9483,6 +9191,7 @@ snapshots: get-intrinsic: 1.3.0 object-inspect: 1.13.4 side-channel-map: 1.0.1 + optional: true side-channel@1.1.0: dependencies: @@ -9491,6 +9200,7 @@ snapshots: side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + optional: true siginfo@2.0.0: {} @@ -9557,6 +9267,7 @@ snapshots: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 + optional: true string-width@1.0.2: dependencies: @@ -9580,12 +9291,6 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 - string.prototype.includes@2.0.1: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.9 @@ -9595,6 +9300,7 @@ snapshots: es-abstract: 1.24.2 es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + optional: true string.prototype.trimend@1.0.9: dependencies: @@ -9602,12 +9308,14 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.2 + optional: true string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.2 + optional: true strip-ansi@3.0.1: dependencies: @@ -9621,7 +9329,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-bom@3.0.0: {} + strip-bom@3.0.0: + optional: true strip-indent@4.1.1: {} @@ -9717,8 +9426,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svg-element-attributes@1.3.1: {} - svg-tags@1.0.0: {} svgo@4.0.1: @@ -9749,10 +9456,6 @@ snapshots: transitivePeerDependencies: - encoding - synckit@0.11.13: - dependencies: - '@pkgr/core': 0.3.6 - table@6.9.0: dependencies: ajv: 8.20.0 @@ -9844,10 +9547,6 @@ snapshots: tributejs@5.1.3: {} - ts-api-utils@2.5.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -9862,6 +9561,7 @@ snapshots: json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 + optional: true tslib@2.8.1: {} @@ -9882,6 +9582,7 @@ snapshots: call-bound: 1.0.4 es-errors: 1.3.0 is-typed-array: 1.1.15 + optional: true typed-array-byte-length@1.0.3: dependencies: @@ -9890,6 +9591,7 @@ snapshots: gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 + optional: true typed-array-byte-offset@1.0.4: dependencies: @@ -9900,6 +9602,7 @@ snapshots: has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 + optional: true typed-array-length@1.0.8: dependencies: @@ -9909,31 +9612,19 @@ snapshots: is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + optional: true - typescript-eslint@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3): + typescript-eslint@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.61.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@5.9.3) - eslint: 10.4.1(jiti@2.7.0) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - typescript-eslint@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.0(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.4.1(jiti@2.7.0) + '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - typescript@5.9.3: {} - typescript@6.0.3: {} typo-js@1.3.2: {} @@ -9948,6 +9639,7 @@ snapshots: has-bigints: 1.1.0 has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + optional: true undici-types@7.24.6: {} @@ -10078,15 +9770,15 @@ snapshots: chart.js: 4.5.1 vue: 3.5.37(typescript@6.0.3) - vue-eslint-parser@10.4.0(eslint@10.4.1(jiti@2.7.0)): + vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0)): dependencies: debug: 4.4.3 - eslint: 10.4.1(jiti@2.7.0) + eslint: 10.5.0(jiti@2.7.0) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 esquery: 1.7.0 - semver: 7.8.1 + semver: 7.8.4 transitivePeerDependencies: - supports-color @@ -10139,6 +9831,7 @@ snapshots: is-number-object: 1.1.1 is-string: 1.1.1 is-symbol: 1.1.1 + optional: true which-builtin-type@1.2.1: dependencies: @@ -10155,6 +9848,7 @@ snapshots: which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.21 + optional: true which-collection@1.0.2: dependencies: @@ -10162,6 +9856,7 @@ snapshots: is-set: 2.0.3 is-weakmap: 2.0.2 is-weakset: 2.0.4 + optional: true which-typed-array@1.1.21: dependencies: @@ -10172,6 +9867,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-tostringtag: 1.0.2 + optional: true which@1.3.1: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c16cfb76cb..8f1c49389b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,10 +3,6 @@ dedupePeerDependents: false updateNotifier: false minimumReleaseAge: 0 -peerDependencyRules: - allowedVersions: - eslint-plugin-github>eslint: '>=9' - allowBuilds: '@scarf/scarf': false core-js: false diff --git a/tailwind.config.ts b/tailwind.config.ts index 199433d0cf..f597121743 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -90,7 +90,7 @@ export default { '8xl': '96px', '9xl': '128px', ...Object.fromEntries(Array.from({length: 100}, (_, i) => { - return [`${i}`, `${i === 0 ? '0' : `${i}px`}`]; + return [String(i), i === 0 ? '0' : `${i}px`]; })), }, extend: { diff --git a/tools/ci-tools.ts b/tools/ci-tools.ts index 4562643247..b872c35187 100644 --- a/tools/ci-tools.ts +++ b/tools/ci-tools.ts @@ -83,7 +83,7 @@ async function setPrLabels(): Promise { Accept: 'application/vnd.github+json', Authorization: `Bearer ${env.GITHUB_TOKEN}`, 'X-GitHub-Api-Version': '2022-11-28', - ...(body ? {'Content-Type': 'application/json'} : {}), + ...(Boolean(body) && {'Content-Type': 'application/json'}), }, body: body ? JSON.stringify(body) : undefined, }); diff --git a/tools/eslint-rules/unescaped-html-literal.ts b/tools/eslint-rules/unescaped-html-literal.ts index 3cbd82f7fe..87c40f0518 100644 --- a/tools/eslint-rules/unescaped-html-literal.ts +++ b/tools/eslint-rules/unescaped-html-literal.ts @@ -12,30 +12,28 @@ const rule: JSRuleDefinition = { }, }, - create(context) { - return { - Literal(node) { - if (typeof node.value !== 'string' || !htmlOpenTag.test(node.value)) return; + create: (context) => ({ + Literal(node) { + if (typeof node.value !== 'string' || !htmlOpenTag.test(node.value)) return; - context.report({ - node, - messageId: 'unescapedHtmlLiteral', - }); - }, - TemplateLiteral(node) { - const templateStart = node.quasis[0]?.value.raw; - if (!templateStart || !htmlOpenTag.test(templateStart)) return; + context.report({ + node, + messageId: 'unescapedHtmlLiteral', + }); + }, + TemplateLiteral(node) { + const templateStart = node.quasis[0]?.value.raw; + if (!templateStart || !htmlOpenTag.test(templateStart)) return; - const parent = node.parent; - if (parent?.type === 'TaggedTemplateExpression' && parent.tag.type === 'Identifier' && parent.tag.name === 'html') return; + const parent = node.parent; + if (parent?.type === 'TaggedTemplateExpression' && parent.tag.type === 'Identifier' && parent.tag.name === 'html') return; - context.report({ - node, - messageId: 'unescapedHtmlLiteral', - }); - }, - }; - }, + context.report({ + node, + messageId: 'unescapedHtmlLiteral', + }); + }, + }), }; export default rule; diff --git a/tools/generate-images.ts b/tools/generate-images.ts index 6e6ac15486..b123dcf4cd 100755 --- a/tools/generate-images.ts +++ b/tools/generate-images.ts @@ -7,7 +7,7 @@ import {argv, exit} from 'node:process'; async function generate(svg: string, path: string, {size, bg}: {size: number, bg?: boolean}) { const outputFile = new URL(path, import.meta.url); - if (String(outputFile).endsWith('.svg')) { + if (outputFile.href.endsWith('.svg')) { const {data} = optimize(svg, { plugins: [ 'preset-default', diff --git a/tools/generate-svg.ts b/tools/generate-svg.ts index c18eacc86f..004ed52042 100755 --- a/tools/generate-svg.ts +++ b/tools/generate-svg.ts @@ -92,7 +92,7 @@ async function processMaterialFileIcons() { } // Use VSCode's "Language ID" mapping from its extensions - for (const [_, langIdExtMap] of Object.entries(vscodeExtensions)) { + for (const langIdExtMap of Object.values(vscodeExtensions)) { for (const [langId, names] of Object.entries(langIdExtMap)) { for (const name of names) { const nameLower = name.toLowerCase(); diff --git a/types.d.ts b/types.d.ts index d6325f5cbd..5737cc9acb 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,21 +1,9 @@ -declare module 'eslint-plugin-no-use-extend-native' { - import type {Eslint} from 'eslint'; - const plugin: Eslint.Plugin; - export = plugin; -} - declare module 'eslint-plugin-array-func' { import type {Eslint} from 'eslint'; const plugin: Eslint.Plugin; export = plugin; } -declare module 'eslint-plugin-github' { - import type {Eslint} from 'eslint'; - const plugin: Eslint.Plugin; - export = plugin; -} - declare module '*.svg' { const value: string; export default value; diff --git a/vite.config.ts b/vite.config.ts index e077fed6ae..85d8626914 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -56,7 +56,7 @@ const commonRolldownOptions: Rolldown.RolldownOptions = { checks: { pluginTimings: false, }, - ...(env.CI ? {plugins: [failOnWarningsPlugin()]} : {}), + ...(env.CI && {plugins: [failOnWarningsPlugin()]}), }; function commonViteOpts({build, ...other}: InlineConfig): InlineConfig { diff --git a/web_src/js/components/ActionRunView.ts b/web_src/js/components/ActionRunView.ts index 582ae0431f..f6e3977086 100644 --- a/web_src/js/components/ActionRunView.ts +++ b/web_src/js/components/ActionRunView.ts @@ -47,9 +47,9 @@ export type LogLineCommand = { export function parseLogLineCommand(line: LogLine): LogLineCommand | null { // TODO: in the future it can be refactored to be a general parser that can parse arguments, drop the "prefix match" - for (const prefix of Object.keys(LogLinePrefixCommandMap)) { + for (const [prefix, commandName] of Object.entries(LogLinePrefixCommandMap)) { if (line.message.startsWith(prefix)) { - return {name: LogLinePrefixCommandMap[prefix], prefix}; + return {name: commandName, prefix}; } } // Handle ::cmd:: and ::cmd args:: format (runner may pass these through raw) diff --git a/web_src/js/components/ViewFileTreeStore.ts b/web_src/js/components/ViewFileTreeStore.ts index 4237f7e323..6e120d2509 100644 --- a/web_src/js/components/ViewFileTreeStore.ts +++ b/web_src/js/components/ViewFileTreeStore.ts @@ -57,9 +57,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri await store.loadViewContent(url); }, - buildTreePathWebUrl(treePath: string) { - return `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`; - }, + buildTreePathWebUrl: (treePath: string) => `${props.repoLink}/src/${props.currentRefNameSubURL}/${pathEscapeSegments(treePath)}`, }); return store; } diff --git a/web_src/js/components/WorkflowGraph.utils.ts b/web_src/js/components/WorkflowGraph.utils.ts index 71ef7e6fc9..3cacceb24d 100644 --- a/web_src/js/components/WorkflowGraph.utils.ts +++ b/web_src/js/components/WorkflowGraph.utils.ts @@ -166,7 +166,7 @@ function buildDirectNeedsMap(jobs: ActionsJob[]): Map { const reducedNeedsByJobId = new Map(); for (const [jobId, needs] of directNeedsByJobId) { reducedNeedsByJobId.set(jobId, needs.filter((need) => { - return !needs.some((other) => other !== need && canReach(need, other)); + return needs.every((other) => other === need || !canReach(need, other)); })); } return reducedNeedsByJobId; diff --git a/web_src/js/features/admin/config.test.ts b/web_src/js/features/admin/config.test.ts index b8468610ca..fc8b161496 100644 --- a/web_src/js/features/admin/config.test.ts +++ b/web_src/js/features/admin/config.test.ts @@ -30,9 +30,9 @@ test('ConfigFormValueMapper', () => { const formData = mapper.collectToFormData(); const result: Record = {}; const keys: string[] = [], values: string[] = []; - for (const [key, value] of formData.entries()) { + for (const [key, value] of formData) { if (key === 'key') keys.push(value as string); - if (key === 'value') values.push(value as string); + else if (key === 'value') values.push(value as string); } for (let i = 0; i < keys.length; i++) { result[keys[i]] = values[i]; diff --git a/web_src/js/features/admin/config.ts b/web_src/js/features/admin/config.ts index 96c7dcf84b..3abf28e4ea 100644 --- a/web_src/js/features/admin/config.ts +++ b/web_src/js/features/admin/config.ts @@ -102,9 +102,7 @@ export class ConfigFormValueMapper { } else if (el.matches('[type="datetime-local"]')) { if (valType !== 'timestamp') requireExplicitValueType(el); if (val) el.value = toDatetimeLocalValue(val); - } else if (el.matches('textarea')) { - el.value = String(val ?? el.value); - } else if (el.matches('input') && (el.getAttribute('type') ?? 'text') === 'text') { + } else if (el.matches('textarea') || (el.matches('input') && (el.getAttribute('type') ?? 'text') === 'text')) { el.value = String(val ?? el.value); } else { unsupportedElement(el); @@ -123,9 +121,7 @@ export class ConfigFormValueMapper { } else if (el.matches('[type="datetime-local"]')) { if (valType !== 'timestamp') requireExplicitValueType(el); val = Math.floor(new Date(el.value).getTime() / 1000) ?? 0; // NaN is fine to JSON.stringify, it becomes null. - } else if (el.matches('textarea')) { - val = el.value; - } else if (el.matches('input') && (el.getAttribute('type') ?? 'text') === 'text') { + } else if (el.matches('textarea') || (el.matches('input') && (el.getAttribute('type') ?? 'text') === 'text')) { val = el.value; } else { unsupportedElement(el); @@ -178,7 +174,7 @@ export class ConfigFormValueMapper { collectToFormData(): FormData { const namedElems: Array = []; - queryElems(this.form, '[name]', (el) => namedElems.push(el as GeneralFormFieldElement)); + queryElems(this.form, '[name]', (el) => { namedElems.push(el as GeneralFormFieldElement) }); // first, process the config options with sub values, for example: // merge "foo.bar.Enabled", "foo.bar.Message" to "foo.bar" diff --git a/web_src/js/features/admin/users.ts b/web_src/js/features/admin/users.ts index 16276773e1..fec4aa0d5f 100644 --- a/web_src/js/features/admin/users.ts +++ b/web_src/js/features/admin/users.ts @@ -5,14 +5,14 @@ export function initAdminUserListSearchForm(): void { const form = document.querySelector('#user-list-search-form'); if (!form) return; - for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) { + for (const button of form.querySelectorAll(`button[name=sort][value="${CSS.escape(searchForm.SortType)}"]`)) { button.classList.add('active'); } if (searchForm.StatusFilterMap) { for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) { if (!v) continue; - for (const input of form.querySelectorAll(`input[name="status_filter[${k}]"][value="${v}"]`)) { + for (const input of form.querySelectorAll(`input[name="status_filter[${CSS.escape(k)}]"][value="${CSS.escape(v)}"]`)) { input.checked = true; } } diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index cc26103aa3..a00ed4ee8b 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -87,10 +87,10 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) { const [attrTargetName, attrTargetProp] = attrTargetCombo.split('.'); // try to find target by: "#target" -> "[name=target]" -> ".target" -> " tag", and then try the modal itself const attrTarget = elModal.querySelector(`#${attrTargetName}`) || - elModal.querySelector(`[name=${attrTargetName}]`) || + elModal.querySelector(`[name=${CSS.escape(attrTargetName)}]`) || elModal.querySelector(`.${attrTargetName}`) || - elModal.querySelector(`${attrTargetName}`) || - (elModal.matches(`${attrTargetName}`) || elModal.matches(`#${attrTargetName}`) || elModal.matches(`.${attrTargetName}`) ? elModal : null); + elModal.querySelector(attrTargetName) || + (elModal.matches(attrTargetName) || elModal.matches(`#${attrTargetName}`) || elModal.matches(`.${attrTargetName}`) ? elModal : null); if (!attrTarget) { if (!window.config.runModeIsProd) throw new Error(`attr target "${attrTargetCombo}" not found for modal`); continue; diff --git a/web_src/js/features/common-fetch-action.ts b/web_src/js/features/common-fetch-action.ts index 3481d240f2..b58aa7a9a0 100644 --- a/web_src/js/features/common-fetch-action.ts +++ b/web_src/js/features/common-fetch-action.ts @@ -114,7 +114,7 @@ function buildFetchActionUrl(el: HTMLElement, opt: FetchActionOpts) { const u = new URL(url, window.location.href); if (name && !u.searchParams.has(name)) { u.searchParams.set(name, val); - url = u.toString(); + url = u.href; } } return url; diff --git a/web_src/js/features/common-form.ts b/web_src/js/features/common-form.ts index 67bcd55f52..793d47c8e2 100644 --- a/web_src/js/features/common-form.ts +++ b/web_src/js/features/common-form.ts @@ -16,17 +16,13 @@ export function initGlobalEnterQuickSubmit() { document.addEventListener('keydown', (e) => { if (e.isComposing) return; if (e.key !== 'Enter') return; + const el = e.target as HTMLElement; const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey); - if (hasCtrlOrMeta && (e.target as HTMLElement).matches('textarea')) { - if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) { - e.preventDefault(); - } - } else if ((e.target as HTMLElement).matches('input') && !(e.target as HTMLElement).closest('form')) { - // input in a normal form could handle Enter key by default, so we only handle the input outside a form - // eslint-disable-next-line unicorn/no-lonely-if - if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) { - e.preventDefault(); - } + const isCtrlEnterInTextarea = hasCtrlOrMeta && el.matches('textarea'); + // an input in a normal form could handle Enter key by default, so we only handle the input outside a form + const isEnterInBareInput = el.matches('input') && !el.closest('form'); + if ((isCtrlEnterInTextarea || isEnterInBareInput) && handleGlobalEnterQuickSubmit(el)) { + e.preventDefault(); } }); } diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts index 069113801f..eec27a2d22 100644 --- a/web_src/js/features/common-issue-list.ts +++ b/web_src/js/features/common-issue-list.ts @@ -35,7 +35,7 @@ export function initCommonIssueListQuickGoto() { const repoLink = elGotoButton.getAttribute('data-repo-link') || ''; elGotoButton.addEventListener('click', () => { - window.location.href = elGotoButton.getAttribute('data-issue-goto-link')!; + window.location.assign(elGotoButton.getAttribute('data-issue-goto-link')!); }); const onInput = async () => { diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index 63271c303b..2b9333263f 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -110,8 +110,8 @@ export async function initDropzone(dropzoneEl: HTMLElement) { }); dzInst.on('submit', () => { - for (const fileUuid of Object.keys(fileUuidDict)) { - fileUuidDict[fileUuid].submitted = true; + for (const value of Object.values(fileUuidDict)) { + value.submitted = true; } }); diff --git a/web_src/js/features/heatmap.ts b/web_src/js/features/heatmap.ts index 341e014bfc..4102ed6726 100644 --- a/web_src/js/features/heatmap.ts +++ b/web_src/js/features/heatmap.ts @@ -24,8 +24,8 @@ export async function initHeatmap() { heatmap[dateStr] = (heatmap[dateStr] || 0) + contributions; } - const values = Object.keys(heatmap).map((v) => { - return {date: new Date(v), count: heatmap[v]}; + const values = Object.entries(heatmap).map(([dateStr, count]) => { + return {date: new Date(dateStr), count}; }); const totalFormatted = totalContributions.toLocaleString(); diff --git a/web_src/js/features/imagediff.ts b/web_src/js/features/imagediff.ts index 6311478e4a..462a1a3cee 100644 --- a/web_src/js/features/imagediff.ts +++ b/web_src/js/features/imagediff.ts @@ -291,7 +291,7 @@ class ImageDiff { function updateOpacity() { if (ctx.imageAfter) { - (ctx.imageAfter.parentNode as HTMLElement).style.opacity = `${Number(rangeInput.value) / 100}`; + (ctx.imageAfter.parentNode as HTMLElement).style.opacity = String(Number(rangeInput.value) / 100); } } diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts index f316cd532d..1154ccc27c 100644 --- a/web_src/js/features/install.ts +++ b/web_src/js/features/install.ts @@ -95,7 +95,7 @@ function initPostInstall() { if (tid && resp.status === 200) { clearInterval(tid); tid = null; - window.location.href = targetUrl; + window.location.assign(targetUrl); } } catch {} }, 1000); diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index ba0ca5203c..da04e247c3 100644 --- a/web_src/js/features/notification.ts +++ b/web_src/js/features/notification.ts @@ -10,7 +10,7 @@ async function receiveUpdateCount(event: MessageEvent<{type: string, data: strin const data = JSON.parse(event.data.data); for (const count of document.querySelectorAll('.notification_count')) { count.classList.toggle('tw-hidden', data.Count === 0); - count.textContent = `${data.Count}`; + count.textContent = String(data.Count); } await updateNotificationTable(); } catch (error) { @@ -112,7 +112,7 @@ async function updateNotificationCount(): Promise { toggleElem('.notification_count', data.new !== 0); for (const el of document.querySelectorAll('.notification_count')) { - el.textContent = `${data.new}`; + el.textContent = String(data.new); } return data.new as number; diff --git a/web_src/js/features/repo-code.ts b/web_src/js/features/repo-code.ts index 64574cc4c0..972c8c27f8 100644 --- a/web_src/js/features/repo-code.ts +++ b/web_src/js/features/repo-code.ts @@ -31,9 +31,9 @@ function selectRange(range: string): Element | null { const updateViewGitBlameFragment = function (anchor: string) { if (!viewGitBlame) return; let href = viewGitBlame.getAttribute('href')!; - href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`; + href = href.replace(/#L\d+$|#L\d+-L\d+$/, ''); if (anchor.length !== 0) { - href = `${href}#${anchor}`; + href += `#${anchor}`; } viewGitBlame.setAttribute('href', href); }; diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 6103766202..b673ea285b 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -45,8 +45,8 @@ export function initCommitFileHistoryFollowRename() { registerGlobalInitFunc('initCommitHistoryFollowRename', (el: HTMLInputElement) => { el.addEventListener('change', () => { const url = new URL(window.location.toString()); - url.searchParams.set('follow-rename', `${el.checked}`); - window.location.assign(url.toString()); + url.searchParams.set('follow-rename', String(el.checked)); + window.location.assign(url.href); }); }); } diff --git a/web_src/js/features/repo-common.ts b/web_src/js/features/repo-common.ts index 50d0db3673..c3b26fd06a 100644 --- a/web_src/js/features/repo-common.ts +++ b/web_src/js/features/repo-common.ts @@ -23,7 +23,7 @@ async function onDownloadArchive(e: Event) { if (data.complete) break; await sleep(Math.min((tryCount + 1) * 750, 2000)); } - window.location.href = el.href; // the archive is ready, start real downloading + window.location.assign(el.href); // the archive is ready, start real downloading } catch (e) { console.error(e); showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500}); diff --git a/web_src/js/features/repo-diff.ts b/web_src/js/features/repo-diff.ts index f866df3526..89af812b67 100644 --- a/web_src/js/features/repo-diff.ts +++ b/web_src/js/features/repo-diff.ts @@ -129,7 +129,7 @@ function initRepoDiffConversationNav() { const navIndex = isPrevious ? previousIndex : nextIndex; const elNavConversation = elAllConversations[navIndex]; const anchor = elNavConversation.querySelector('.comment')!.id; - window.location.href = `#${anchor}`; + window.location.assign(`#${anchor}`); }); } @@ -245,7 +245,7 @@ async function onLocationHashChange() { const issueCommentPrefix = '#issuecomment-'; if (currentHash.startsWith(issueCommentPrefix)) { const commentId = currentHash.substring(issueCommentPrefix.length); - const expandButton = document.querySelector(`.code-expander-button[data-hidden-comment-ids*=",${commentId},"]`); + const expandButton = document.querySelector(`.code-expander-button[data-hidden-comment-ids*=",${CSS.escape(commentId)},"]`); if (expandButton) { // avoid infinite loop, do not re-click the button if already clicked const attrAutoLoadClicked = 'data-auto-load-clicked'; diff --git a/web_src/js/features/repo-graph.ts b/web_src/js/features/repo-graph.ts index 2177368b9f..742ecfc47b 100644 --- a/web_src/js/features/repo-graph.ts +++ b/web_src/js/features/repo-graph.ts @@ -42,7 +42,7 @@ export function initRepoGraphGit() { elGraphBody.classList.add('is-loading'); try { - const resp = await GET(ajaxUrl.toString()); + const resp = await GET(ajaxUrl.href); elGraphBody.innerHTML = await resp.text(); } finally { elGraphBody.classList.remove('is-loading'); @@ -51,7 +51,7 @@ export function initRepoGraphGit() { const dropdownSelected = params.getAll('branch'); if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') { - dropdownSelected.splice(0, 0, '...flow-hide-pr-refs'); + dropdownSelected.unshift('...flow-hide-pr-refs'); } const $dropdown = fomanticQuery('#flow-select-refs-dropdown'); diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts index 8bc7af6195..29bad895e6 100644 --- a/web_src/js/features/repo-home.ts +++ b/web_src/js/features/repo-home.ts @@ -96,10 +96,7 @@ export function initRepoTopicBar() { }; const query = stripTags(this.urlData.query.trim()); let found_query = false; - const current_topics = []; - for (const el of queryElemChildren(topicDropdown, 'a.ui.label.visible')) { - current_topics.push(el.getAttribute('data-value')); - } + const current_topics = Array.from(queryElemChildren(topicDropdown, 'a.ui.label.visible'), (el) => el.getAttribute('data-value')); if (res.topics) { let found = false; diff --git a/web_src/js/features/repo-issue-content.ts b/web_src/js/features/repo-issue-content.ts index eda5f4c8ce..ebd21c2c51 100644 --- a/web_src/js/features/repo-issue-content.ts +++ b/web_src/js/features/repo-issue-content.ts @@ -148,7 +148,7 @@ export async function initRepoIssueContentHistory() { if (resp.editedHistoryCountMap[0] && elIssueDescription) { showContentHistoryMenu(issueBaseUrl, elIssueDescription, '0'); } - for (const [commentId, _editedCount] of Object.entries(resp.editedHistoryCountMap)) { + for (const commentId of Object.keys(resp.editedHistoryCountMap)) { if (commentId === '0') continue; const elIssueComment = document.querySelector(`#issuecomment-${commentId}`); if (elIssueComment) showContentHistoryMenu(issueBaseUrl, elIssueComment, commentId); diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts index 046c1beee5..088e96fde2 100644 --- a/web_src/js/features/repo-issue-list.ts +++ b/web_src/js/features/repo-issue-list.ts @@ -58,10 +58,7 @@ function initRepoIssueListCheckboxes() { const url = el.getAttribute('data-url')!; let action = el.getAttribute('data-action')!; let elementId = el.getAttribute('data-element-id')!; - const issueIDList: string[] = []; - for (const el of document.querySelectorAll('.issue-checkbox:checked')) { - issueIDList.push(el.getAttribute('data-issue-id')!); - } + const issueIDList: string[] = Array.from(document.querySelectorAll('.issue-checkbox:checked'), (el) => (el.getAttribute('data-issue-id')!)); const issueIDs = issueIDList.join(','); if (!issueIDs) return; @@ -109,7 +106,7 @@ function initDropdownUserRemoteSearch(el: Element) { fullTextSearch: true, selectOnKeydown: false, action: (_text: string, value: string) => { - window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value)); + window.location.assign(actionJumpUrl.replace('{username}', encodeURIComponent(value))); }, }); @@ -192,7 +189,7 @@ function initPinRemoveButton() { // Delete the tooltip el._tippy.destroy(); // Remove the Card - el.closest(`div.issue-card[data-issue-id="${id}"]`)!.remove(); + el.closest(`div.issue-card[data-issue-id="${CSS.escape(String(id))}"]`)!.remove(); } }); } diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 07058c06d4..69ca3f4f6b 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -28,7 +28,7 @@ function initRepoIssueLabelFilter(elDropdown: HTMLElement) { const queryLabels = url.searchParams.get('labels') || ''; const selectedLabelIds = new Set(); for (const id of queryLabels ? queryLabels.split(',') : []) { - selectedLabelIds.add(`${Math.abs(parseInt(id))}`); // "labels" contains negative ids, which are excluded + selectedLabelIds.add(String(Math.abs(parseInt(id)))); // "labels" contains negative ids, which are excluded } const excludeLabel = (e: MouseEvent | KeyboardEvent, item: Element) => { @@ -130,9 +130,9 @@ export function initRepoIssueCommentDelete() { // on the Conversation page, there is no parent "tr", so no need to do anything for "add-code-comment" if (lineType) { if (lineType === 'same') { - document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`)!.classList.remove('tw-invisible'); + document.querySelector(`[data-path="${CSS.escape(String(path))}"] .add-code-comment[data-idx="${CSS.escape(String(idx))}"]`)!.classList.remove('tw-invisible'); } else { - document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`)!.classList.remove('tw-invisible'); + document.querySelector(`[data-path="${CSS.escape(String(path))}"] .add-code-comment[data-side="${CSS.escape(String(side))}"][data-idx="${CSS.escape(String(idx))}"]`)!.classList.remove('tw-invisible'); } } conversationHolder.remove(); diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts index 64a2e966c9..25846f67c6 100644 --- a/web_src/js/features/repo-projects.ts +++ b/web_src/js/features/repo-projects.ts @@ -120,11 +120,11 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void { } // update the newly saved column title and color in the project board (to avoid reload) - const elEditButton = writableProjectBoard.querySelector(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`)!; + const elEditButton = writableProjectBoard.querySelector(`.show-project-column-modal-edit[${CSS.escape(attrDataColumnId)}="${CSS.escape(columnId)}"]`)!; elEditButton.setAttribute(attrDataColumnTitle, elColumnTitle.value); elEditButton.setAttribute(attrDataColumnColor, elColumnColor.value); - const elBoardColumn = writableProjectBoard.querySelector(`.project-column[data-id="${columnId}"]`)!; + const elBoardColumn = writableProjectBoard.querySelector(`.project-column[data-id="${CSS.escape(columnId)}"]`)!; const elBoardColumnTitle = elBoardColumn.querySelector(`.project-column-title-text`)!; elBoardColumnTitle.textContent = elColumnTitle.value; if (elColumnColor.value) { diff --git a/web_src/js/features/repo-release.ts b/web_src/js/features/repo-release.ts index 702dcd3482..690911d9ed 100644 --- a/web_src/js/features/repo-release.ts +++ b/web_src/js/features/repo-release.ts @@ -12,7 +12,7 @@ export function initRepoReleaseNew() { registerGlobalEventFunc('click', 'onReleaseEditAttachmentDelete', (el) => { const uuid = el.getAttribute('data-uuid')!; const id = el.getAttribute('data-id')!; - document.querySelector(`input[name='attachment-del-${uuid}']`)!.value = 'true'; + document.querySelector(`input[name='attachment-del-${CSS.escape(uuid)}']`)!.value = 'true'; hideElem(`#attachment-${id}`); }); registerGlobalInitFunc('initReleaseEditForm', (elForm: HTMLFormElement) => { diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index f30f4a7f5f..9d0791cc30 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -79,7 +79,7 @@ async function loginPasskey() { } const reply = await res.json(); - window.location.href = reply?.redirect ?? `${appSubUrl}/`; + window.location.assign(reply?.redirect ?? `${appSubUrl}/`); } catch (err) { webAuthnError('general', errorMessage(err)); } @@ -151,7 +151,7 @@ async function verifyAssertion(assertedCredential: any) { // TODO: Credential ty } const reply = await res.json(); - window.location.href = reply?.redirect ?? `${appSubUrl}/`; + window.location.assign(reply?.redirect ?? `${appSubUrl}/`); } async function webauthnRegistered(newCredential: any) { // TODO: Credential type does not work @@ -188,7 +188,7 @@ function webAuthnError(errorType: ErrorType, message:string = '') { if (errorType === 'general') { elErrorMsg.textContent = message || 'unknown error'; } else { - const elTypedError = document.querySelector(`#webauthn-error [data-webauthn-error-msg=${errorType}]`); + const elTypedError = document.querySelector(`#webauthn-error [data-webauthn-error-msg=${CSS.escape(errorType)}]`); if (elTypedError) { elErrorMsg.textContent = `${elTypedError.textContent}${message ? ` ${message}` : ''}`; } else { diff --git a/web_src/js/markup/html2markdown.ts b/web_src/js/markup/html2markdown.ts index 9331f6b29a..4c8f51f00e 100644 --- a/web_src/js/markup/html2markdown.ts +++ b/web_src/js/markup/html2markdown.ts @@ -18,15 +18,9 @@ function prepareProcessors(ctx:ProcessorContext): Processors { const level = parseInt(el.tagName.slice(1)); el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; }, - STRONG(el: HTMLElement) { - return `**${el.textContent}**`; - }, - EM(el: HTMLElement) { - return `_${el.textContent}_`; - }, - DEL(el: HTMLElement) { - return `~~${el.textContent}~~`; - }, + STRONG: (el: HTMLElement) => `**${el.textContent}**`, + EM: (el: HTMLElement) => `_${el.textContent}_`, + DEL: (el: HTMLElement) => `~~${el.textContent}~~`, A(el: HTMLElement) { const text = el.textContent || 'link'; const href = el.getAttribute('href'); @@ -62,9 +56,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; return el; }, - INPUT(el: HTMLElement) { - return (el as HTMLInputElement).checked ? '[x] ' : '[ ] '; - }, + INPUT: (el: HTMLElement) => (el as HTMLInputElement).checked ? '[x] ' : '[ ] ', CODE(el: HTMLElement) { const text = el.textContent; if (el.parentNode && (el.parentNode as HTMLElement).tagName === 'PRE') { @@ -86,8 +78,8 @@ function prepareProcessors(ctx:ProcessorContext): Processors { function processElement(ctx :ProcessorContext, processors: Processors, el: HTMLElement): string | void { if (el.hasAttribute('data-markdown-generated-content')) return el.textContent; - if (el.tagName === 'A' && el.children.length === 1 && el.children[0].tagName === 'IMG') { - return processElement(ctx, processors, el.children[0] as HTMLElement); + if (el.tagName === 'A' && el.children.length === 1 && el.firstElementChild!.tagName === 'IMG') { + return processElement(ctx, processors, el.firstElementChild as HTMLElement); } const isListContainer = el.tagName === 'OL' || el.tagName === 'UL'; diff --git a/web_src/js/markup/render-iframe.test.ts b/web_src/js/markup/render-iframe.test.ts index bdbe523cdf..def2a2a8e2 100644 --- a/web_src/js/markup/render-iframe.test.ts +++ b/web_src/js/markup/render-iframe.test.ts @@ -29,7 +29,6 @@ describe('navigateToIframeLink', () => { test('unsafe links', () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined); - window.location.href = 'http://localhost:3000/'; // eslint-disable-next-line no-script-url navigateToIframeLink('javascript:void(0);', '_blank'); diff --git a/web_src/js/markup/render-iframe.ts b/web_src/js/markup/render-iframe.ts index 6a191ac5c6..31c5a734ae 100644 --- a/web_src/js/markup/render-iframe.ts +++ b/web_src/js/markup/render-iframe.ts @@ -4,7 +4,7 @@ import {isDarkTheme} from '../utils.ts'; function safeRenderIframeLink(link: any): string | null { try { - const url = new URL(`${link}`, window.location.href); + const url = new URL(link, window.location.href); if (url.protocol !== 'http:' && url.protocol !== 'https:') { console.error(`Unsupported link protocol: ${link}`); return null; diff --git a/web_src/js/modules/codeeditor/main.ts b/web_src/js/modules/codeeditor/main.ts index 8feea2b475..25eef4837c 100644 --- a/web_src/js/modules/codeeditor/main.ts +++ b/web_src/js/modules/codeeditor/main.ts @@ -203,9 +203,7 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn }, }), cm.language.foldGutter({ - markerDOM(open: boolean) { - return createElementFromHTML(svg(open ? 'octicon-chevron-down' : 'octicon-chevron-right', 13)); - }, + markerDOM: (open: boolean) => createElementFromHTML(svg(open ? 'octicon-chevron-down' : 'octicon-chevron-right', 13)), }), cm.view.highlightActiveLineGutter(), cm.view.highlightSpecialChars(), diff --git a/web_src/js/modules/errors.ts b/web_src/js/modules/errors.ts index 4b6504dc99..0163fd8b52 100644 --- a/web_src/js/modules/errors.ts +++ b/web_src/js/modules/errors.ts @@ -20,11 +20,11 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error', d } // compact the message to a data attribute to avoid too many duplicated messages const msgCompact = `${msgType}-${msg.trim()}`.replace(/[^-\w\u{80}-\u{10FFFF}]+/gu, ''); - let msgContainer = parentContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); + let msgContainer = parentContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${CSS.escape(msgCompact)}"]`); if (!msgContainer) { const el = document.createElement('div'); el.innerHTML = html`
`; - msgContainer = el.childNodes[0] as HTMLDivElement; + msgContainer = el.firstElementChild as HTMLDivElement; } // merge duplicated messages into "the message (count)" format @@ -51,8 +51,7 @@ export function isGiteaError(filename: string, stack: string): boolean { if (extensionRe.test(filename) || extensionRe.test(stack)) return false; const assetBaseUrl = new URL(`${window.config.assetUrlPrefix}/`, window.location.origin).href; if (filename && !filename.startsWith(assetBaseUrl) && !filename.startsWith(window.location.origin)) return false; - if (stack && !stack.includes(assetBaseUrl)) return false; - return true; + return !stack || stack.includes(assetBaseUrl); } export function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) { diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index a12b56d23d..93bc89728c 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -253,22 +253,22 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT dropdown.addEventListener('mousedown', () => { ignoreClickPreVisible += isMenuVisible() ? 1 : 0; ignoreClickPreEvents++; - }, true); + }, {capture: true}); dropdown.addEventListener('focus', () => { ignoreClickPreVisible += isMenuVisible() ? 1 : 0; ignoreClickPreEvents++; deferredRefreshAriaActiveItem(); - }, true); + }, {capture: true}); dropdown.addEventListener('blur', () => { ignoreClickPreVisible = ignoreClickPreEvents = 0; deferredRefreshAriaActiveItem(100); - }, true); + }, {capture: true}); dropdown.addEventListener('mouseup', () => { setTimeout(() => { ignoreClickPreVisible = ignoreClickPreEvents = 0; deferredRefreshAriaActiveItem(100); }, 0); - }, true); + }, {capture: true}); dropdown.addEventListener('click', (e: MouseEvent) => { if (isMenuVisible() && ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible @@ -277,7 +277,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT e.stopPropagation(); // if the dropdown menu has been opened by focus, do not trigger the next click event again } ignoreClickPreEvents = ignoreClickPreVisible = 0; - }, true); + }, {capture: true}); } // Although Fomantic Dropdown supports "hideDividers", it doesn't really work with our "scoped dividers" diff --git a/web_src/js/modules/fomantic/tab.ts b/web_src/js/modules/fomantic/tab.ts index a58dd80bba..7da95520bd 100644 --- a/web_src/js/modules/fomantic/tab.ts +++ b/web_src/js/modules/fomantic/tab.ts @@ -8,7 +8,7 @@ export function initTabSwitcher(tabItemContainer: Element) { for (const elItem of tabItems) { const tabName = elItem.getAttribute('data-tab')!; elItem.addEventListener('click', () => { - const elPanel = document.querySelector(`.ui.tab[data-tab="${tabName}"]`)!; + const elPanel = document.querySelector(`.ui.tab[data-tab="${CSS.escape(tabName)}"]`)!; queryElemSiblings(elPanel, '.ui.tab', (el) => el.classList.remove('active')); queryElemSiblings(elItem, '.item[data-tab]', (el) => el.classList.remove('active')); elItem.classList.add('active'); diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index c2ca9ab51b..a7df1c0485 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -89,7 +89,7 @@ function attachTooltip(target: Element, content: Content | null = null): Instanc allowHTML: target.getAttribute('data-tooltip-render') === 'html', placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start', followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false, - ...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}), + ...((target.getAttribute('data-tooltip-interactive') === 'true') && {interactive: true, aria: {content: 'describedby', expanded: false}}), }; if (!target._tippy) { diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts index ca72ab48ce..a6cfef6848 100644 --- a/web_src/js/modules/toast.ts +++ b/web_src/js/modules/toast.ts @@ -45,7 +45,7 @@ type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast}; /** See https://github.com/apvarun/toastify-js#api for options */ function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast | null { const parent = document.querySelector('.ui.dimmer.active') ?? document.body; - const duplicateKey = preventDuplicates ? (preventDuplicates === true ? `${level}-${message}` : preventDuplicates) : ''; + const duplicateKey = preventDuplicates ? (typeof preventDuplicates === 'string' ? preventDuplicates : `${level}-${message}`) : ''; // prevent showing duplicate toasts with the same level and message, and give visual feedback for end users if (preventDuplicates) { diff --git a/web_src/js/modules/worker.ts b/web_src/js/modules/worker.ts index 64c32fbe81..eb951d94e9 100644 --- a/web_src/js/modules/worker.ts +++ b/web_src/js/modules/worker.ts @@ -50,7 +50,7 @@ export class UserEventsSharedWorker { // * in this case, the logout fetch call already completes and has sent the "logout" message to the worker // * there can be a data-race between the fetch call's redirection and the "logout" message from the worker // * the fetch call's logout redirection should always win over the worker message, because it might have a custom location - setTimeout(() => { window.location.href = `${appSubUrl}/` }, 1000); + setTimeout(() => { window.location.assign(`${appSubUrl}/`) }, 1000); } else if (event.data.type === 'close') { this.sharedWorker.port.postMessage({type: 'close'}); this.sharedWorker.port.close(); diff --git a/web_src/js/render/plugins/inplace-pdf-viewer.ts b/web_src/js/render/plugins/inplace-pdf-viewer.ts index 7447f38ec4..7eb193bda7 100644 --- a/web_src/js/render/plugins/inplace-pdf-viewer.ts +++ b/web_src/js/render/plugins/inplace-pdf-viewer.ts @@ -4,9 +4,7 @@ export function newInplacePluginPdfViewer(): InplaceRenderPlugin { return { name: 'pdf-viewer', - canHandle(filename: string, _mimeType: string): boolean { - return filename.toLowerCase().endsWith('.pdf'); - }, + canHandle: (filename: string, _mimeType: string): boolean => filename.toLowerCase().endsWith('.pdf'), async render(container: HTMLElement, fileUrl: string): Promise { const PDFObject = await import('pdfobject'); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index b18f33f33d..03ab783142 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -9,8 +9,8 @@ type ElementsCallback = (el: T) => Promisable; type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable; function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]): ArrayLikeIterable { - if (typeof el === 'string' || el instanceof String) { - el = document.querySelectorAll(el as string); + if (typeof el === 'string') { + el = document.querySelectorAll(el); } if (el instanceof Node) { func(el, ...args); diff --git a/web_src/js/utils/match.ts b/web_src/js/utils/match.ts index 41333d9fa4..ac8ff16c56 100644 --- a/web_src/js/utils/match.ts +++ b/web_src/js/utils/match.ts @@ -8,7 +8,7 @@ import type {Issue, Mention} from '../types.ts'; const maxMatches = 6; function sortAndReduce(map: Map): T[] { - const sortedMap = new Map(Array.from(map.entries()).sort((a, b) => a[1] - b[1])); + const sortedMap = new Map(Array.from(map).sort((a, b) => a[1] - b[1])); return Array.from(sortedMap.keys()).slice(0, maxMatches); } diff --git a/web_src/js/utils/testhelper.ts b/web_src/js/utils/testhelper.ts index 8da77d8134..56ec639c33 100644 --- a/web_src/js/utils/testhelper.ts +++ b/web_src/js/utils/testhelper.ts @@ -10,11 +10,11 @@ export function dedent(str: string) { const match = str.match(/^[ \t]*(?=\S)/gm); if (!match) return str; - let minIndent = Number.POSITIVE_INFINITY; + let minIndent = Infinity; for (const indent of match) { minIndent = Math.min(minIndent, indent.length); } - if (minIndent === 0 || minIndent === Number.POSITIVE_INFINITY) { + if (minIndent === 0 || minIndent === Infinity) { return str; } diff --git a/web_src/js/utils/time.ts b/web_src/js/utils/time.ts index cc005a1304..07b23f3b1b 100644 --- a/web_src/js/utils/time.ts +++ b/web_src/js/utils/time.ts @@ -37,7 +37,7 @@ export function firstStartDateAfterDate(inputDate: Date): number { } const dayOfWeek = inputDate.getUTCDay(); const daysUntilSunday = 7 - dayOfWeek; - const resultDate = new Date(inputDate.getTime()); + const resultDate = new Date(inputDate); resultDate.setUTCDate(resultDate.getUTCDate() + daysUntilSunday); return resultDate.valueOf(); } diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index b20d11e243..51409a3324 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -21,7 +21,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { this.popup.style.display = ''; this.button!.setAttribute('aria-expanded', 'true'); setTimeout(() => this.popup.focus(), 0); - document.addEventListener('click', this.onClickOutside, true); + document.addEventListener('click', this.onClickOutside, {capture: true}); } hidePopup() { @@ -125,7 +125,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { const itemRight = item.offsetLeft + item.offsetWidth; if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space const onlyLastItem = idx === menuItems.length - 1 && this.overflowItems.length === 0; - const lastItemFit = onlyLastItem && menuRight - itemRight > 0; + const lastItemFit = onlyLastItem && menuRight > itemRight; const moveToPopup = !onlyLastItem || !lastItemFit; if (moveToPopup) this.overflowItems.push(item); } -- 2.54.0 From ceec230fc0e8e4271cf9fa089d8919d99e4dfce3 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 21 Jun 2026 19:57:22 +0800 Subject: [PATCH 006/169] fix: walk git log context error handling (#38182) Fix #38177 Make WalkGitLog can handle EOF and context errors correctly, and don't export these private functions & methods & structs. --- modules/git/commit_info_nogogit.go | 2 +- modules/git/commit_info_nogogit_test.go | 54 ++++++++ modules/git/last_commit_cache_nogogit.go | 2 +- ...e_status.go => log_name_status_nogogit.go} | 126 +++++++----------- 4 files changed, 104 insertions(+), 80 deletions(-) create mode 100644 modules/git/commit_info_nogogit_test.go rename modules/git/{log_name_status.go => log_name_status_nogogit.go} (74%) diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index cf53f9fdf4..a7125d23ba 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -106,7 +106,7 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac // GetLastCommitForPaths returns last commit information func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) { // We read backwards from the commit to obtain all of the commits - revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...) + revs, err := walkGitLog(ctx, commit.repo, commit, treePath, paths...) if err != nil { return nil, err } diff --git a/modules/git/commit_info_nogogit_test.go b/modules/git/commit_info_nogogit_test.go new file mode 100644 index 0000000000..268130d3a7 --- /dev/null +++ b/modules/git/commit_info_nogogit_test.go @@ -0,0 +1,54 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build !gogit + +package git + +import ( + "context" + "path/filepath" + "testing" + + "gitea.dev/modules/test" + "gitea.dev/modules/util" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEntries_GetCommitsInfo_ContextErr(t *testing.T) { + repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "repo1_bare")) + require.NoError(t, err) + defer repo.Close() + + commit, err := repo.GetCommit("feaf4ba6bc635fec442f46ddd4512416ec43c2c2") + require.NoError(t, err) + entries, err := commit.Tree.ListEntries() + require.NoError(t, err) + + countCommitInfosCommit := func(infos []CommitInfo) (nilCommits, nonNilCommits int) { + for _, info := range infos { + nilCommits += util.Iif(info.Commit == nil, 1, 0) + nonNilCommits += util.Iif(info.Commit != nil, 1, 0) + } + return nilCommits, nonNilCommits + } + + ctx, cancel := context.WithCancel(t.Context()) + defer test.MockVariableValue(&walkGitLogDebugBeforeNext)() + + walkGitLogDebugBeforeNext = cancel + commitInfos, _, err := entries.GetCommitsInfo(ctx, "/any/repo-link", commit, "") + assert.NoError(t, err) + nilCommits, nonNilCommits := countCommitInfosCommit(commitInfos) + assert.Equal(t, 0, nonNilCommits) // no commit info due to canceled (or deadline-exceeded) context + assert.Equal(t, 3, nilCommits) + + walkGitLogDebugBeforeNext = nil + commitInfos, _, err = entries.GetCommitsInfo(t.Context(), "/any/repo-link", commit, "") + assert.NoError(t, err) + nilCommits, nonNilCommits = countCommitInfosCommit(commitInfos) + assert.Equal(t, 3, nonNilCommits) + assert.Equal(t, 0, nilCommits) +} diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go index 155cb3cb7c..e034eaa168 100644 --- a/modules/git/last_commit_cache_nogogit.go +++ b/modules/git/last_commit_cache_nogogit.go @@ -32,7 +32,7 @@ func (c *Commit) recursiveCache(ctx context.Context, tree *Tree, treePath string entryPaths[i] = entry.Name() } - _, err = WalkGitLog(ctx, c.repo, c, treePath, entryPaths...) + _, err = walkGitLog(ctx, c.repo, c, treePath, entryPaths...) if err != nil { return err } diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status_nogogit.go similarity index 74% rename from modules/git/log_name_status.go rename to modules/git/log_name_status_nogogit.go index a7347a6d8a..b11645833d 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status_nogogit.go @@ -1,6 +1,8 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +//go:build !gogit + package git import ( @@ -18,10 +20,8 @@ import ( "gitea.dev/modules/log" ) -// LogNameStatusRepo opens git log --raw in the provided repo and returns a stdin pipe, a stdout reader and cancel function -func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, paths ...string) (*bufio.Reader, func()) { - // Lets also create a context so that we can absolutely ensure that the command should die when we're done - +// logNameStatusRepo opens git log --raw in the provided repo and returns a parser +func logNameStatusRepo(ctx context.Context, repository, head, treepath string, paths ...string) *logNameStatusRepoParser { cmd := gitcmd.NewCommand() cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head) @@ -54,77 +54,62 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p ctx, ctxCancel := context.WithCancel(ctx) go func() { err := cmd.WithDir(repository).RunWithStderr(ctx) - if err != nil && !errors.Is(err, context.Canceled) { + if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { log.Error("Unable to run git command %v: %v", cmd.LogString(), err) } }() bufReader := bufio.NewReaderSize(stdoutReader, 32*1024) - - return bufReader, func() { - ctxCancel() - stdoutReaderClose() + return &logNameStatusRepoParser{ + treepath: treepath, + paths: paths, + rd: bufReader, + close: func() { + ctxCancel() + stdoutReaderClose() + }, } } -// LogNameStatusRepoParser parses a git log raw output from LogRawRepo -type LogNameStatusRepoParser struct { +// logNameStatusRepoParser parses a git log raw output from LogRawRepo +type logNameStatusRepoParser struct { treepath string paths []string next []byte buffull bool rd *bufio.Reader - cancel func() + close func() } -// NewLogNameStatusRepoParser returns a new parser for a git log raw output -func NewLogNameStatusRepoParser(ctx context.Context, repository, head, treepath string, paths ...string) *LogNameStatusRepoParser { - rd, cancel := LogNameStatusRepo(ctx, repository, head, treepath, paths...) - return &LogNameStatusRepoParser{ - treepath: treepath, - paths: paths, - rd: rd, - cancel: cancel, - } -} - -// LogNameStatusCommitData represents a commit artefact from git log raw -type LogNameStatusCommitData struct { +// logNameStatusCommitData represents a commit artifact from git log raw +type logNameStatusCommitData struct { CommitID string ParentIDs []string Paths []bool } -// Next returns the next LogStatusCommitData -func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int, changed []bool, maxpathlen int) (*LogNameStatusCommitData, error) { +// walkNext returns the next LogStatusCommitData +func (g *logNameStatusRepoParser) walkNext(treepath string, paths2ids map[string]int, changed []bool, maxpathlen int) (*logNameStatusCommitData, error) { var err error if len(g.next) == 0 { g.buffull = false g.next, err = g.rd.ReadSlice('\x00') - if err != nil { - switch err { - case bufio.ErrBufferFull: - g.buffull = true - case io.EOF: - return nil, nil //nolint:nilnil // return nil to signal EOF - default: - return nil, err - } + switch { + case errors.Is(err, bufio.ErrBufferFull): + g.buffull = true + case err != nil: + return nil, err } } - ret := LogNameStatusCommitData{} + ret := logNameStatusCommitData{} if bytes.Equal(g.next, []byte("commit\000")) { g.next, err = g.rd.ReadSlice('\x00') - if err != nil { - switch err { - case bufio.ErrBufferFull: - g.buffull = true - case io.EOF: - return nil, nil //nolint:nilnil // return nil to signal EOF - default: - return nil, err - } + switch { + case errors.Is(err, bufio.ErrBufferFull): + g.buffull = true + case err != nil: + return nil, err } } @@ -273,13 +258,10 @@ diffloop: } } -// Close closes the parser -func (g *LogNameStatusRepoParser) Close() { - g.cancel() -} +var walkGitLogDebugBeforeNext func() // is used to simulate various edge git process cases -// WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files -func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) { +// walkGitLog walks the git log --name-status for the head commit in the provided treepath and files +func walkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) { headRef := head.ID.String() tree, err := head.SubTree(treepath) @@ -322,11 +304,9 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st } } - g := NewLogNameStatusRepoParser(ctx, repo.Path, head.ID.String(), treepath, paths...) - // don't use defer g.Close() here as g may change its value - instead wrap in a func - defer func() { - g.Close() - }() + g := logNameStatusRepo(ctx, repo.Path, head.ID.String(), treepath, paths...) + // don't use defer g.cancel() here as g may change its value - instead wrap in a func + defer func() { g.close() }() results := make([]string, len(paths)) remaining := len(paths) @@ -340,25 +320,16 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st heaploop: for { - select { - case <-ctx.Done(): - if ctx.Err() == context.DeadlineExceeded { - break heaploop - } - g.Close() - return nil, ctx.Err() - default: + if walkGitLogDebugBeforeNext != nil { + walkGitLogDebugBeforeNext() } - current, err := g.Next(treepath, path2idx, changed, maxpathlen) - if err != nil { - if errors.Is(err, context.DeadlineExceeded) { - break heaploop - } - g.Close() - return nil, err - } - if current == nil { - break heaploop + current, err := g.walkNext(treepath, path2idx, changed, maxpathlen) + if ctx.Err() != nil { + break heaploop // context is either canceled or deadline exceeded - break the loop and return what we have so far + } else if errors.Is(err, io.EOF) { + break heaploop // reached to the end of log output + } else if err != nil { + return nil, err // other unknown errors } parentRemaining.Remove(current.CommitID) for i, found := range current.Paths { @@ -395,14 +366,14 @@ heaploop: if remaining <= nextRestart { commitSinceNextRestart++ if 4*commitSinceNextRestart > 3*commitSinceLastEmptyParent { - g.Close() remainingPaths := make([]string, 0, len(paths)) for i, pth := range paths { if results[i] == "" { remainingPaths = append(remainingPaths, pth) } } - g = NewLogNameStatusRepoParser(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...) + g.close() + g = logNameStatusRepo(ctx, repo.Path, lastEmptyParent, treepath, remainingPaths...) parentRemaining = make(container.Set[string]) nextRestart = (remaining * 3) / 4 continue heaploop @@ -410,7 +381,6 @@ heaploop: } parentRemaining.AddMultiple(current.ParentIDs...) } - g.Close() resultsMap := map[string]string{} for i, pth := range paths { -- 2.54.0 From 180af33f865c9fa9f78bf9a9fd1fc9024ac19624 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 21 Jun 2026 16:34:07 +0200 Subject: [PATCH 007/169] perf: Various performance regression fixes (#38078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes five N+1 / O(n) query patterns found across common user paths. Each uses a bulk query that already existed elsewhere in the codebase. | Location | Problem | Introduced in | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- | | `IssueList.LoadIsRead` | `.In("issue_id")` missing its arg — xorm generates `WHERE 0=1`, so `IsRead` is **never** set; every issue always appears unread | #29515 | | `ParseCommitsWithStatus` | `GetLatestCommitStatus` called once per commit (O(n) queries on commit list / PR commits tab) | #33605 | | `getReleaseInfos` (release list) | `GetLatestCommitStatus` called once per release for CI badges | #29149 | | User milestone dashboard | O(n×m) nested loop matching milestones to repos | #26300 | | `findCodeComments` (PR diff) | `LoadResolveDoer` + `LoadReactions` called per inline comment — up to ~150 queries on a PR with 50 comments | #20821 | --------- Co-authored-by: Lauris B --- models/issues/comment_code.go | 16 ++++---- models/issues/comment_list.go | 68 ++++++++++++++++++++++++++++++++ models/issues/issue_list.go | 5 ++- models/issues/issue_list_test.go | 17 ++++++++ routers/web/repo/release.go | 19 ++++++--- routers/web/user/home.go | 12 +++--- services/git/commit.go | 30 ++++++++------ 7 files changed, 135 insertions(+), 32 deletions(-) diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 7195736f92..f6cebe37b0 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -79,6 +79,14 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu return nil, err } + if err := comments.loadResolveDoers(ctx); err != nil { + return nil, err + } + + if err := comments.loadReactions(ctx, issue.Repo); err != nil { + return nil, err + } + // Find all reviews by ReviewID reviews := make(map[int64]*Review) ids := make([]int64, 0, len(comments)) @@ -107,14 +115,6 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu comments[n] = comment n++ - if err := comment.LoadResolveDoer(ctx); err != nil { - return nil, err - } - - if err := comment.LoadReactions(ctx, issue.Repo); err != nil { - return nil, err - } - var err error rctx := renderhelper.NewRenderContextRepoComment(ctx, issue.Repo, renderhelper.RepoCommentOptions{ FootnoteContextID: strconv.FormatInt(comment.ID, 10), diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 522c066309..610bd16f5a 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -11,6 +11,7 @@ import ( user_model "gitea.dev/models/user" "gitea.dev/modules/container" "gitea.dev/modules/log" + "gitea.dev/modules/setting" ) // CommentList defines a list of comments @@ -444,6 +445,73 @@ func (comments CommentList) loadReviews(ctx context.Context) error { return nil } +// loadResolveDoers bulk-loads the resolve doer for all code comments that have one. +func (comments CommentList) loadResolveDoers(ctx context.Context) error { + resolveDoerIDs := container.FilterSlice(comments, func(c *Comment) (int64, bool) { + return c.ResolveDoerID, c.ResolveDoerID != 0 && c.Type == CommentTypeCode + }) + if len(resolveDoerIDs) == 0 { + return nil + } + + userMaps, err := user_model.GetUsersMapByIDs(ctx, resolveDoerIDs) + if err != nil { + return err + } + + for _, comment := range comments { + if comment.ResolveDoerID == 0 || comment.Type != CommentTypeCode { + continue + } + if u, ok := userMaps[comment.ResolveDoerID]; ok { + comment.ResolveDoer = u + } else { + comment.ResolveDoer = user_model.NewGhostUser() + } + } + return nil +} + +// loadReactions bulk-loads reactions for all comments in the list. +func (comments CommentList) loadReactions(ctx context.Context, repo *repo_model.Repository) error { + if len(comments) == 0 { + return nil + } + + commentIDs := container.FilterSlice(comments, func(c *Comment) (int64, bool) { + return c.ID, c.Reactions == nil + }) + if len(commentIDs) == 0 { + return nil + } + + var allReactions ReactionList + if err := db.GetEngine(ctx). + Where("`comment_id` > 0"). + In("comment_id", commentIDs). + In("`type`", setting.UI.Reactions). + Asc("issue_id", "comment_id", "created_unix", "id"). + Find(&allReactions); err != nil { + return err + } + + if _, err := allReactions.LoadUsers(ctx, repo); err != nil { + return err + } + + reactByComment := make(map[int64]ReactionList, len(commentIDs)) + for _, r := range allReactions { + reactByComment[r.CommentID] = append(reactByComment[r.CommentID], r) + } + + for _, comment := range comments { + if comment.Reactions == nil { + comment.Reactions = reactByComment[comment.ID] + } + } + return nil +} + // LoadAttributes loads attributes of the comments, except for attachments and // comments func (comments CommentList) LoadAttributes(ctx context.Context) (err error) { diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index 45e25c2da8..250344dd66 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -591,9 +591,12 @@ func (issues IssueList) GetApprovalCounts(ctx context.Context) (map[int64][]*Rev func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error { issueIDs := issues.getIssueIDs() + if len(issueIDs) == 0 { + return nil + } issueUsers := make([]*IssueUser, 0, len(issueIDs)) if err := db.GetEngine(ctx).Where("uid =?", userID). - In("issue_id"). + In("issue_id", issueIDs). Find(&issueUsers); err != nil { return err } diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go index 1c9f2a1e8e..bba864b4c5 100644 --- a/models/issues/issue_list_test.go +++ b/models/issues/issue_list_test.go @@ -11,6 +11,7 @@ import ( "gitea.dev/modules/setting" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestIssueList_LoadRepositories(t *testing.T) { @@ -30,6 +31,22 @@ func TestIssueList_LoadRepositories(t *testing.T) { } } +func TestIssueList_LoadIsRead(t *testing.T) { + // Regression: In("issue_id") was missing the issueIDs argument, causing + // xorm to generate "0=1" and never mark any issue as read. + require.NoError(t, unittest.PrepareTestDatabase()) + + issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + + // Fixture: uid=1 has is_read=true on issue 1 only. + issueList := issues_model.IssueList{issue1, issue2} + require.NoError(t, issueList.LoadIsRead(t.Context(), 1)) + + assert.True(t, issue1.IsRead, "issue 1 should be marked read for user 1") + assert.False(t, issue2.IsRead, "issue 2 should not be marked read for user 1") +} + func TestIssueList_LoadAttributes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) setting.Service.EnableTimetracking = true diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 5e267cc113..128fcd5369 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -101,6 +101,19 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) canReadActions := ctx.Repo.Permission.CanRead(unit.TypeActions) + // Bulk-load commit statuses for all releases in one query. + var commitStatusMap map[string][]*git_model.CommitStatus + if canReadActions && len(releases) > 0 { + shas := make([]string, 0, len(releases)) + for _, r := range releases { + shas = append(shas, r.Sha1) + } + commitStatusMap, err = git_model.GetLatestCommitStatusForRepoCommitIDs(ctx, ctx.Repo.Repository.ID, shas) + if err != nil { + return nil, err + } + } + releaseInfos := make([]*ReleaseInfo, 0, len(releases)) for _, r := range releases { if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { @@ -130,11 +143,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) } if canReadActions { - statuses, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptionsAll) - if err != nil { - return nil, err - } - + statuses := commitStatusMap[r.Sha1] info.CommitStatus = git_model.CalcCommitStatus(statuses) info.CommitStatuses = statuses } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 6951be45ef..75b51151f3 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -242,13 +242,13 @@ func Milestones(ctx *context.Context) { } sort.Sort(showRepos) + repoByID := make(map[int64]*repo_model.Repository, len(showRepos)) + for _, repo := range showRepos { + repoByID[repo.ID] = repo + } + for i := 0; i < len(milestones); { - for _, repo := range showRepos { - if milestones[i].RepoID == repo.ID { - milestones[i].Repo = repo - break - } - } + milestones[i].Repo = repoByID[milestones[i].RepoID] if milestones[i].Repo == nil { log.Warn("Cannot find milestone %d 's repository %d", milestones[i].ID, milestones[i].RepoID) milestones = append(milestones[:i], milestones[i+1:]...) diff --git a/services/git/commit.go b/services/git/commit.go index bc0516b5d6..662e050713 100644 --- a/services/git/commit.go +++ b/services/git/commit.go @@ -7,7 +7,6 @@ import ( "context" asymkey_model "gitea.dev/models/asymkey" - "gitea.dev/models/db" git_model "gitea.dev/models/git" "gitea.dev/models/gituser" repo_model "gitea.dev/models/repo" @@ -72,20 +71,27 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo // ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.SignCommit, repo *repo_model.Repository) ([]*git_model.SignCommitWithStatuses, error) { - newCommits := make([]*git_model.SignCommitWithStatuses, 0, len(oldCommits)) + if len(oldCommits) == 0 { + return nil, nil + } + commitIDs := make([]string, 0, len(oldCommits)) for _, c := range oldCommits { - commit := &git_model.SignCommitWithStatuses{ - SignCommit: c, - } - statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commit.GitCommit.ID.String(), db.ListOptionsAll) - if err != nil { - return nil, err - } + commitIDs = append(commitIDs, c.GitCommit.ID.String()) + } + statusMap, err := git_model.GetLatestCommitStatusForRepoCommitIDs(ctx, repo.ID, commitIDs) + if err != nil { + return nil, err + } - commit.Statuses = statuses - commit.Status = git_model.CalcCommitStatus(statuses) - newCommits = append(newCommits, commit) + newCommits := make([]*git_model.SignCommitWithStatuses, 0, len(oldCommits)) + for _, c := range oldCommits { + statuses := statusMap[c.GitCommit.ID.String()] + newCommits = append(newCommits, &git_model.SignCommitWithStatuses{ + SignCommit: c, + Statuses: statuses, + Status: git_model.CalcCommitStatus(statuses), + }) } return newCommits, nil } -- 2.54.0 From e5891263f89912bfaf5908971332a8ee27dfcb28 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 21 Jun 2026 10:13:18 -0700 Subject: [PATCH 008/169] docs: update changelog for 1.26.3 & 1.26.4 (#38178) Front port changelog for 1.26.3 & 1.26.4 --------- Co-authored-by: bircni --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 691e6eaed1..c811e6bc04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,56 @@ This changelog goes through the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.com). +## [1.26.4](https://github.com/go-gitea/gitea/releases/tag/1.26.4) - 2026-06-21 + +* SECURITY + * fix(auth): do not auto-reactivate disabled users on OAuth2 callback (#38009) (#38183) + +* BUGFIXES + * fix: walk git log context error handling (#38182) (#38185) + +## [1.26.3](https://github.com/go-gitea/gitea/releases/tag/1.26.3) - 2026-06-18 + +* BREAKING + * fix(actions)!: require merged PR to bypass fork PR approval gate (#38010) (#38041) + +* SECURITY + * fix(hostmatcher): patch incorrect private list (#38170) (#38173) + * fix: Various security fixes (#38103) (#38151) + * fix: Various sec fixes (#38108) (#38147) + * fix: allow git clone of private repos with anonymous code access (#38074) (#38146) + * fix(auth): ignore stale OIDC external login links to organizations (#37875) (#38141) + * fix(hostmatcher): block reserved IP ranges from external/private filters (#38039) (#38059) + * fix(lfs): require Code-unit access for cross-repo LFS object reuse (#38006) (#38050) + * fix(lfs): reject unknown SSH LFS sub-verbs to prevent auth bypass (#38008) (#38015) + * fix: bound CODEOWNERS regex match time (#38011) (#38025) + * fix: bound debian ParseControlFile to a single control stanza (#38044) (#38055) + * fix(deps): update module golang.org/x/net to v0.55.0 [security] (#37813) (#37829) + +* API + * feat(api): add Link header in ListForks (#38052) (#38063) + +* BUGFIXES + * fix: Fix the panic when ssh remote lfs endpoint parsing failure (#38026) (#38158) + * fix(api): nil pointer panic when filtering tracked times by a non-existent user (#38112) (#38115) + * fix: keep literal "false" value displayed in workflow_dispatch choice dropdowns (#38080) (#38096) + * fix: parse HEAD ref (#38119) + * fix: git cmd (#38084) (#38087) + * fix(releases): generate notes for initial tag (#37697) (#37986) + * fix(actions): return 404 when job log blob is missing (#38003) (#38004) + * fix(actions): exclude `workflow_call` from workflow trigger detection (#37894) (#37899) + * fix(actions): keep action run title clickable when commit subject is a URL (#37867) (#37898) + * fix(actions): reject workflow_dispatch for workflows without that trigger (#37660) (#37895) + * fix(actions): ack re-sent `UpdateLog` finalize idempotently (#37885) (#37892) + * fix: http content file render (#37850) (#37856) + * fix(issues): clear stale ReviewTypeRequest when submitting pending review (#37809) (#37815) + * fix: Fix issue target branch selection for non-collaborators (#36916) (#38164) + +* BUILD + * fix(deps): update `@playwright/test` to 1.60.0 (#38144) + * ci: add `tools/ci-tools.ts` for the PR labeler workflow (#37831) + * fix(build): swagger css import (#37801) (#37803) + ## [1.26.2](https://github.com/go-gitea/gitea/releases/tag/1.26.2) - 2026-05-20 * SECURITY -- 2.54.0 From 685b62c60fc595e3612a85f0895471876db56292 Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 22 Jun 2026 05:50:02 +0200 Subject: [PATCH 009/169] fix(api): don't expose private org membership via public_members (#38145) --- routers/api/v1/org/member.go | 11 +++++++++++ tests/integration/api_org_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 11b46a05c1..ce7f227af6 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -119,6 +119,12 @@ func ListPublicMembers(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + // don't disclose membership of organizations the doer cannot see + if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { + ctx.APIErrorNotFound() + return + } + listMembers(ctx, false) } @@ -201,6 +207,11 @@ func IsPublicMember(ctx *context.APIContext) { if ctx.Written() { return } + // don't disclose membership of organizations the doer cannot see + if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) { + ctx.APIErrorNotFound() + return + } is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID) if err != nil { ctx.APIErrorInternal(err) diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 21123ebeb3..1702eb116e 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -264,6 +264,32 @@ func testAPIOrgGeneral(t *testing.T) { }) } +func TestAPIOrgPrivateMembersNotLeaked(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // privated_org (org 23) has private visibility and a single member, user5 + const orgName = "privated_org" + const memberName = "user5" + + // member publicizes their own membership inside the private org + memberSession := loginUser(t, memberName) + memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + + // an outsider must not be able to learn about the membership of a private org + outsiderSession := loginUser(t, "user2") + outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + + // the member can still see the public membership of their own org + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) +} + func testAPIDeleteOrgRepos(t *testing.T) { org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) orgRepos, err := repo_model.GetOrgRepositories(t.Context(), org3.ID) -- 2.54.0 From 2c2611eab9f2e9ab877150f4cf6fcef0d0991d2e Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sun, 21 Jun 2026 21:08:48 -0700 Subject: [PATCH 010/169] chore(deps): update dependency djlint to v1.39.2 (#38192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [djlint](https://redirect.github.com/djlint/djLint) | `==1.39.0` → `==1.39.2` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/djlint/1.39.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/djlint/1.39.0/1.39.2?slim=true) | --- ### Release Notes
djlint/djLint (djlint) ### [`v1.39.2`](https://redirect.github.com/djlint/djLint/blob/HEAD/CHANGELOG.md#1392---2026-06-11) [Compare Source](https://redirect.github.com/djlint/djLint/compare/v1.39.0...v1.39.2) v1.39.1 was not published due to mypyc compilation error. ##### Packaging - Fix mypyc compilation.
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). Co-authored-by: bircni --- pyproject.toml | 2 +- uv.lock | 116 ++++++++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 631d0a5052..9c5ec3f2d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires-python = ">=3.10" [dependency-groups] dev = [ - "djlint==1.39.0", + "djlint==1.39.2", "yamllint==1.38.0", "zizmor==1.25.2", ] diff --git a/uv.lock b/uv.lock index 67df36399f..302ba1c873 100644 --- a/uv.lock +++ b/uv.lock @@ -39,7 +39,7 @@ wheels = [ [[package]] name = "djlint" -version = "1.39.0" +version = "1.39.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -54,63 +54,63 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a7/5ba1032d01ceba641b92b1c76c758a0a06959585c6d36608371526809a08/djlint-1.39.0.tar.gz", hash = "sha256:75e7e1a0c592121751c48360104b3c402f4d6406ea862ba76f8867b3eb51ba97", size = 55174, upload-time = "2026-06-05T19:22:37.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/d8/119f35832801129dc6a4bdf82c163bb80d0095891eea9fc3543eaf8c9792/djlint-1.39.2.tar.gz", hash = "sha256:dd27ef03bd26d1e0a167da26ec2a6fcb511dddd032b2553abf71a5c31eaa8b34", size = 56083, upload-time = "2026-06-11T14:03:01.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/df/a81550590ab37a3d99880b5dc781616f1944bd3b3e353bf041ee1d5fee7d/djlint-1.39.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc806fbc58d69941b5280f31f6126e0545f8408e99264d3a0dce1de767c8dd79", size = 520521, upload-time = "2026-06-05T19:23:12.411Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c3/c40a148a23d19fbeb9d1028e159fe4c16981c538d73beb9c4f28f0dd0e94/djlint-1.39.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2621cfe40bd3cd439a028370b80ff8934fa6414f8cca1f27221957d1775b8fe", size = 496510, upload-time = "2026-06-05T19:22:44.882Z" }, - { url = "https://files.pythonhosted.org/packages/e8/35/4a9bf5e689146c98b4644dc85dc66b050d465cde5353ada06ad6fb3fd362/djlint-1.39.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4be236e58cac714bae3931970b4ae73425c200951803a8afd635f2a11a9463ac", size = 524712, upload-time = "2026-06-05T19:23:26.775Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fd/0d227699fb927136bece3df66638e0554f6eacb2bf9d3aea398402d97fe8/djlint-1.39.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f63e8cf847fcf748cadc7feb9acc265b89578fd043350c8776eb7b0825a0e5e9", size = 542959, upload-time = "2026-06-05T19:23:06.972Z" }, - { url = "https://files.pythonhosted.org/packages/5a/fa/9badca5dc6d2bbae9cf81db959d887ea41f7333e9c8e87b0374175e85be4/djlint-1.39.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab3427fa50149d0f618de08a437d24931ffce3e0505615556ed78e502edcb4d9", size = 529748, upload-time = "2026-06-05T19:22:56.673Z" }, - { url = "https://files.pythonhosted.org/packages/19/26/7f68a5b835451ababcf373830ba4068d5083ff2b06d9d423f3cf73fbf26f/djlint-1.39.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5800abf3d506708094497d55fdfbaae7b522b551c273ca22abeb5d682c28875e", size = 552687, upload-time = "2026-06-05T19:22:32.665Z" }, - { url = "https://files.pythonhosted.org/packages/f4/6c/3c3252d8e6904db7ff4458545b48ae98af14a364cbfee1a7738c73386dd6/djlint-1.39.0-cp310-cp310-win32.whl", hash = "sha256:e69532cd9c970871ec475509fe41d8b5a453a51cd4a82b1e4f175f3144fa1a63", size = 405814, upload-time = "2026-06-05T19:22:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/eb/60/11fb512bd868161834f19fd088eb99e1c9a3cd024e0ba1fc3f28aa0b51d9/djlint-1.39.0-cp310-cp310-win_amd64.whl", hash = "sha256:b1ae7b0c3413adde6619dddf0ac58be55436489af482c45743caa9862282117b", size = 451264, upload-time = "2026-06-05T19:22:59.176Z" }, - { url = "https://files.pythonhosted.org/packages/ec/be/54d79236a7c29a373302ac5f0f3d92089003594dc02a40f58fc553e869fa/djlint-1.39.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a15b5164b75544045c28a9950d29fb3b4992fd02217dae2a0607085547bb900", size = 388868, upload-time = "2026-06-05T19:23:25.724Z" }, - { url = "https://files.pythonhosted.org/packages/47/f6/3044a6be9d4ac207b39f001be7e0f6a695d007cfb4b4e45d761712cb23f9/djlint-1.39.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b9e02481c6725c7ec01ed4603bec6c8e8e8ffb9cd60280c10dba98d8edced43", size = 509864, upload-time = "2026-06-05T19:23:27.987Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/30e67cfd1232d07ef3e6057e6017bfec6f08825aa08adc8cab5d3070cbe1/djlint-1.39.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c78334b22cf39d1f24c6e404eedab3f634f5eed70c8ce437762d47efdfa3a33e", size = 484321, upload-time = "2026-06-05T19:23:00.494Z" }, - { url = "https://files.pythonhosted.org/packages/bd/73/e1d5b0f3446123395c54a0084e85c4ceebc9d69b096c07ea11781df68db8/djlint-1.39.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08c67a870591e233604c5402163b44d4e872e801aa6a09fc082b4db33beb7049", size = 512772, upload-time = "2026-06-05T19:23:16.285Z" }, - { url = "https://files.pythonhosted.org/packages/89/8b/bb8a67ed58229511f0a137368c9e938e645d126946fbcaf98df8e1728e84/djlint-1.39.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcaf7ef93dfde168b6d61ea9caf35f294232e5b72d2b6d401d04f1d753758435", size = 533865, upload-time = "2026-06-05T19:23:31.624Z" }, - { url = "https://files.pythonhosted.org/packages/fc/87/694fc944f94703ff8530dac13461dfff07354307d413265036b4cb6c2ecc/djlint-1.39.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bf9a88b60a521a7a795102ce16086745f6d8e1783d2517e87e13efb5cd3057d0", size = 520819, upload-time = "2026-06-05T19:23:03.786Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/5fcec4ed089d9d1d3a2967511cda5758a21153c346c7a645ac635e085d09/djlint-1.39.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:65d4008fcba8a3fb1550a37bee2271e13fd55f37e569122de6507e6c3a77bc10", size = 543638, upload-time = "2026-06-05T19:22:34.962Z" }, - { url = "https://files.pythonhosted.org/packages/a9/bb/9cb11cb40314573006b52a48870b51041c65fb3e33fe5e30b08dffe1bf6a/djlint-1.39.0-cp311-cp311-win32.whl", hash = "sha256:759a54d9fa426cb74b5ac02e55c3e6c22c8ce63401a2846cba302dad2888190a", size = 404853, upload-time = "2026-06-05T19:23:08.067Z" }, - { url = "https://files.pythonhosted.org/packages/8b/36/f79c30f9b83186a68e9977065fcbdac075547497fe253708ec73a517899a/djlint-1.39.0-cp311-cp311-win_amd64.whl", hash = "sha256:09fbaf395d88b8b372c284c25464d9bbc0f41fbb98444f3fc227773806c90fa1", size = 451186, upload-time = "2026-06-05T19:23:17.364Z" }, - { url = "https://files.pythonhosted.org/packages/41/1f/f05d094b2d2c192b2f32c12918a4dc0362723716a60254fd8cd3de95ef5c/djlint-1.39.0-cp311-cp311-win_arm64.whl", hash = "sha256:db91ccdbb475150038b0d272df6e8e1d8d4acc658990fa3484de4d5e46532f32", size = 387598, upload-time = "2026-06-05T19:23:30.422Z" }, - { url = "https://files.pythonhosted.org/packages/56/ef/dac918a5a78fe90f141f05d648db86e2033e39af8210b8e6d34a6c4c2c2a/djlint-1.39.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7e28a346d52ecccd580c577045c03711321c0ca6a0d224267a00f186695dbc1d", size = 520028, upload-time = "2026-06-05T19:22:42.688Z" }, - { url = "https://files.pythonhosted.org/packages/d9/dd/b1b2c34e43ac3fbf172cf0bed692a813284e1eecd335bdea8634318a6304/djlint-1.39.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df5417ba6511a1655b50f1393ea6c1c8015bc20a5cbb27297b57ae78fe2d17ac", size = 491722, upload-time = "2026-06-05T19:23:14.683Z" }, - { url = "https://files.pythonhosted.org/packages/06/14/f010c3eb471f2c96a226af2eeaab634f032e1323e677fdd63cdcd981f173/djlint-1.39.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3f45b96a84998e02049b3d555599da3c3e3254354c3401b52505a79aefbf59", size = 515560, upload-time = "2026-06-05T19:22:49.674Z" }, - { url = "https://files.pythonhosted.org/packages/fb/8b/6cd7a60a494d156da84a07a92a77206f4ece42d24ed025b544b7d22eb98e/djlint-1.39.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa0acde91ce23f733e2b133b4db627a9dbd13f9cccceb0d55c6bcaa6556befc8", size = 539935, upload-time = "2026-06-05T19:23:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/ce/74/0b1353cbe9992d29cef42535b89726b92243c38e48683ee92bdde6dd65d2/djlint-1.39.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2f23eeb266efb075b738a45ab908594bbdaaee7ce20ab4dbc61c17501f70db16", size = 522470, upload-time = "2026-06-05T19:23:18.423Z" }, - { url = "https://files.pythonhosted.org/packages/8b/17/27995ca81db8af36b8469941e232adeab010e0a08c75b9622e03f755f5b5/djlint-1.39.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:adc646935fa1e3f6fec01a7eae471a1b41dddc6f2847a4402d3dc9d7483d5337", size = 548865, upload-time = "2026-06-05T19:22:45.92Z" }, - { url = "https://files.pythonhosted.org/packages/cf/60/c1ed2d49a54b1e28fcba790be28bb3a1fa7555cd9ea288c56e522118eb6b/djlint-1.39.0-cp312-cp312-win32.whl", hash = "sha256:5841c0cf72ded43e08bd97c9743116332c1b13a36d98cb7cc353c44ab86cf3b9", size = 407021, upload-time = "2026-06-05T19:23:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/03/4b/3327b925dbb5244a06c76fec06e5abea9f53b9d78d254a4e1264124afc5a/djlint-1.39.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f0db716dd94681e7dedf695563689249f7c470873f3f74b2765b7743783435e", size = 453876, upload-time = "2026-06-05T19:22:38.037Z" }, - { url = "https://files.pythonhosted.org/packages/da/dc/b690d59b6457fd2aab2cbcb66d93b9e95f0c3d04de81d5ef9a4b5cc9c545/djlint-1.39.0-cp312-cp312-win_arm64.whl", hash = "sha256:412f6319d9888548068af681c05722c9acfbe760d5b29d44832e1a40eb116be2", size = 388980, upload-time = "2026-06-05T19:22:39.228Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d0/6055cebb538718e46b3874d3a1c0c768aaf744a1354f342b1932985c882b/djlint-1.39.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fb2948211eb369bd28175f2007cc924bff7e2403ec1f42f22f6d4381c32bad31", size = 517087, upload-time = "2026-06-05T19:22:40.617Z" }, - { url = "https://files.pythonhosted.org/packages/39/be/726afcd62b9ce6382d2c10a9122a45daf4a47b6e2af4a7536c82b8b5f4fc/djlint-1.39.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1476f077af638ba21813cc17d8e7d31b1d5473e707d98c659e6ac2bdf5210e6", size = 489869, upload-time = "2026-06-05T19:22:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a0/f26dc11c62111f6d80550e9188b2d207691f0664ed3b7dbd62ed5d418e32/djlint-1.39.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19dbef7852fabe445ce4ea2b05da888df0513e1798c4ae7cd8f0c68cf0bc8cbb", size = 513551, upload-time = "2026-06-05T19:23:13.49Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/2ffe28c44d27aa006314c1b352a0b6039ab05dd4b7b3dbac494315b912ab/djlint-1.39.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c8c7bba68633f6a4a211dd35ded9337ec52a7a2991afc816f928f741296c1b3", size = 537832, upload-time = "2026-06-05T19:22:30.67Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/2cb7966a7a93b4758a380500c9a18fa22688b071dba5b52106107b48de4e/djlint-1.39.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5564bc51531332ba67bc8d952825ac2a42a7ec1618413a4da15bf957257c0d6", size = 520497, upload-time = "2026-06-05T19:23:19.497Z" }, - { url = "https://files.pythonhosted.org/packages/81/d0/b32648761b1529b030897b931998a6dabe6a15473c4724e1080c2ca737ae/djlint-1.39.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b836e79f690d83aa429cfa3240045e086f9e0764afbc88654004f455e2a9835a", size = 547304, upload-time = "2026-06-05T19:23:21.742Z" }, - { url = "https://files.pythonhosted.org/packages/26/6d/c0e7c61fdeee741ee7eec85a14dd40c8d2e1ee9efeb96a8a7302a8daef47/djlint-1.39.0-cp313-cp313-win32.whl", hash = "sha256:f18c148fc6cfb32dd8a0af7c80067f02d3faa83f5aea16a7c7fd5111d303ee69", size = 406746, upload-time = "2026-06-05T19:22:57.969Z" }, - { url = "https://files.pythonhosted.org/packages/74/c5/7ea676211bbb85665b2f82f2cc64925a4f54d866d57887ab943e97016fcf/djlint-1.39.0-cp313-cp313-win_amd64.whl", hash = "sha256:7c38a8e90f8a73adf08b6852ee34bf3c734873f2ff1df58e56206308272cb275", size = 453441, upload-time = "2026-06-05T19:22:41.662Z" }, - { url = "https://files.pythonhosted.org/packages/04/49/3056c368937e98d6cb7d1ac662e64e93bc9b5ddf5a2afcd01839c0095a51/djlint-1.39.0-cp313-cp313-win_arm64.whl", hash = "sha256:e95095623cf5d6e84161c9a08e81f29ea5f7f1c804107ccf7cd2fe27a750a3bc", size = 388639, upload-time = "2026-06-05T19:22:53.201Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c2/76fa9ffa5b88784a2704b64f08d902bc8071a99bdd79a983f56b3e2dfcdf/djlint-1.39.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a092b0beb93d9a6fe5e1e28934e4f933c483ce791aae9aec47e3f07a29511a61", size = 515957, upload-time = "2026-06-05T19:23:09.12Z" }, - { url = "https://files.pythonhosted.org/packages/d5/44/638b92e40ad5b473df6728c3c6c7ebd9d50823d4cf8dd5bdf22073bd1d57/djlint-1.39.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7ca3cd2c1ca610ad6e6357abba51e8153dc19f1d34764bcf453084199a4732a2", size = 488676, upload-time = "2026-06-05T19:22:43.787Z" }, - { url = "https://files.pythonhosted.org/packages/27/b6/50e91d06554b74dc558a6af6349643c0165ff6dcc5142908ae2db012acca/djlint-1.39.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0011c2b78fa26752e3373129965dcbe80253af7fd2807e394fdfd4ea6281d99", size = 517217, upload-time = "2026-06-05T19:22:48.533Z" }, - { url = "https://files.pythonhosted.org/packages/77/2d/f9f900ae26b44b3b79090667148eeb016464cfe70d0211e2afe0fda9ab4c/djlint-1.39.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:683ec039c2864670f1806fc96e4650f3f7e310222acb5d602608aeb24ca352e9", size = 537472, upload-time = "2026-06-05T19:22:51.868Z" }, - { url = "https://files.pythonhosted.org/packages/f2/ad/28ef34f629e728042341c397261fc2593a2eec489e44a7863cf646edc628/djlint-1.39.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:326a5ec019b084eb2d837f39d0bea6727806867e9d1e26d3f4bf0cd6bc67bf8f", size = 523546, upload-time = "2026-06-05T19:23:29.143Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6a/7ce68fdf319d9abda560fe3509d60abefe25ef118ae21d03399b1dfc84e7/djlint-1.39.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e655ac4e4346b3f5a61b53a9351104d33e4a7376f1c22acf4fadf1183f90128a", size = 546627, upload-time = "2026-06-05T19:22:31.67Z" }, - { url = "https://files.pythonhosted.org/packages/04/89/3e5bfaeb7b39a078a9a8d4fc7331e60f12f0e5c1251bc6c622be8c592ad4/djlint-1.39.0-cp314-cp314-win32.whl", hash = "sha256:0b5e30ab98c4de74698211ce6a60a502307d176015bf98269f74a39d862fc694", size = 412745, upload-time = "2026-06-05T19:22:35.955Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/b891316176513c233507dbf2f82747552e401079e3f917c46fbf84c5ef05/djlint-1.39.0-cp314-cp314-win_amd64.whl", hash = "sha256:9d4927b1bf65445e3c8dda8d1b96ab3019dbce1eaa88850760df78962bf2724e", size = 462295, upload-time = "2026-06-05T19:23:05.893Z" }, - { url = "https://files.pythonhosted.org/packages/06/44/ba3bf57ee70e969407e96d7accfb13d00c776674dbce95f8b07e1c7f731f/djlint-1.39.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b6a684f5cd8fc71ad55cd3c1acffa0cd4108bc63ad1524f9ca1d76b1b354e47", size = 396557, upload-time = "2026-06-05T19:22:54.276Z" }, - { url = "https://files.pythonhosted.org/packages/26/c0/bdb3eb96bd8e5d65546fe63063b787e302b981ec2f1436b1a0027404c311/djlint-1.39.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:afa4ba49d6b67f3c0145d78448c292e75d5822e76c189ef681399ead8492c599", size = 561022, upload-time = "2026-06-05T19:23:23.09Z" }, - { url = "https://files.pythonhosted.org/packages/96/98/e35b87ebc8f2a6985aed5ea7b85145d9e6e5d5b67fc3b612396a84604791/djlint-1.39.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1fee96af514bd1cb6b62d1107bb177d4d2f49361e5e9cd14f56f9650cdc2b5ad", size = 534450, upload-time = "2026-06-05T19:22:33.683Z" }, - { url = "https://files.pythonhosted.org/packages/87/f4/3ff2615cc2826c91ec3c7c26e8abedb35b3a546a068bc70ef385b2079c17/djlint-1.39.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ef06848e1ed5d987bb1aaf950ffe3a87b14e5937d9d42dbb1d0469ebe7a74dc", size = 552149, upload-time = "2026-06-05T19:22:27.861Z" }, - { url = "https://files.pythonhosted.org/packages/c3/fc/6fea3ea0075d06d1d5444a7ad72bf51c612795339e95d4b281599c61b9ee/djlint-1.39.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffcbca30ad41bc054c7c7ed5341ea651b034a60d4eff0aa2ab0bb8cb40f2b9b0", size = 570693, upload-time = "2026-06-05T19:22:55.293Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/af8a4012652a33208b3e0ca04c23446711fa5ecf8936809c04c6213c47b8/djlint-1.39.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8aace5a239e5f317b030a5c05d22d55edac5142366ffa1a15e5e5c8675044e44", size = 557296, upload-time = "2026-06-05T19:23:24.545Z" }, - { url = "https://files.pythonhosted.org/packages/6e/13/bf86a4f5d140ab6052a3aca8742cb446ec851946c7dcb625eb18a2564893/djlint-1.39.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9912c361968a3c881fd3eaff5a5dc56a0a409a7904355d998d430ff294550744", size = 579052, upload-time = "2026-06-05T19:23:10.177Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/5d2850606e321f8d6e56fe74fcb283c12493d179279bb52f347d0338aa6e/djlint-1.39.0-cp314-cp314t-win32.whl", hash = "sha256:12d3175f48317ec692da693a15ce7b939b3114f16b8d644bb037784bcef0bd52", size = 457432, upload-time = "2026-06-05T19:23:04.728Z" }, - { url = "https://files.pythonhosted.org/packages/f4/9f/6dc179c101d30c1aa4269e0cada79667c043d15392e515fb7e4e36e8a8df/djlint-1.39.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a3077dc9a4b3bb2724cd0231f008d309fe4ef4048af06b7edd1adba723356248", size = 513546, upload-time = "2026-06-05T19:23:11.375Z" }, - { url = "https://files.pythonhosted.org/packages/d9/0d/e3acb7da4ce3df5d699412b9442b885286df7e45647c205d65e593d02711/djlint-1.39.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f7228e01d5ceaf74fb5270d7bdfbd30dffe65e88216a70824765bca6acb2a4fb", size = 412286, upload-time = "2026-06-05T19:22:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/bd/45/50bddcbcee9566c213f14db5b154ade285c4842b88cdcdcc8d536d515147/djlint-1.39.0-py3-none-any.whl", hash = "sha256:3ef41f7bbf7761978e86e24ebdaf58704b17d847e9d0b5d9cb9f761ce976cff0", size = 60750, upload-time = "2026-06-05T19:23:02.846Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/ede7c8e1f0a49fc5baea9c358617f11ce5fcb34f53594fcec447fd440356/djlint-1.39.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d21abcd252e3f28f13f5090f2c214f49e91a53b71c0e949460fb8e3065634c6", size = 539969, upload-time = "2026-06-11T14:02:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/52/87/8d81b169062626d8b1782404816d22bfdc2454e9aabf61be4be79acd9ea6/djlint-1.39.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f3d1a797c348115ee3fbea39751223721247501f74728cdb6be6776f2e663cd5", size = 514397, upload-time = "2026-06-11T14:02:27.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/0f72462cad08d40803a5132a8a95b42f7bfa692b806d52beaf4206ccdb13/djlint-1.39.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69830fb1d0e0f5ccd09d248bb9bc4adf6f9937ea76037e68cc3dda7ec33c4701", size = 544179, upload-time = "2026-06-11T14:02:04.932Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/f433649196eb123192cbb619c3cc5b58b09fd629f5442d589c16b2adaca3/djlint-1.39.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17979198e7084b5e24bc22b89be89d12977b37304d6a24e6d382f6699d96ecc2", size = 564445, upload-time = "2026-06-11T14:02:07.572Z" }, + { url = "https://files.pythonhosted.org/packages/01/b6/0f105e9d8128574edfd2db201caf89ab0a3496c706a4b6f8a8417ffc4210/djlint-1.39.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ea0863fd53bd21e75ea0d04711898855bd579d6bd9783dae797deea82b428500", size = 550594, upload-time = "2026-06-11T14:03:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d2/728cd56ae7525f3ff2dc93aa996d01edce164c7b6709e54cdd7c3a4a6c61/djlint-1.39.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:820ce7467d6200a7435155bbd0a080316d7c16a5c4730712c2e3b8c905657430", size = 574855, upload-time = "2026-06-11T14:01:52.72Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/3f03d55710398cfc9dc4ee38537b8d709d75c218a1312e77dce11919e3b1/djlint-1.39.2-cp310-cp310-win32.whl", hash = "sha256:3343ae644cf3e16aaf4b3027833255c93bde9798521d5f8481a7099adce2dba5", size = 421933, upload-time = "2026-06-11T14:02:06.242Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c7/982a098e6eecfe530206d4c40356e71f6b69c7610d38341369f54eb25565/djlint-1.39.2-cp310-cp310-win_amd64.whl", hash = "sha256:0877d6a6db2f88dc8ae16752605f8852d8503e5939f4dc48ba6520f6ba2dfade", size = 470431, upload-time = "2026-06-11T14:02:51.036Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/a4e953506bbdf9fb344aa9f0cd1eaff56cce41bbc6916ef4005b1748536c/djlint-1.39.2-cp310-cp310-win_arm64.whl", hash = "sha256:9ab8822909d084148506450b49a9abfd13e62423cb7b8d89cd9153ada53ac1e0", size = 405286, upload-time = "2026-06-11T14:02:28.513Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/4bac0dfe37cab284b00fb4981d3c24e7338c4750ab72a1a1cabee3641097/djlint-1.39.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a30c4b46914ec15e8260b1f935f2a9d786111852fb069e3fb9c9c2f46fcae74", size = 530075, upload-time = "2026-06-11T14:01:54.174Z" }, + { url = "https://files.pythonhosted.org/packages/96/be/f4ead398868229985a0dd38e513b5c2c37d565136ff2615ee842ac3ef80e/djlint-1.39.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7483e8b46eb6c92f2275024452d762b2bff09278238d0bbc6ad181f969b9a4dc", size = 505501, upload-time = "2026-06-11T14:01:57.088Z" }, + { url = "https://files.pythonhosted.org/packages/d7/00/c791f9a111cd2aa904549558621f7e00ce19e7b309c11f836ae1e91a673f/djlint-1.39.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81314434617ac888d1f53f4cf8ffb718bad79f454bc3a6af2874d347e0ab8a8f", size = 534141, upload-time = "2026-06-11T14:02:52.482Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/8dc966b6b1993f64dbd162106d158459e52cc5aa18edb5a29fffe646e5eb/djlint-1.39.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:473803d2a6bff1bc61c0bfac3f3c36ebe58348342a3c234275969c57eeba8814", size = 552778, upload-time = "2026-06-11T14:02:47.051Z" }, + { url = "https://files.pythonhosted.org/packages/fc/38/64cbb0467cd38d129a2f245a73be84b297133e92f2f049a33f7f68623321/djlint-1.39.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33aa6da970c7ed3e62573e00d1aaf7f3f234361b571e55d0a916d300a69bffc6", size = 541041, upload-time = "2026-06-11T14:02:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/23/97/735cfd6dc20056dcbed1b4f8709974493098a9bd54ffae26d3cfe376a8e7/djlint-1.39.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c261e83fca81ed7dfc992f5bae9013e33cb5a6df5a71f96476962fd13c4e734c", size = 564589, upload-time = "2026-06-11T14:02:14.117Z" }, + { url = "https://files.pythonhosted.org/packages/be/e0/6c023418f8d70beace93debb69efcbca41475c083d381d08e0022e403867/djlint-1.39.2-cp311-cp311-win32.whl", hash = "sha256:73e5277c5c553935448d209643a7adeb9fe185550ec9007470c132ccaa5ff339", size = 421581, upload-time = "2026-06-11T14:02:30.021Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/3d973b5b86cbd3058116015c0f858fee439a25fc76a4b05646328155ee25/djlint-1.39.2-cp311-cp311-win_amd64.whl", hash = "sha256:117af0b57f71c8531c3fbb95162c20e74ace0d957f65ac93539ff42e84b778c8", size = 470411, upload-time = "2026-06-11T14:02:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/df/11/40cdefcae1fc49e77c285bcee31b83710875fbb994c4c789155341851a89/djlint-1.39.2-cp311-cp311-win_arm64.whl", hash = "sha256:af01dd3555f65dcb3d735a7254ead88ddb50e6dad76a12af22c85ff3ea66a062", size = 403952, upload-time = "2026-06-11T14:02:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/8d0d8134e59f2e94144ffed957bf8de1e57b3be2545605172306a0dcf8ff/djlint-1.39.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4f26ec23a39c1ef45f2357e95395aa566010d513282ed34a06f0f54690888b0", size = 540630, upload-time = "2026-06-11T14:02:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d8/862798ab0bac48974dae8f54ad3a0024f8797c3c59c06cf92f8a474c1c17/djlint-1.39.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f907116317fa429d9e29aac529001fdeec48a862131154e5792ebcd9d5415012", size = 511270, upload-time = "2026-06-11T14:02:16.559Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c5/ee8cacf7953843ff0605ed205ab2d0289411a118be3453c8686977c763b3/djlint-1.39.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eacde1123ace11f43029e565850861e73f4d7f44aaeb07c2af1ce40dc242d84", size = 537483, upload-time = "2026-06-11T14:02:24.475Z" }, + { url = "https://files.pythonhosted.org/packages/ac/9b/15d9632db5b187594acb50fb199b26bb0b512e66535ced8345894c0e3d01/djlint-1.39.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525b0cf09ff13b84a17c704fb7e34144d94fd49fa0a29e23e70394487b3f382b", size = 560049, upload-time = "2026-06-11T14:01:58.369Z" }, + { url = "https://files.pythonhosted.org/packages/0f/eb/b3d07b4e1df39194c4342d52c7224ed84267458eb67989c6405079251968/djlint-1.39.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a54dac1106ce607b134faa59acca01bfb3f0b36d6ccc562558d90fb0ddce55f2", size = 543416, upload-time = "2026-06-11T14:02:41.653Z" }, + { url = "https://files.pythonhosted.org/packages/58/d2/d8697eb492efa1c83f546efe151c559cf392a1fba145b3fab9ab870a9606/djlint-1.39.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6767bc0b0a04a022308d94be004e1889801cdac1f858563f6c397094580d78e1", size = 570385, upload-time = "2026-06-11T14:02:08.898Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fc/ba2531e2469ed4d0bb5cfda11e46d48a709999a4de61d3d0d5bad4f6eaef/djlint-1.39.2-cp312-cp312-win32.whl", hash = "sha256:059dbea42758394d6fae582e3b158e098ebdc9cb4eb68634bcb3334f95c55799", size = 423531, upload-time = "2026-06-11T14:02:19.123Z" }, + { url = "https://files.pythonhosted.org/packages/d4/70/2471ae8f78bb9639ccc2cd96ca17996a50d95806638a221415d8235e518d/djlint-1.39.2-cp312-cp312-win_amd64.whl", hash = "sha256:38129305d27f5e635404b131f21a2a4d8ffcc1f58446c0f3b4a25b38140ec710", size = 472749, upload-time = "2026-06-11T14:02:31.454Z" }, + { url = "https://files.pythonhosted.org/packages/d8/16/f03cd22c81652445d4f61c7658ca25e4fe048fd188ad58198c1cca36d9d1/djlint-1.39.2-cp312-cp312-win_arm64.whl", hash = "sha256:2382bfc52d2bf9ca9565696e4ee89ce811783090f36b4b03ce30888672da9506", size = 405100, upload-time = "2026-06-11T14:02:03.425Z" }, + { url = "https://files.pythonhosted.org/packages/05/9b/d1a630b2495f8b6a60555012dd57c661a9fe683c1f8483287ecddae749dc/djlint-1.39.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c243a1f18211cb9a4fec5b95f0b43c61941414f71fdb3d77dcc9798f9644b623", size = 538489, upload-time = "2026-06-11T14:02:54.322Z" }, + { url = "https://files.pythonhosted.org/packages/87/39/7e15d99f40cf6ec1d624df48ab465725914cf6c789fdf6f6dc28db264666/djlint-1.39.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:74d65499c5b29e5269eb6a4bff9dbf426c22cba54f4810d30fbfbcb482c9035f", size = 510153, upload-time = "2026-06-11T14:02:10.287Z" }, + { url = "https://files.pythonhosted.org/packages/db/a6/9ab6b79728a3a7f8235eac0f0d21e01e7457adfe3f368a3a1132eaa52897/djlint-1.39.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25b955e23c09bdfdb04de6b40b848cf93b39952178aa37d940af921a9715da4b", size = 535815, upload-time = "2026-06-11T14:02:01.034Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/3eceecb4bead34e9087102078b58c084fa53f995827f03ea7cbf42fd5c48/djlint-1.39.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a2c0191f93181557e8dcbca2a6766ab972f769bb9c32d488cefa1b5195a74d", size = 557939, upload-time = "2026-06-11T14:02:43.072Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/95bdb7d40699ae60d4dfda0ed7278e0a09f224c505bf0cd396f1e6ab4c23/djlint-1.39.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e0aa5e3087dd346ae3197781fa425aebccce6beed593f4dd17cbb00179ef8444", size = 541687, upload-time = "2026-06-11T14:02:11.683Z" }, + { url = "https://files.pythonhosted.org/packages/73/a7/6b8ba672e1c7c8216306ecfacd4053b645ef583f573c69a3f2087c654922/djlint-1.39.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30094c7533b0e919f4674baeaf0acd061b1eb6c8ffbf4d6d3dc856c686747411", size = 568429, upload-time = "2026-06-11T14:01:55.72Z" }, + { url = "https://files.pythonhosted.org/packages/d2/50/03b189c613fb3f0286e8ddfbc3a31856c6a2d3c7a01eb89cf8441ca25f21/djlint-1.39.2-cp313-cp313-win32.whl", hash = "sha256:3eb8942afbf2e5d0ffc208d88db33dc38abb585ce03fdd17d9a0f03e4b7e761a", size = 423325, upload-time = "2026-06-11T14:01:48.24Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/a0a34a80b732ae227b395bdd2718836c696336524bf6f97210cc6ff871e6/djlint-1.39.2-cp313-cp313-win_amd64.whl", hash = "sha256:9040e8ff1e7e141cd67402110f0edb67835955003c7a076c97398dcee5f814d1", size = 472106, upload-time = "2026-06-11T14:02:12.8Z" }, + { url = "https://files.pythonhosted.org/packages/bf/65/c28711b4e15c932e2be15eee9b0d37465394bab65acb0f2f5f51ee42decd/djlint-1.39.2-cp313-cp313-win_arm64.whl", hash = "sha256:a54edd4326adc48577d244425e8d3c175ec3cc6abd3732418efeccc0b92b9d1a", size = 404747, upload-time = "2026-06-11T14:02:33.238Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/16cfba85037a7dcd2333263594c8c94f6443b0f3726f55cd2426fc7fac5b/djlint-1.39.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e2fefc27180dad754e76c9ece6f5dbe99a808c7a0dd4ff4213dd39d82f1878cd", size = 537519, upload-time = "2026-06-11T14:03:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9b/dd5dffc2eadb46a3167c13151b6f7dbcef39996da56794d45f43b1826a61/djlint-1.39.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c62372a9d037d612f9b957829c0dbdef96e017dc2d9cf4970e094de6fee4ee55", size = 509380, upload-time = "2026-06-11T14:02:35.978Z" }, + { url = "https://files.pythonhosted.org/packages/0a/38/2e89eea336e5990999112fddd7b238407a3d9183a06acb38e00bd79d1dfd/djlint-1.39.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d27a88e84fd9e75cc49247b9d200f51c7bd037364efd58256d3b0e70c10775db", size = 538317, upload-time = "2026-06-11T14:02:49.693Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fe/2546eecaaed67b2f92c2bd571b88b84692cd9b6883bdbfe6370391d86219/djlint-1.39.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b77ce23aabab2426f5130948c0d605bd7414550bbca8a3892b7d255644c2da", size = 557757, upload-time = "2026-06-11T14:02:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fa/36bc8176a4a1702ed8446b606ddecc9502d398a55c3803e69469ba8304f9/djlint-1.39.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4547b35fd6f5030ffe4555e66916f8ba8107e0e9c2c6a4e721fb980ba79e3fce", size = 544474, upload-time = "2026-06-11T14:02:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/62/69/5c0733745e5a2a275f3dad4977745890a777c5a74141b6c0e13a998992c2/djlint-1.39.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1257a2db80184ba70a195d8fe6026b54dab4dbc5156a90a61adbc916e500a6b1", size = 567721, upload-time = "2026-06-11T14:01:49.913Z" }, + { url = "https://files.pythonhosted.org/packages/5c/66/7c2a820f2d9948ff0b423d6395664a13979ff702edeb6efd51bf404b5c66/djlint-1.39.2-cp314-cp314-win32.whl", hash = "sha256:766d0266fbb0064c7579d66ed06487d9198d99880c5bbab03f24386edfa766f5", size = 429369, upload-time = "2026-06-11T14:02:20.447Z" }, + { url = "https://files.pythonhosted.org/packages/2e/32/006d9e22e2184c87cd556a7333d12c5fba2ec8b78bc6333621c4b067b81d/djlint-1.39.2-cp314-cp314-win_amd64.whl", hash = "sha256:345608f6eefe627d39f2491a673bc80688a86af3977113fc91b01f4b827bec2a", size = 481219, upload-time = "2026-06-11T14:01:59.669Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/5488c01bc730b86af531b72066c913d0ebe234ddb3101f9898eb044ffc44/djlint-1.39.2-cp314-cp314-win_arm64.whl", hash = "sha256:ed0d24f5af98f7ef8c50061b64ab4cdd61abfd133df9f99810d3efc82e25a3c1", size = 412865, upload-time = "2026-06-11T14:02:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/de/89/66b5260dc2f677264e0580565eb1a7b30bd842bebaba7940e11dc9a348b0/djlint-1.39.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:16cc5df9891a80090600f32111448e175148670cb7ca7789c04c4cdf9f9334d8", size = 584161, upload-time = "2026-06-11T14:02:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/7a/38/e1a31e0fdd2cf090f4f862f282fe1fb0b363c0e248a105ed7f3aa16a5508/djlint-1.39.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20df1b4a79bc05329c207110478ad26513deaef3a7409b4a3cab55216172749", size = 556646, upload-time = "2026-06-11T14:02:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/57/58/95733fc6f42a0bf31aa1186e0323fe224327bb840666d083261913cac90b/djlint-1.39.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c2f34d9ef9aa73536bf485648ce4fa381bb3d8d843fb97ec6725d5b3457b84d", size = 573320, upload-time = "2026-06-11T14:02:21.965Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7c/9a03f9e859bc3dd05992dd8bf40298bcc18693dbbcba25a3eec58dd25fa5/djlint-1.39.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:851a9c78674120c99fe6842bcd3377bce698583823ee7861ae4992e84fff3e9b", size = 593052, upload-time = "2026-06-11T14:02:39.044Z" }, + { url = "https://files.pythonhosted.org/packages/b7/73/d0fa38536f1652584ddb6db9487bf8ef95322b92ce88ff4fe16719715526/djlint-1.39.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c9df4a39d57476bcd2a02ae8d873796b92517517d4512f497b40be3e7398e84", size = 579381, upload-time = "2026-06-11T14:02:40.393Z" }, + { url = "https://files.pythonhosted.org/packages/ee/35/689726a28c99af26b925f97f66b7046eee3b3881ca3161a04695e8e8112b/djlint-1.39.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e0356e3a66357d72ca5d69f2fb78d47fed26efb31d104cba44f5e6fbf503a4e2", size = 602713, upload-time = "2026-06-11T14:02:44.356Z" }, + { url = "https://files.pythonhosted.org/packages/91/03/5e59d32de6d3b4fc6113450e6198755d169b23fc7d8daaa5bbcc2ebc5c57/djlint-1.39.2-cp314-cp314t-win32.whl", hash = "sha256:ad5213ce1bbab6c18e1461498f219ca66382c40766d499e233ff2174a23026c6", size = 475348, upload-time = "2026-06-11T14:01:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/bd/58/bd0ca13d2cfb31b666efc3b10af5dd37a72269b0282ec6490993e9655704/djlint-1.39.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d5711a2945844446fa19c35f151e077c2119411e49db30a3f97f32b9325b89bf", size = 535320, upload-time = "2026-06-11T14:03:00.083Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c5/b2b8d8918f770a0dac8b2ad7f4a5caa3bc63c60f4cd178418b72f19f0a5a/djlint-1.39.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f894464e6946e761003844ac5bfe4f51f362258a51d4e65a9641a4bd74da4ea2", size = 429535, upload-time = "2026-06-11T14:02:02.145Z" }, + { url = "https://files.pythonhosted.org/packages/62/c1/ffb80fed94a3bf746d37134b504b794de893cb71e229ea2e1c91f14d1864/djlint-1.39.2-py3-none-any.whl", hash = "sha256:7c49bfda97816afd36273f12d67aa432ed44cd9f62d5c31f9ea9c316ebe808b2", size = 62347, upload-time = "2026-06-11T14:02:23.136Z" }, ] [[package]] @@ -138,7 +138,7 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "djlint", specifier = "==1.39.0" }, + { name = "djlint", specifier = "==1.39.2" }, { name = "yamllint", specifier = "==1.38.0" }, { name = "zizmor", specifier = "==1.25.2" }, ] -- 2.54.0 From 7684221ed47b49d572e2177cc2258e66a4fcdf99 Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 22 Jun 2026 06:51:16 +0200 Subject: [PATCH 011/169] feat(actions): implement `jobs..continue-on-error` (#38100) Support `continue-on-error` for workflow jobs when aggregating an Actions workflow run status. Previously, `continue-on-error` was parsed from workflow YAML but was not persisted or used when calculating the overall run result. As a result, a failed job could incorrectly fail the entire workflow even when the workflow explicitly allowed that job to fail. This PR stores the parsed `continue-on-error` value on each action run job and treats failed jobs with `continue-on-error: true` as successful when computing the workflow run status, matching GitHub Actions behavior. ## Changes - Add `ContinueOnError` to `jobparser.Job`. - Add `continue_on_error` to `ActionRunJob` with a `NOT NULL DEFAULT FALSE` migration. - Populate `ActionRunJob.ContinueOnError` when creating workflow run jobs. - Update workflow status aggregation so failed `continue-on-error` jobs do not fail the overall run. - Leave `resolveCheckNeeds` unchanged so dependent jobs still see the job result as `failure` and are skipped by default. ## Compatibility This is backward compatible. If only the runner or only the server is updated, `continue-on-error` continues to degrade to the previous behavior and is effectively ignored until both sides support it. Related runner PR: https://gitea.com/gitea/runner/pulls/1032 --------- Signed-off-by: bircni Co-authored-by: Lunny Xiao --- models/actions/run_job.go | 11 ++- models/actions/status_test.go | 54 ++++++++++++ models/migrations/migrations.go | 1 + models/migrations/v1_27/v340.go | 24 ++++++ modules/actions/jobparser/jobparser.go | 3 + modules/actions/jobparser/jobparser_test.go | 5 ++ modules/actions/jobparser/model.go | 83 +++++++++++-------- modules/actions/jobparser/model_test.go | 46 ++++++++++ .../testdata/continue_on_error_expr.in.yaml | 10 +++ .../testdata/continue_on_error_expr.out.yaml | 25 ++++++ services/actions/job_emitter.go | 5 ++ services/actions/job_emitter_test.go | 18 ++++ services/actions/rerun.go | 1 + services/actions/rerun_test.go | 16 ++++ services/actions/reusable_workflow.go | 1 + services/actions/run.go | 1 + 16 files changed, 268 insertions(+), 36 deletions(-) create mode 100644 models/migrations/v1_27/v340.go create mode 100644 modules/actions/jobparser/testdata/continue_on_error_expr.in.yaml create mode 100644 modules/actions/jobparser/testdata/continue_on_error_expr.out.yaml diff --git a/models/actions/run_job.go b/models/actions/run_job.go index df01546fd8..02877e0e2c 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -108,6 +108,10 @@ type ActionRunJob struct { // ParentJobID scopes `Needs` resolution: name lookups happen only among rows sharing the same ParentJobID. 0 for top-level rows. ParentJobID int64 `xorm:"index NOT NULL DEFAULT 0"` + // ContinueOnError mirrors the job-level continue-on-error field from the workflow YAML. + // When true, a failure of this job does not fail the overall workflow run. + ContinueOnError bool `xorm:"NOT NULL DEFAULT FALSE"` + Started timeutil.TimeStamp Stopped timeutil.TimeStamp Created timeutil.TimeStamp `xorm:"created"` @@ -500,9 +504,12 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status { allSkipped := len(jobs) != 0 var hasFailure, hasCancelled, hasCancelling, hasWaiting, hasRunning, hasBlocked bool for _, job := range jobs { - allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped) + // A failed job with continue-on-error:true does not fail the workflow run. + // It counts as a "continued failure" and is treated like success for aggregation. + isContinuedFailure := job.ContinueOnError && job.Status == StatusFailure + allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped || isContinuedFailure) allSkipped = allSkipped && job.Status == StatusSkipped - hasFailure = hasFailure || job.Status == StatusFailure + hasFailure = hasFailure || (job.Status == StatusFailure && !job.ContinueOnError) hasCancelled = hasCancelled || job.Status == StatusCancelled hasCancelling = hasCancelling || job.Status == StatusCancelling hasWaiting = hasWaiting || job.Status == StatusWaiting diff --git a/models/actions/status_test.go b/models/actions/status_test.go index f1551b2892..eed45af9b0 100644 --- a/models/actions/status_test.go +++ b/models/actions/status_test.go @@ -48,3 +48,57 @@ func TestStatusFromResult(t *testing.T) { assert.Equal(t, tt.want, StatusFromResult(tt.result), "result=%s", tt.result) } } + +func newJob(status Status, continueOnError bool) *ActionRunJob { + return &ActionRunJob{Status: status, ContinueOnError: continueOnError} +} + +func TestAggregateJobStatusContinueOnError(t *testing.T) { + cases := []struct { + name string + jobs []*ActionRunJob + want Status + }{ + { + name: "all success", + jobs: []*ActionRunJob{newJob(StatusSuccess, false), newJob(StatusSuccess, false)}, + want: StatusSuccess, + }, + { + name: "one failure without continue-on-error", + jobs: []*ActionRunJob{newJob(StatusSuccess, false), newJob(StatusFailure, false)}, + want: StatusFailure, + }, + { + name: "one failure with continue-on-error", + jobs: []*ActionRunJob{newJob(StatusSuccess, false), newJob(StatusFailure, true)}, + want: StatusSuccess, + }, + { + name: "only continued-failure", + jobs: []*ActionRunJob{newJob(StatusFailure, true)}, + want: StatusSuccess, + }, + { + name: "continued-failure plus real failure", + jobs: []*ActionRunJob{newJob(StatusFailure, true), newJob(StatusFailure, false)}, + want: StatusFailure, + }, + { + name: "all skipped", + jobs: []*ActionRunJob{newJob(StatusSkipped, false), newJob(StatusSkipped, false)}, + want: StatusSkipped, + }, + { + name: "continued-failure plus skipped counts as success", + jobs: []*ActionRunJob{newJob(StatusFailure, true), newJob(StatusSkipped, false)}, + want: StatusSuccess, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, AggregateJobStatus(tt.jobs)) + }) + } +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index a026ee7a52..006016f447 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -417,6 +417,7 @@ func prepareMigrationTasks() []*migration { newMigration(337, "Add visibility to team", v1_27.AddVisibilityToTeam), newMigration(338, "Expand legacy MSSQL issue/comment long-text columns", v1_27.ExpandIssueAndCommentLongTextFieldsForMSSQL), newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex), + newMigration(340, "Add ContinueOnError column to ActionRunJob", v1_27.AddContinueOnErrorToActionRunJob), } return preparedMigrations } diff --git a/models/migrations/v1_27/v340.go b/models/migrations/v1_27/v340.go new file mode 100644 index 0000000000..dcb942dfb4 --- /dev/null +++ b/models/migrations/v1_27/v340.go @@ -0,0 +1,24 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "gitea.dev/models/db" + + "xorm.io/xorm" +) + +// AddContinueOnErrorToActionRunJob adds the ContinueOnError column to ActionRunJob, +// storing the job-level continue-on-error value from the workflow YAML. +func AddContinueOnErrorToActionRunJob(x db.EngineMigration) error { + type ActionRunJob struct { + ContinueOnError bool `xorm:"NOT NULL DEFAULT FALSE"` + } + + _, err := x.SyncWithOptions(xorm.SyncOptions{ + IgnoreDropIndices: true, + IgnoreConstrains: true, + }, new(ActionRunJob)) + return err +} diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index e7a2b48498..79c7b7b433 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -69,6 +69,9 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { runsOn[i] = evaluator.Interpolate(v) } job.RawRunsOn = encodeRunsOn(runsOn) + if err := evaluator.EvaluateYamlNode(&job.RawContinueOnError); err != nil { + return nil, fmt.Errorf("evaluate continue-on-error for job %q: %w", id, err) + } swf := &SingleWorkflow{ Name: workflow.Name, RawOn: workflow.RawOn, diff --git a/modules/actions/jobparser/jobparser_test.go b/modules/actions/jobparser/jobparser_test.go index e74f0644f8..05bb3151b7 100644 --- a/modules/actions/jobparser/jobparser_test.go +++ b/modules/actions/jobparser/jobparser_test.go @@ -58,6 +58,11 @@ func TestParse(t *testing.T) { options: nil, wantErr: false, }, + { + name: "continue_on_error_expr", + options: nil, + wantErr: false, + }, } invalidFileTests := []struct { name string diff --git a/modules/actions/jobparser/model.go b/modules/actions/jobparser/model.go index c80626e4c0..97a36235d2 100644 --- a/modules/actions/jobparser/model.go +++ b/modules/actions/jobparser/model.go @@ -79,23 +79,37 @@ func (w *SingleWorkflow) Marshal() ([]byte, error) { } type Job struct { - Name string `yaml:"name,omitempty"` - RawNeeds yaml.Node `yaml:"needs,omitempty"` - RawRunsOn yaml.Node `yaml:"runs-on,omitempty"` - Env yaml.Node `yaml:"env,omitempty"` - If yaml.Node `yaml:"if,omitempty"` - Steps []*Step `yaml:"steps,omitempty"` - TimeoutMinutes string `yaml:"timeout-minutes,omitempty"` - Services map[string]*ContainerSpec `yaml:"services,omitempty"` - Strategy Strategy `yaml:"strategy,omitempty"` - RawContainer yaml.Node `yaml:"container,omitempty"` - Defaults Defaults `yaml:"defaults,omitempty"` - Outputs map[string]string `yaml:"outputs,omitempty"` - Uses string `yaml:"uses,omitempty"` - With map[string]any `yaml:"with,omitempty"` - RawSecrets yaml.Node `yaml:"secrets,omitempty"` - RawConcurrency *model.RawConcurrency `yaml:"concurrency,omitempty"` - RawPermissions yaml.Node `yaml:"permissions,omitempty"` + Name string `yaml:"name,omitempty"` + RawNeeds yaml.Node `yaml:"needs,omitempty"` + RawRunsOn yaml.Node `yaml:"runs-on,omitempty"` + Env yaml.Node `yaml:"env,omitempty"` + If yaml.Node `yaml:"if,omitempty"` + Steps []*Step `yaml:"steps,omitempty"` + TimeoutMinutes string `yaml:"timeout-minutes,omitempty"` + RawContinueOnError yaml.Node `yaml:"continue-on-error,omitempty"` + Services map[string]*ContainerSpec `yaml:"services,omitempty"` + Strategy Strategy `yaml:"strategy,omitempty"` + RawContainer yaml.Node `yaml:"container,omitempty"` + Defaults Defaults `yaml:"defaults,omitempty"` + Outputs map[string]string `yaml:"outputs,omitempty"` + Uses string `yaml:"uses,omitempty"` + With map[string]any `yaml:"with,omitempty"` + RawSecrets yaml.Node `yaml:"secrets,omitempty"` + RawConcurrency *model.RawConcurrency `yaml:"concurrency,omitempty"` + RawPermissions yaml.Node `yaml:"permissions,omitempty"` +} + +// GetContinueOnError decodes the continue-on-error field to a bool. +// The field may be a literal bool or an already-evaluated expression node. +func (j *Job) GetContinueOnError() bool { + if j.RawContinueOnError.Kind == 0 { + return false + } + var v bool + if err := j.RawContinueOnError.Decode(&v); err != nil { + return false + } + return v } func (j *Job) Clone() *Job { @@ -103,23 +117,24 @@ func (j *Job) Clone() *Job { return nil } return &Job{ - Name: j.Name, - RawNeeds: j.RawNeeds, - RawRunsOn: j.RawRunsOn, - Env: j.Env, - If: j.If, - Steps: j.Steps, - TimeoutMinutes: j.TimeoutMinutes, - Services: j.Services, - Strategy: j.Strategy, - RawContainer: j.RawContainer, - Defaults: j.Defaults, - Outputs: j.Outputs, - Uses: j.Uses, - With: j.With, - RawSecrets: j.RawSecrets, - RawConcurrency: j.RawConcurrency, - RawPermissions: j.RawPermissions, + Name: j.Name, + RawNeeds: j.RawNeeds, + RawRunsOn: j.RawRunsOn, + Env: j.Env, + If: j.If, + Steps: j.Steps, + TimeoutMinutes: j.TimeoutMinutes, + RawContinueOnError: j.RawContinueOnError, + Services: j.Services, + Strategy: j.Strategy, + RawContainer: j.RawContainer, + Defaults: j.Defaults, + Outputs: j.Outputs, + Uses: j.Uses, + With: j.With, + RawSecrets: j.RawSecrets, + RawConcurrency: j.RawConcurrency, + RawPermissions: j.RawPermissions, } } diff --git a/modules/actions/jobparser/model_test.go b/modules/actions/jobparser/model_test.go index 567c160f92..ff85e912af 100644 --- a/modules/actions/jobparser/model_test.go +++ b/modules/actions/jobparser/model_test.go @@ -336,6 +336,52 @@ func TestSingleWorkflow_SetJob(t *testing.T) { }) } +func TestGetContinueOnError(t *testing.T) { + tests := []struct { + name string + yaml string + want bool + }{ + { + name: "absent", + yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n steps:\n - run: echo hi\n", + want: false, + }, + { + name: "static true", + yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: true\n steps:\n - run: echo hi\n", + want: true, + }, + { + name: "static false", + yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: false\n steps:\n - run: echo hi\n", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Parse([]byte(tt.yaml)) + require.NoError(t, err) + require.Len(t, got, 1) + _, job := got[0].Job() + assert.Equal(t, tt.want, job.GetContinueOnError()) + }) + } + + // Expression case: ${{ matrix.experimental }} must resolve per matrix variant. + t.Run("matrix expression", func(t *testing.T) { + content := ReadTestdata(t, "continue_on_error_expr.in.yaml") + got, err := Parse(content) + require.NoError(t, err) + require.Len(t, got, 2) + // sorted by matrix name: (false) before (true) + _, jobFalse := got[0].Job() + _, jobTrue := got[1].Job() + assert.False(t, jobFalse.GetContinueOnError()) + assert.True(t, jobTrue.GetContinueOnError()) + }) +} + func TestParseMappingNode(t *testing.T) { tests := []struct { input string diff --git a/modules/actions/jobparser/testdata/continue_on_error_expr.in.yaml b/modules/actions/jobparser/testdata/continue_on_error_expr.in.yaml new file mode 100644 index 0000000000..5dc55d38eb --- /dev/null +++ b/modules/actions/jobparser/testdata/continue_on_error_expr.in.yaml @@ -0,0 +1,10 @@ +name: test +jobs: + job1: + strategy: + matrix: + experimental: [false, true] + runs-on: ubuntu-22.04 + continue-on-error: ${{ matrix.experimental }} + steps: + - run: echo hi diff --git a/modules/actions/jobparser/testdata/continue_on_error_expr.out.yaml b/modules/actions/jobparser/testdata/continue_on_error_expr.out.yaml new file mode 100644 index 0000000000..0d3afff878 --- /dev/null +++ b/modules/actions/jobparser/testdata/continue_on_error_expr.out.yaml @@ -0,0 +1,25 @@ +name: test +jobs: + job1: + name: job1 (false) + runs-on: ubuntu-22.04 + steps: + - run: echo hi + continue-on-error: false + strategy: + matrix: + experimental: + - false +--- +name: test +jobs: + job1: + name: job1 (true) + runs-on: ubuntu-22.04 + steps: + - run: echo hi + continue-on-error: true + strategy: + matrix: + experimental: + - true diff --git a/services/actions/job_emitter.go b/services/actions/job_emitter.go index 9c9a408db0..1b45eab83f 100644 --- a/services/actions/job_emitter.go +++ b/services/actions/job_emitter.go @@ -380,6 +380,11 @@ func (r *jobStatusResolver) resolveCheckNeeds(id int64) (allDone, allSucceed boo if !needStatus.IsDone() { allDone = false } + // A failed need with continue-on-error:true is treated as success, matching AggregateJobStatus, + // so a downstream job with an implicit `success()` is not skipped. + if needJob := r.jobMap[need]; needJob != nil && needJob.ContinueOnError && needStatus == actions_model.StatusFailure { + continue + } if needStatus.In(actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped) { allSucceed = false } diff --git a/services/actions/job_emitter_test.go b/services/actions/job_emitter_test.go index 892ba6c2c5..6b15ecd346 100644 --- a/services/actions/job_emitter_test.go +++ b/services/actions/job_emitter_test.go @@ -131,6 +131,24 @@ jobs: }, want: map[int64]actions_model.Status{2: actions_model.StatusSkipped}, }, + { + name: "`if` is empty and a failed need has continue-on-error", + jobs: actions_model.ActionJobList{ + {ID: 1, JobID: "job1", Status: actions_model.StatusFailure, ContinueOnError: true, Needs: []string{}}, + {ID: 2, JobID: "job2", Status: actions_model.StatusBlocked, Needs: []string{"job1"}, WorkflowPayload: []byte( + ` +name: test +on: push +jobs: + job2: + runs-on: ubuntu-latest + needs: job1 + steps: + - run: echo "should run, job1 failure is masked by continue-on-error" +`)}, + }, + want: map[int64]actions_model.Status{2: actions_model.StatusWaiting}, + }, } assert.NoError(t, unittest.PrepareTestDatabase()) ctx := t.Context() diff --git a/services/actions/rerun.go b/services/actions/rerun.go index 1ef84608a0..17076a594c 100644 --- a/services/actions/rerun.go +++ b/services/actions/rerun.go @@ -506,6 +506,7 @@ func cloneRunJobForAttempt(templateJob *actions_model.ActionRunJob, attempt *act AttemptJobID: templateJob.AttemptJobID, Needs: slices.Clone(templateJob.Needs), RunsOn: slices.Clone(templateJob.RunsOn), + ContinueOnError: templateJob.ContinueOnError, Status: templateJob.Status, RawConcurrency: templateJob.RawConcurrency, IsConcurrencyEvaluated: templateJob.IsConcurrencyEvaluated, diff --git a/services/actions/rerun_test.go b/services/actions/rerun_test.go index baa678a440..9c0f63e165 100644 --- a/services/actions/rerun_test.go +++ b/services/actions/rerun_test.go @@ -80,6 +80,22 @@ func TestGetFailedJobsForRerun(t *testing.T) { }) } +func TestCloneRunJobForAttempt(t *testing.T) { + attempt := &actions_model.ActionRunAttempt{ID: 42, Attempt: 2} + + t.Run("preserves continue-on-error", func(t *testing.T) { + template := &actions_model.ActionRunJob{ContinueOnError: true, Status: actions_model.StatusFailure} + clone := cloneRunJobForAttempt(template, attempt) + assert.True(t, clone.ContinueOnError) + }) + + t.Run("defaults to false when template has it unset", func(t *testing.T) { + template := &actions_model.ActionRunJob{ContinueOnError: false} + clone := cloneRunJobForAttempt(template, attempt) + assert.False(t, clone.ContinueOnError) + }) +} + func TestRerunValidation(t *testing.T) { runningRun := &actions_model.ActionRun{Status: actions_model.StatusRunning} diff --git a/services/actions/reusable_workflow.go b/services/actions/reusable_workflow.go index 65a6acfbd0..cf77824c4d 100644 --- a/services/actions/reusable_workflow.go +++ b/services/actions/reusable_workflow.go @@ -325,6 +325,7 @@ func insertCallerChildren(ctx context.Context, run *actions_model.ActionRun, att AttemptJobID: attemptJobID, Needs: needs, RunsOn: parsedChild.RunsOn(), + ContinueOnError: parsedChild.GetContinueOnError(), Status: actions_model.StatusBlocked, ParentJobID: caller.ID, WorkflowSourceRepoID: sourceRepoID, diff --git a/services/actions/run.go b/services/actions/run.go index 82335af861..58e50de0c2 100644 --- a/services/actions/run.go +++ b/services/actions/run.go @@ -164,6 +164,7 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte Status: util.Iif(shouldBlockJob, actions_model.StatusBlocked, actions_model.StatusWaiting), WorkflowSourceRepoID: run.RepoID, WorkflowSourceCommitSHA: run.CommitSHA, + ContinueOnError: job.GetContinueOnError(), } // Parse workflow/job permissions (no clamping here) if perms := ExtractJobPermissionsFromWorkflow(v, job); perms != nil { -- 2.54.0 From a4781dde89ee9cc2e5af73a2c4d2f4d32cda0aee Mon Sep 17 00:00:00 2001 From: puni9869 <80308335+puni9869@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:15:24 +0530 Subject: [PATCH 012/169] fix(indexer): fix assignee filters in issue search (#38021) fix(indexer): fix assignee filters in issue search (#38021) Issue search filtering still relied on the legacy single-assignee field, so searches such as "Assigned to you" could miss issues when a keyword query was used. Index all issue assignee IDs and add an explicit no_assignee field so specific, any-assignee, and no-assignee filters work consistently across Bleve, Elasticsearch, and Meilisearch. Fixes #36299. --- modules/indexer/issues/bleve/bleve.go | 23 ++++--- modules/indexer/issues/bleve/bleve_test.go | 68 +++++++++++++++++++ .../issues/elasticsearch/elasticsearch.go | 22 +++--- modules/indexer/issues/internal/model.go | 3 +- .../indexer/issues/internal/tests/tests.go | 27 ++++++-- .../indexer/issues/meilisearch/meilisearch.go | 22 +++--- modules/indexer/issues/util.go | 8 ++- 7 files changed, 133 insertions(+), 40 deletions(-) diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index a919317728..25dbdebfa4 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -11,7 +11,6 @@ import ( indexer_internal "gitea.dev/modules/indexer/internal" inner_bleve "gitea.dev/modules/indexer/internal/bleve" "gitea.dev/modules/indexer/issues/internal" - "gitea.dev/modules/optional" "gitea.dev/modules/util" "github.com/blevesearch/bleve/v2" @@ -27,7 +26,7 @@ import ( const ( issueIndexerAnalyzer = "issueIndexer" issueIndexerDocType = "issueIndexerDocType" - issueIndexerLatestVersion = 6 + issueIndexerLatestVersion = 7 ) const unicodeNormalizeName = "unicodeNormalize" @@ -86,7 +85,8 @@ func generateIssueIndexMapping() (mapping.IndexMapping, error) { docMapping.AddFieldMappingsAt("project_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("no_project", boolFieldMapping) docMapping.AddFieldMappingsAt("poster_id", numberFieldMapping) - docMapping.AddFieldMappingsAt("assignee_id", numberFieldMapping) + docMapping.AddFieldMappingsAt("assignee_ids", numberFieldMapping) + docMapping.AddFieldMappingsAt("no_assignee", boolFieldMapping) docMapping.AddFieldMappingsAt("mention_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("reviewed_ids", numberFieldMapping) docMapping.AddFieldMappingsAt("review_requested_ids", numberFieldMapping) @@ -258,14 +258,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( queries = append(queries, inner_bleve.NumericEqualityQuery(posterIDInt64, "poster_id")) } - if options.AssigneeID != "" { - if options.AssigneeID == "(any)" { - queries = append(queries, inner_bleve.NumericRangeInclusiveQuery(optional.Some[int64](1), optional.None[int64](), "assignee_id")) - } else { - // "(none)" becomes 0, it means no assignee - assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) - queries = append(queries, inner_bleve.NumericEqualityQuery(assigneeIDInt64, "assignee_id")) - } + switch options.AssigneeID { + case "": + case "(any)": + queries = append(queries, inner_bleve.BoolFieldQuery(false, "no_assignee")) + case "(none)": + queries = append(queries, inner_bleve.BoolFieldQuery(true, "no_assignee")) + default: + assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) + queries = append(queries, inner_bleve.NumericEqualityQuery(assigneeIDInt64, "assignee_ids")) } if options.MentionID.Has() { diff --git a/modules/indexer/issues/bleve/bleve_test.go b/modules/indexer/issues/bleve/bleve_test.go index ccd7ab7b92..7dc664e0ef 100644 --- a/modules/indexer/issues/bleve/bleve_test.go +++ b/modules/indexer/issues/bleve/bleve_test.go @@ -6,7 +6,11 @@ package bleve import ( "testing" + "gitea.dev/modules/indexer/issues/internal" "gitea.dev/modules/indexer/issues/internal/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBleveIndexer(t *testing.T) { @@ -16,3 +20,67 @@ func TestBleveIndexer(t *testing.T) { tests.TestIndexer(t, indexer) } + +func TestBleveIndexerNoAssignee(t *testing.T) { + dir := t.TempDir() + indexer := NewIndexer(dir) + defer indexer.Close() + + _, err := indexer.Init(t.Context()) + require.NoError(t, err) + + require.NoError(t, indexer.Index(t.Context(), + &internal.IndexerData{ID: 1, Title: "assigned through assignee_ids", AssigneeIDs: []int64{2}}, + &internal.IndexerData{ID: 2, Title: "unassigned", NoAssignee: true}, + &internal.IndexerData{ID: 3, Title: "assigned through multiple assignee_ids", AssigneeIDs: []int64{3, 4}}, + )) + + testCases := []struct { + name string + opts *internal.SearchOptions + expectedIDs []int64 + }{ + { + name: "none", + opts: &internal.SearchOptions{AssigneeID: "(none)"}, + expectedIDs: []int64{2}, + }, + { + name: "any", + opts: &internal.SearchOptions{AssigneeID: "(any)"}, + expectedIDs: []int64{1, 3}, + }, + { + name: "specific", + opts: &internal.SearchOptions{AssigneeID: "2"}, + expectedIDs: []int64{1}, + }, + { + name: "specific first multi-assignee", + opts: &internal.SearchOptions{AssigneeID: "3"}, + expectedIDs: []int64{3}, + }, + { + name: "specific second multi-assignee", + opts: &internal.SearchOptions{AssigneeID: "4"}, + expectedIDs: []int64{3}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result, err := indexer.Search(t.Context(), testCase.opts) + require.NoError(t, err) + assert.Equal(t, int64(len(testCase.expectedIDs)), result.Total) + assert.ElementsMatch(t, testCase.expectedIDs, searchResultIDs(result)) + }) + } +} + +func searchResultIDs(result *internal.SearchResult) []int64 { + ids := make([]int64, 0, len(result.Hits)) + for _, hit := range result.Hits { + ids = append(ids, hit.ID) + } + return ids +} diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index baef644cc1..87670a428d 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -16,7 +16,7 @@ import ( "gitea.dev/modules/util" ) -const issueIndexerLatestVersion = 3 +const issueIndexerLatestVersion = 4 var _ internal.Indexer = &Indexer{} @@ -57,7 +57,8 @@ const ( "project_ids": { "type": "integer", "index": true }, "no_project": { "type": "boolean", "index": true }, "poster_id": { "type": "integer", "index": true }, - "assignee_id": { "type": "integer", "index": true }, + "assignee_ids": { "type": "integer", "index": true }, + "no_assignee": { "type": "boolean", "index": true }, "mention_ids": { "type": "integer", "index": true }, "reviewed_ids": { "type": "integer", "index": true }, "review_requested_ids": { "type": "integer", "index": true }, @@ -177,14 +178,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( query.Must(es.TermQuery("poster_id", posterIDInt64)) } - if options.AssigneeID != "" { - if options.AssigneeID == "(any)" { - query.Must(es.NewRangeQuery("assignee_id").Gte(1)) - } else { - // "(none)" becomes 0, it means no assignee - assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) - query.Must(es.TermQuery("assignee_id", assigneeIDInt64)) - } + switch options.AssigneeID { + case "": + case "(any)": + query.Must(es.TermQuery("no_assignee", false)) + case "(none)": + query.Must(es.TermQuery("no_assignee", true)) + default: + assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) + query.Must(es.TermQuery("assignee_ids", assigneeIDInt64)) } if options.MentionID.Has() { diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index b964f349df..0e9162f0b9 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -34,7 +34,8 @@ type IndexerData struct { NoProject bool `json:"no_project"` // True if ProjectIDs is empty ProjectColumnMap map[int64]int64 `json:"project_column_map,omitempty"` // Maps project ID to column ID for each project the issue is in PosterID int64 `json:"poster_id"` - AssigneeID int64 `json:"assignee_id"` + AssigneeIDs []int64 `json:"assignee_ids"` + NoAssignee bool `json:"no_assignee"` // True if the issue has no assignees MentionIDs []int64 `json:"mention_ids"` ReviewedIDs []int64 `json:"reviewed_ids"` ReviewRequestedIDs []int64 `json:"review_requested_ids"` diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index 276f3fb5bb..8337272592 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -377,10 +377,10 @@ var cases = []*testIndexerCase{ Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { assert.Len(t, result.Hits, 5) for _, v := range result.Hits { - assert.Equal(t, int64(1), data[v.ID].AssigneeID) + assert.True(t, slices.Contains(data[v.ID].AssigneeIDs, int64(1))) } assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { - return v.AssigneeID == 1 + return slices.Contains(v.AssigneeIDs, int64(1)) }), result.Total) }, }, @@ -395,10 +395,10 @@ var cases = []*testIndexerCase{ Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { assert.Len(t, result.Hits, 5) for _, v := range result.Hits { - assert.Equal(t, int64(0), data[v.ID].AssigneeID) + assert.True(t, data[v.ID].NoAssignee) } assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { - return v.AssigneeID == 0 + return v.NoAssignee }), result.Total) }, }, @@ -630,10 +630,10 @@ var cases = []*testIndexerCase{ Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { assert.Len(t, result.Hits, 180) for _, v := range result.Hits { - assert.GreaterOrEqual(t, data[v.ID].AssigneeID, int64(1)) + assert.False(t, data[v.ID].NoAssignee) } assert.Equal(t, countIndexerData(data, func(v *internal.IndexerData) bool { - return v.AssigneeID >= 1 + return !v.NoAssignee }), result.Total) }, }, @@ -686,6 +686,18 @@ func generateDefaultIndexerData() []*internal.IndexerData { for i := range projectIDs { projectIDs[i] = int64(i) + 1 // projectID should not be 0 } + var assigneeIDs []int64 + if issueIndex%10 != 0 { + assigneeID := issueIndex % 10 + assigneeIDs = []int64{assigneeID} + if issueIndex%3 == 0 { + nextAssigneeID := assigneeID + 1 + if nextAssigneeID == 10 { + nextAssigneeID = 1 + } + assigneeIDs = append(assigneeIDs, nextAssigneeID) + } + } data = append(data, &internal.IndexerData{ ID: id, @@ -702,7 +714,8 @@ func generateDefaultIndexerData() []*internal.IndexerData { ProjectIDs: projectIDs, NoProject: len(projectIDs) == 0, PosterID: id%10 + 1, // PosterID should not be 0 - AssigneeID: issueIndex % 10, + AssigneeIDs: assigneeIDs, + NoAssignee: len(assigneeIDs) == 0, MentionIDs: mentionIDs, ReviewedIDs: reviewedIDs, ReviewRequestedIDs: reviewRequestedIDs, diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index ddde9c89f0..1ec55bc7e2 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -20,7 +20,7 @@ import ( ) const ( - issueIndexerLatestVersion = 5 + issueIndexerLatestVersion = 6 // TODO: make this configurable if necessary maxTotalHits = 10000 @@ -74,7 +74,8 @@ func NewIndexer(url, apiKey, indexerName string) *Indexer { "project_ids", "no_project", "poster_id", - "assignee_id", + "assignee_ids", + "no_assignee", "mention_ids", "reviewed_ids", "review_requested_ids", @@ -195,14 +196,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( query.And(inner_meilisearch.NewFilterEq("poster_id", posterIDInt64)) } - if options.AssigneeID != "" { - if options.AssigneeID == "(any)" { - query.And(inner_meilisearch.NewFilterGte("assignee_id", 1)) - } else { - // "(none)" becomes 0, it means no assignee - assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) - query.And(inner_meilisearch.NewFilterEq("assignee_id", assigneeIDInt64)) - } + switch options.AssigneeID { + case "": + case "(any)": + query.And(inner_meilisearch.NewFilterEq("no_assignee", false)) + case "(none)": + query.And(inner_meilisearch.NewFilterEq("no_assignee", true)) + default: + assigneeIDInt64, _ := strconv.ParseInt(options.AssigneeID, 10, 64) + query.And(inner_meilisearch.NewFilterEq("assignee_ids", assigneeIDInt64)) } if options.MentionID.Has() { diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index bbfd42be6b..993356c0b4 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -87,6 +87,11 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD return nil, false, err } + assigneeIDs := make([]int64, 0, len(issue.Assignees)) + for _, a := range issue.Assignees { + assigneeIDs = append(assigneeIDs, a.ID) + } + projectIDs := make([]int64, 0, len(issue.Projects)) for _, project := range issue.Projects { projectIDs = append(projectIDs, project.ID) @@ -112,7 +117,8 @@ func getIssueIndexerData(ctx context.Context, issueID int64) (*internal.IndexerD ProjectIDs: projectIDs, NoProject: len(projectIDs) == 0, PosterID: issue.PosterID, - AssigneeID: issue.AssigneeID, + AssigneeIDs: assigneeIDs, + NoAssignee: len(assigneeIDs) == 0, MentionIDs: mentionIDs, ReviewedIDs: reviewedIDs, ReviewRequestedIDs: reviewRequestedIDs, -- 2.54.0 From 649cb6ff3ea8d3a2bee1b715fe0ad92da726178e Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 22 Jun 2026 08:16:09 +0200 Subject: [PATCH 013/169] fix(actions): show run index in run view and fix summary graph height (#38165) - Display the per-repository run number as `#N` next to the run title in the run view, matching the runs list and GitHub - Add the run `Index` to the run view API response (and the devtest mock) to support that - Restore the summary panel's `flex: 1` so the workflow graph fills the right-column height even when a run has no job summaries - Keep the job-summary section content-sized so it doesn't compete with the graph for height - Gate the devtest mock job summaries to a subset of runs so the devtest page also exercises the no-summary layout image --- routers/web/devtest/mock_actions.go | 26 ++++++++++++++---------- routers/web/repo/actions/view.go | 2 ++ web_src/js/components/ActionRunView.ts | 1 + web_src/js/components/RepoActionView.vue | 10 ++++++++- web_src/js/modules/gitea-actions.ts | 1 + 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index bc6fdeb907..aee7218874 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -85,6 +85,7 @@ func MockActionsRunsJobs(ctx *context.Context) { } resp := &actions.ViewResponse{} resp.State.Run.RepoID = 12345 + resp.State.Run.Index = runID resp.State.Run.TitleHTML = `mock run title link` resp.State.Run.Link = setting.AppSubURL + "/devtest/repo-action-view/runs/" + strconv.FormatInt(runID, 10) resp.State.Run.CanDeleteArtifact = true @@ -199,17 +200,20 @@ func MockActionsRunsJobs(ctx *context.Context) { resp.State.Run.CanRerunFailed = runID == 30 && isLatestAttempt // Mock job summaries so the devtest page can preview the Summary panel rendering. - resp.State.Run.JobSummaries = []*actions.ViewJobSummary{ - { - JobID: runID * 10, - JobName: "job 100 (testsubname)", - SummaryHTML: renderUtils.MarkdownToHtml("### Devtest job summary\n\n- Markdown rendering\n- Links: [example](https://example.com)\n\n```sh\necho hello\n```\n"), - }, - { - JobID: runID*10 + 2, - JobName: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit", - SummaryHTML: renderUtils.MarkdownToHtml("### Another summary\n\nThis demonstrates multiple job summaries in one run.\n\n- Item A\n- Item B\n"), - }, + // Only some runs have summaries, so the page also exercises the "no summary" state. + if runID == 10 || runID == 20 { + resp.State.Run.JobSummaries = []*actions.ViewJobSummary{ + { + JobID: runID * 10, + JobName: "job 100 (testsubname)", + SummaryHTML: renderUtils.MarkdownToHtml("### Devtest job summary\n\n- Markdown rendering\n- Links: [example](https://example.com)\n\n```sh\necho hello\n```\n"), + }, + { + JobID: runID*10 + 2, + JobName: "ULTRA LOOOOOOOOOOOONG job name 102 that exceeds the limit", + SummaryHTML: renderUtils.MarkdownToHtml("### Another summary\n\nThis demonstrates multiple job summaries in one run.\n\n- Item A\n- Item B\n"), + }, + } } resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{ diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 2f4f1950ec..34f8ae4475 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -294,6 +294,7 @@ type ViewResponse struct { // or "/owner/repo/actions/runs/123/attempts/2" for a historical attempt. // Use this when the target should reflect the currently-viewed attempt. ViewLink string `json:"viewLink"` + Index int64 `json:"index"` // the per-repository run number, displayed as "#N" Title string `json:"title"` TitleHTML template.HTML `json:"titleHTML"` Status string `json:"status"` @@ -561,6 +562,7 @@ func fillViewRunResponseSummary(ctx *context_module.Context, resp *ViewResponse, isLatestAttempt := run.LatestAttemptID == 0 || (attempt != nil && attempt.ID == run.LatestAttemptID) resp.State.Run.RepoID = ctx.Repo.Repository.ID + resp.State.Run.Index = run.Index // the title for the "run" is from the commit message resp.State.Run.Title = run.Title resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository) diff --git a/web_src/js/components/ActionRunView.ts b/web_src/js/components/ActionRunView.ts index f6e3977086..bc1c2b736e 100644 --- a/web_src/js/components/ActionRunView.ts +++ b/web_src/js/components/ActionRunView.ts @@ -107,6 +107,7 @@ export function buildJobsByParentJobID(jobs: ActionsJob[]): Map {

+ #{{ run.index }}
- {{if and .AllowDisableOrEnableWorkflow .CurWorkflowIsListed $.CurWorkflow}} + {{if or .WorkflowBadge (and .AllowDisableOrEnableWorkflow .CurWorkflowIsListed $.CurWorkflow)}} {{end}} @@ -126,6 +133,49 @@
{{template "repo/actions/runs_list" .}}
+ {{if .WorkflowBadge}} + + {{end}} {{else}} diff --git a/web_src/js/features/repo-actions.test.ts b/web_src/js/features/repo-actions.test.ts new file mode 100644 index 0000000000..bcb2063fb7 --- /dev/null +++ b/web_src/js/features/repo-actions.test.ts @@ -0,0 +1,31 @@ +import {updateWorkflowBadgeFields} from './repo-actions.ts'; + +test('updateWorkflowBadgeFields updates badge snippets for selected branch', () => { + document.body.innerHTML = ` +
+ + + + +
+ `; + + const form = document.querySelector('[data-badge-url]')!; + + updateWorkflowBadgeFields(form, 'release/1.0 & hotfix'); + + const badgeURL = 'https://gitea.example.com/user1/repo1/actions/workflows/build/test%20workflow.yml/badge.svg?branch=release%2F1.0+%26+hotfix'; + expect(form.querySelector('[data-workflow-badge-image]')!.src).toBe(badgeURL); + expect(form.querySelector('#workflow-badge-url')!.value).toBe(badgeURL); + expect(form.querySelector('#workflow-badge-markdown')!.value).toBe( + `[![CI \\[prod\\]\\\\build "fast" ](${badgeURL})](https://gitea.example.com/user1/repo1/actions?workflow=build%2Ftest+workflow.yml)`, + ); + expect(form.querySelector('#workflow-badge-html')!.value).toBe( + `CI [prod]\\build "fast" <ok>`, + ); +}); diff --git a/web_src/js/features/repo-actions.ts b/web_src/js/features/repo-actions.ts index 3d4bd49a4f..f710619712 100644 --- a/web_src/js/features/repo-actions.ts +++ b/web_src/js/features/repo-actions.ts @@ -1,7 +1,36 @@ import {createApp} from 'vue'; import RepoActionView from '../components/RepoActionView.vue'; +import {queryElems} from '../utils/dom.ts'; + +function escapeHTMLAttribute(value: string): string { + return value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>'); +} + +export function updateWorkflowBadgeFields(form: HTMLElement, branch: string): void { + const badgeURL = new URL(form.getAttribute('data-badge-url')!); + badgeURL.searchParams.set('branch', branch); + + const badgeURLString = badgeURL.href; + const workflowURL = form.getAttribute('data-workflow-url')!; + const markdownAltText = form.getAttribute('data-markdown-alt-text')!; + const htmlAltText = form.getAttribute('data-html-alt-text')!; + + form.querySelector('[data-workflow-badge-image]')!.src = badgeURLString; + form.querySelector('#workflow-badge-url')!.value = badgeURLString; + form.querySelector('#workflow-badge-markdown')!.value = `[![${markdownAltText}](${badgeURLString})](${workflowURL})`; + form.querySelector('#workflow-badge-html')!.value = `${escapeHTMLAttribute(htmlAltText)}`; +} + +function initWorkflowBadgeBranchSelection(): void { + queryElems(document, '[data-workflow-badge-form]', (form) => { + const branchInput = form.querySelector('[data-workflow-badge-branch]')!; + branchInput.addEventListener('change', () => updateWorkflowBadgeFields(form, branchInput.value)); + }); +} export function initRepositoryActionView() { + initWorkflowBadgeBranchSelection(); + const el = document.querySelector('#repo-action-view'); if (!el) return; -- 2.54.0 From 0319358e5eaf39aa38f53a6160ddfaf2858aba54 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 28 Jun 2026 07:58:29 +0200 Subject: [PATCH 034/169] fix(web): Correctly align the "disabled" label on larger workflow names (#38240) --- templates/repo/actions/list.tmpl | 6 +++--- web_src/css/base.css | 1 + web_src/css/modules/menu.css | 21 --------------------- 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 472953a34d..2aac08d61b 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -11,14 +11,14 @@ {{ctx.Locale.Tr "actions.runs.all_workflows"}} {{range .workflows}} - {{.DisplayName}} + {{.DisplayName}} {{if .ErrMsg}} - {{svg "octicon-alert" 16 "tw-text-red"}} + {{svg "octicon-alert" 16 "tw-text-red"}} {{end}} {{if $.ActionsConfig.IsWorkflowDisabled .Entry.Name}} -
{{ctx.Locale.Tr "disabled"}}
+
{{ctx.Locale.Tr "disabled"}}
{{end}}
{{end}} diff --git a/web_src/css/base.css b/web_src/css/base.css index 92b82e9d89..64da8fa9c1 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -863,6 +863,7 @@ table th[data-sortt-desc] .svg { .ui.list.flex-items-block > .item, .ui.vertical.menu.flex-items-block > .item, +.ui.vertical.menu .item.flex-text-block, .ui.form .field > label.flex-text-block, /* override fomantic "block" style */ .flex-items-block > .item, .flex-text-block { diff --git a/web_src/css/modules/menu.css b/web_src/css/modules/menu.css index 458d47fad0..abdce8785b 100644 --- a/web_src/css/modules/menu.css +++ b/web_src/css/modules/menu.css @@ -173,17 +173,6 @@ margin-top: 0.35714286em; } -.ui.menu .item > .label:not(.floating) { - margin-left: 1em; - padding: 0.3em 0.78571429em; -} -.ui.vertical.menu .item > .label { - margin-top: -0.15em; - margin-bottom: -0.15em; - padding: 0.3em 0.78571429em; - float: right; - text-align: center; -} .ui.menu .item > .label { background: var(--color-label-bg); color: var(--color-label-text); @@ -285,16 +274,6 @@ border-radius: 0 0 0.28571429rem 0.28571429rem; } -.ui.vertical.menu .item > i.icon { - width: 1.18em; - float: right; - margin: 0 0 0 0.5em; -} -.ui.vertical.menu .item > .label + i.icon { - float: none; - margin: 0 0.5em 0 0; -} - .ui.vertical.menu .item::before { position: absolute; content: ""; -- 2.54.0 From c9920b7bd0f6ec1f7590f104711b09d55917f9e8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 28 Jun 2026 01:06:33 -0700 Subject: [PATCH 035/169] fix(oauth): restrict introspection to the token's client (#38042) Bind OAuth token introspection responses to the authenticated client. Return an inactive response when the token grant belongs to a different OAuth application to avoid leaking token metadata across clients. Add integration coverage for cross-client introspection attempts against both access tokens and refresh tokens. Assisted-by: GPT-5.4 --- routers/web/auth/oauth2_provider.go | 52 ++++++++++++++------ tests/integration/oauth_test.go | 76 +++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index 0ed20329b4..62a86bfc0c 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -128,7 +128,7 @@ func InfoOAuth(ctx *context.Context) { // IntrospectOAuth introspects an oauth token func IntrospectOAuth(ctx *context.Context) { - clientIDValid := false + var introspectingApp *auth.OAuth2Application authHeader := ctx.Req.Header.Get("Authorization") if parsed, ok := httpauth.ParseAuthorizationHeader(authHeader); ok && parsed.BasicAuth != nil { clientID, clientSecret := parsed.BasicAuth.Username, parsed.BasicAuth.Password @@ -139,9 +139,14 @@ func IntrospectOAuth(ctx *context.Context) { ctx.HTTPError(http.StatusInternalServerError) return } - clientIDValid = err == nil && app.ValidateClientSecret([]byte(clientSecret)) + clientIDValid := err == nil && app.ValidateClientSecret([]byte(clientSecret)) + if clientIDValid { + introspectingApp = app + } } - if !clientIDValid { + if introspectingApp == nil { + // RFC 7662 requires the caller to authenticate to the introspection endpoint. + // https://www.rfc-editor.org/rfc/rfc7662.html#section-2.1 ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea OAuth2"`) ctx.PlainText(http.StatusUnauthorized, "no valid authorization") return @@ -156,21 +161,36 @@ func IntrospectOAuth(ctx *context.Context) { form := web.GetForm(ctx).(*forms.IntrospectTokenForm) token, err := oauth2_provider.ParseToken(form.Token, oauth2_provider.DefaultSigningKey) - if err == nil { - grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) - if err == nil && grant != nil { - app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) - if err == nil && app != nil { - response.Active = true - response.Scope = grant.Scope - response.RegisteredClaims = oauth2_provider.NewJwtRegisteredClaimsFromUser(app.ClientID, grant.UserID, nil /*exp*/) - } - if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil { - response.Username = user.Name - } - } + if err != nil { + // RFC 7662 returns inactive token metadata for invalid/unknown tokens. + // https://www.rfc-editor.org/rfc/rfc7662.html#section-2.2 + log.Trace("Ignoring invalid token during introspection: %v", err) + ctx.JSON(http.StatusOK, response) + return } + grant, err := auth.GetOAuth2GrantByID(ctx, token.GrantID) + if err != nil { + ctx.ServerError("GetOAuth2GrantByID", err) + return + } + if grant == nil || grant.ApplicationID != introspectingApp.ID { + // RFC 7662 allows the server to reply inactive when the caller must not learn more. + // https://www.rfc-editor.org/rfc/rfc7662.html#section-2.2 + ctx.JSON(http.StatusOK, response) + return + } + + response.Active = true + response.Scope = grant.Scope + response.RegisteredClaims = oauth2_provider.NewJwtRegisteredClaimsFromUser(introspectingApp.ClientID, grant.UserID, nil /*exp*/) + user, err := user_model.GetUserByID(ctx, grant.UserID) + if err != nil { + ctx.ServerError("GetUserByID", err) + return + } + response.Username = user.Name + ctx.JSON(http.StatusOK, response) } diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index ee9eb838a3..b42860763a 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -14,6 +14,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "strconv" "strings" "testing" @@ -33,6 +34,7 @@ import ( "gitea.dev/tests" "github.com/PuerkitoBio/goquery" + jwt "github.com/golang-jwt/jwt/v5" "github.com/markbates/goth" "github.com/markbates/goth/gothic" "github.com/stretchr/testify/assert" @@ -122,6 +124,7 @@ func TestOAuth2(t *testing.T) { t.Run("RefreshTokenInvalidation", testRefreshTokenInvalidation) t.Run("RefreshTokenCrossClientUsage", testRefreshTokenCrossClientUsage) t.Run("OAuthIntrospection", testOAuthIntrospection) + t.Run("OAuthIntrospectionCrossClientIsolation", testOAuthIntrospectionCrossClientIsolation) t.Run("OAuthGrantScopesReadUserFailRepos", testOAuthGrantScopesReadUserFailRepos) t.Run("OAuthGrantScopesBasicRespectsWriteUser", testOAuthGrantScopesBasicRespectsWriteUser) t.Run("OAuthGrantScopesReadRepositoryFailOrganization", testOAuthGrantScopesReadRepositoryFailOrganization) @@ -705,6 +708,79 @@ func testOAuthIntrospection(t *testing.T) { assert.Contains(t, resp.Body.String(), "no valid authorization") } +func testOAuthIntrospectionCrossClientIsolation(t *testing.T) { + resourceOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + clientA := createOAuthTestApplication(t, "user1", "introspection-primary-client", []string{"https://primary.example/oauth/callback"}) + clientB := createOAuthTestApplication(t, "user2", "introspection-secondary-client", []string{"https://secondary.example/oauth/callback"}) + code, verifier := issueOAuthAuthorizationCode(t, resourceOwner, clientA, clientA.RedirectURIs[0], "openid profile") + + req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{ + "grant_type": "authorization_code", + "client_id": clientA.ClientID, + "client_secret": clientA.ClientSecret, + "redirect_uri": clientA.RedirectURIs[0], + "code": code, + "code_verifier": verifier, + }) + resp := MakeRequest(t, req, http.StatusOK) + type tokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + } + tokenParsed := new(tokenResponse) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), tokenParsed)) + require.NotEmpty(t, tokenParsed.AccessToken) + require.NotEmpty(t, tokenParsed.RefreshToken) + + type introspectResponse struct { + Active bool `json:"active"` + Scope string `json:"scope,omitempty"` + Username string `json:"username,omitempty"` + jwt.RegisteredClaims + } + + assertBlockedIntrospection := func(token string) { + t.Helper() + + req = NewRequestWithValues(t, "POST", "/login/oauth/introspect", map[string]string{ + "token": token, + }) + req.SetBasicAuth(clientB.ClientID, clientB.ClientSecret) + resp = MakeRequest(t, req, http.StatusOK) + + blocked := new(introspectResponse) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), blocked)) + assert.False(t, blocked.Active) + assert.Empty(t, blocked.Scope) + assert.Empty(t, blocked.Username) + assert.Empty(t, blocked.Subject) + assert.Empty(t, blocked.Audience) + } + + assertAllowedIntrospection := func(token string) { + t.Helper() + + req = NewRequestWithValues(t, "POST", "/login/oauth/introspect", map[string]string{ + "token": token, + }) + req.SetBasicAuth(clientA.ClientID, clientA.ClientSecret) + resp = MakeRequest(t, req, http.StatusOK) + + allowed := new(introspectResponse) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), allowed)) + assert.True(t, allowed.Active) + assert.Equal(t, "openid profile", allowed.Scope) + assert.Equal(t, resourceOwner.Name, allowed.Username) + assert.Equal(t, strconv.FormatInt(resourceOwner.ID, 10), allowed.Subject) + assert.Equal(t, jwt.ClaimStrings{clientA.ClientID}, allowed.Audience) + } + + assertBlockedIntrospection(tokenParsed.AccessToken) + assertAllowedIntrospection(tokenParsed.AccessToken) + assertBlockedIntrospection(tokenParsed.RefreshToken) + assertAllowedIntrospection(tokenParsed.RefreshToken) +} + func testOAuthGrantScopesReadUserFailRepos(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) accessToken := issueOAuthAccessTokenForScope(t, user, "openid read:user") -- 2.54.0 From f46c9a97698f4a9eac8ac7b16679c4949a271d43 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 28 Jun 2026 03:31:35 -0600 Subject: [PATCH 036/169] feat(actions): support owner-level and global scoped workflows (#38154) ## Summary This PR adds **scoped workflows** to Gitea Actions. Workflows defined centrally in a "source" repository that automatically run on every repository in scope: an organization's repositories, or (for instance admins) every repository on the instance. Each scoped run executes in the consuming repository's own context (its runners, secrets, and branch) while its content is read from the source repository, so an org or instance can mandate shared CI across many repositories without copying workflow files into each one. An owner or instance admin registers source repositories on a settings page and can mark individual workflows as **required**. A required scoped workflow cannot be opted out by a consuming repository and gates its pull-request merges; an optional one can be disabled per repository. Scoped workflows live under a dedicated `SCOPED_WORKFLOW_DIRS` (default `.gitea/scoped_workflows`), kept separate from regular `WORKFLOW_DIRS`. ## Main changes ### Configuration New `SCOPED_WORKFLOW_DIRS` setting, validated to not overlap with `WORKFLOW_DIRS`. Default: `.gitea/scoped_workflows` ### Data model & migration - New `action_scoped_workflow_source` table mapping a registering owner (`owner_id`, where `0` = instance-level) to a source repository, with a per-workflow `WorkflowConfigs` map. - `ActionRun` gains `WorkflowRepoID` / `WorkflowCommitSHA` (the pinned content source) and an `IsScopedRun` flag. ### Detection & run creation On consumer events, scoped workflows from the effective sources (the owner's own sources plus instance-level ones) are matched and turned into runs that execute in the consumer's context, with content pinned to the source repo's default-branch commit. `on: workflow_run` and `on: schedule` are currently not supported. ### Opt-out A consuming repository can disable an optional scoped workflow (tracked separately from regular `DisabledWorkflows`); required scoped workflows can never be disabled, opted out, or bypassed. ### Commit status A scoped run's status context format is `": / ()"` (for example: `my-org/scoped-workflows: db-tests / test-sqlite (pull_request)`), keeping it distinct from a same-named repo-level workflow and from other sources. ### Required status checks Admins mark workflows required and supply status-check patterns. `EffectiveRequiredContexts` appends those patterns to the branch protection's required contexts and they are matched must-present-and-pass. If the status checks from scoped workflows fail, the PR cannot be merged. NOTE: scoped workflows' required status checks patterns can protect any target branch that has a protection rule, even though the rule's "Status Check" is disabled. A target branch with no protection rule cannot be protected.
Screenshots image
### Reusable workflows (`uses:`) A scoped workflow's local `uses: ./...` resolves against the source repository. `uses:` directory validation honors the instance-configurable `WORKFLOW_DIRS` and `SCOPED_WORKFLOW_DIRS` (previously hardcoded to `.gitea`/`.github/workflows`). ### Manual dispatch `workflow_dispatch` is supported for scoped workflows (web and API), resolving inputs/content from the source repo. ### Performance A process-local LRU cache keyed by source repo ID for the per-source workflow parse, so instance-level and owner-level sources don't open the source repo and parse workflow files on every event. ### UI Org / user / admin pages to register and remove sources, search repositories, and mark workflows required with their status-check patterns. The repository Actions sidebar groups scoped workflows by source with owner/instance labels and required/disabled badges.
Screenshots Scoped workflows setting page: image Consumer repo's Actions runs list: image - `Owner`: this is a owner-level scoped workflows source repo - `Global`: this is a global scoped workflows source repo - `Required`: this scoped workflow is required, repo admin cannot disable it
--- Docs: https://gitea.com/gitea/docs/pulls/447 --------- Co-authored-by: bircni --- custom/conf/app.example.ini | 4 + models/actions/run.go | 19 +- models/actions/run_list.go | 24 +- models/actions/run_list_test.go | 125 +++++ models/actions/run_test.go | 55 ++ models/actions/scoped_workflow.go | 179 +++++++ models/actions/scoped_workflow_test.go | 139 +++++ models/migrations/migrations.go | 1 + models/migrations/v1_27/v342.go | 42 ++ models/repo/repo_unit_actions.go | 25 + models/repo/repo_unit_actions_test.go | 51 ++ modules/actions/jobparser/uses.go | 30 +- modules/actions/jobparser/uses_test.go | 27 +- modules/actions/scoped_workflows.go | 83 +++ modules/actions/scoped_workflows_test.go | 62 +++ modules/actions/workflows.go | 53 +- modules/setting/actions.go | 39 +- modules/setting/actions_test.go | 71 +++ options/locale/locale_en-US.json | 27 + routers/api/v1/repo/action.go | 33 +- routers/api/v1/shared/action.go | 5 + routers/web/repo/actions/actions.go | 217 +++++++- routers/web/repo/actions/view.go | 150 +++++- routers/web/repo/issue_view.go | 4 +- routers/web/repo/pull.go | 26 +- routers/web/repo/pull_merge_box.go | 2 +- .../web/shared/actions/scoped_workflows.go | 368 +++++++++++++ .../shared/actions/scoped_workflows_test.go | 75 +++ routers/web/web.go | 12 + services/actions/commit_status.go | 28 +- services/actions/commit_status_test.go | 77 ++- services/actions/context_test.go | 22 +- services/actions/notifier.go | 19 +- services/actions/notifier_helper.go | 207 ++++++-- services/actions/rerun.go | 11 +- services/actions/reusable_workflow.go | 16 +- services/actions/reusable_workflow_test.go | 30 ++ services/actions/run.go | 8 +- services/actions/schedule_tasks.go | 3 + services/actions/scoped_workflow_cache.go | 84 +++ services/actions/workflow.go | 107 +++- services/convert/convert.go | 60 +++ services/org/org.go | 1 + services/pull/commit_status.go | 75 ++- services/pull/commit_status_test.go | 64 +++ services/repository/delete.go | 1 + services/user/delete.go | 1 + services/webhook/notifier.go | 20 +- templates/admin/actions.tmpl | 3 + templates/admin/navbar.tmpl | 5 +- templates/org/settings/actions.tmpl | 2 + templates/org/settings/navbar.tmpl | 5 +- templates/repo/actions/list.tmpl | 40 +- templates/repo/actions/view_component.tmpl | 1 + templates/repo/actions/workflow_dispatch.tmpl | 4 +- .../shared/actions/scoped_workflows.tmpl | 88 ++++ templates/swagger/v1_json.tmpl | 14 + templates/swagger/v1_openapi3_json.tmpl | 18 + templates/user/settings/actions.tmpl | 2 + templates/user/settings/navbar.tmpl | 5 +- .../actions_scoped_workflow_test.go | 495 ++++++++++++++++++ web_src/css/actions.css | 25 + .../js/components/ActionRunSummaryView.vue | 2 +- web_src/js/components/ActionRunView.ts | 1 + web_src/js/components/RepoActionView.vue | 6 +- web_src/js/features/common-page.ts | 2 + .../js/features/comp/ScopedWorkflows.test.ts | 104 ++++ web_src/js/features/comp/ScopedWorkflows.ts | 38 ++ web_src/js/features/comp/SearchRepoBox.ts | 4 +- web_src/js/features/repo-actions.ts | 1 + web_src/js/modules/gitea-actions.ts | 1 + 71 files changed, 3399 insertions(+), 249 deletions(-) create mode 100644 models/actions/scoped_workflow.go create mode 100644 models/actions/scoped_workflow_test.go create mode 100644 models/migrations/v1_27/v342.go create mode 100644 models/repo/repo_unit_actions_test.go create mode 100644 modules/actions/scoped_workflows.go create mode 100644 modules/actions/scoped_workflows_test.go create mode 100644 routers/web/shared/actions/scoped_workflows.go create mode 100644 routers/web/shared/actions/scoped_workflows_test.go create mode 100644 services/actions/scoped_workflow_cache.go create mode 100644 templates/shared/actions/scoped_workflows.tmpl create mode 100644 tests/integration/actions_scoped_workflow_test.go create mode 100644 web_src/js/features/comp/ScopedWorkflows.test.ts create mode 100644 web_src/js/features/comp/ScopedWorkflows.ts diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 548c39d4b6..e5451af76a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -3002,6 +3002,10 @@ LEVEL = Info ;; Comma-separated list of workflow directories, the first one to exist ;; in a repo is used to find Actions workflow files ;WORKFLOW_DIRS = .gitea/workflows,.github/workflows +;; Comma-separated list of scoped workflow directories in a source repository, the first one to exist is used. +;; Files here are picked up only when the repo is registered as a scoped-workflow source; in any other repo they neither run repo-level nor scope-level. +;; Must not overlap with WORKFLOW_DIRS. Leave empty so no directory is scanned; no scoped workflows are found or run. +;SCOPED_WORKFLOW_DIRS = .gitea/scoped_workflows ;; Maximum number of attempts a single workflow run can have. Default value is 50. ;MAX_RERUN_ATTEMPTS = 50 diff --git a/models/actions/run.go b/models/actions/run.go index 8d6d804340..79b7145935 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "net/url" "strings" "time" @@ -50,6 +51,13 @@ type ActionRun struct { Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed RawConcurrency string // raw concurrency + // WorkflowRepoID/WorkflowCommitSHA record the (repo, commit) the run's workflow file content came from. + // Always filled (repo-level run = the repo itself; scoped run = the source repo). + WorkflowRepoID int64 `xorm:"NOT NULL DEFAULT 0"` + WorkflowCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"` + + IsScopedRun bool `xorm:"NOT NULL DEFAULT false"` // IsScopedRun explicitly classifies scoped runs. + // Started and Stopped are identical to the latest attempt after ActionRunAttempt was introduced. // When a rerun creates a new latest attempt, they are reset until the new attempt starts and stops. Started timeutil.TimeStamp @@ -88,7 +96,11 @@ func (run *ActionRun) WorkflowLink() string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID) + // A scoped run's workflow is disambiguated by its source repo, so carry scoped_workflow_source_repo_id back to the run list + if run.IsScopedRun { + return fmt.Sprintf("%s/actions/?workflow=%s&scoped_workflow_source_repo_id=%d", run.Repo.Link(), url.QueryEscape(run.WorkflowID), run.WorkflowRepoID) + } + return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), url.QueryEscape(run.WorkflowID)) } // RefLink return the url of run's ref @@ -291,7 +303,10 @@ func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branc var run ActionRun q := db.GetEngine(ctx).Where("repo_id=?", repoID). And("ref = ?", branch). - And("workflow_id = ?", workflowFile) + And("workflow_id = ?", workflowFile). + // TODO: the badge only reflects the repo's own (repo-level) runs; a same-named scoped run must not leak in. + // Support a scoped-workflow badge later by making this source-aware. + And("is_scoped_run = ?", false) if event != "" { q.And("event = ?", event) } diff --git a/models/actions/run_list.go b/models/actions/run_list.go index e07b30c265..6f836d3e11 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" "gitea.dev/modules/container" + "gitea.dev/modules/optional" "gitea.dev/modules/translation" webhook_module "gitea.dev/modules/webhook" @@ -61,7 +62,9 @@ type FindRunOptions struct { RepoID int64 OwnerID int64 WorkflowID string - Ref string // the commit/tag/… that caused this workflow + WorkflowRepoID int64 // source-aware filter: the repo a run's workflow content came from (0 = any) + IsScopedRun optional.Option[bool] // is the run from a scoped workflow + Ref string // the commit/tag/… that caused this workflow TriggerUserID int64 TriggerEvent webhook_module.HookEventType Status []Status @@ -77,6 +80,12 @@ func (opts FindRunOptions) ToConds() builder.Cond { if opts.WorkflowID != "" { cond = cond.And(builder.Eq{"`action_run`.workflow_id": opts.WorkflowID}) } + if opts.WorkflowRepoID > 0 { + cond = cond.And(builder.Eq{"`action_run`.workflow_repo_id": opts.WorkflowRepoID}) + } + if opts.IsScopedRun.Has() { + cond = cond.And(builder.Eq{"`action_run`.is_scoped_run": opts.IsScopedRun.Value()}) + } if opts.TriggerUserID > 0 { cond = cond.And(builder.Eq{"`action_run`.trigger_user_id": opts.TriggerUserID}) } @@ -156,9 +165,20 @@ func GetRunBranches(ctx context.Context, repoID int64) ([]string, error) { // GetRunWorkflowIDs returns all distinct WorkflowIDs that have at least // one ActionRun in the given repo. func GetRunWorkflowIDs(ctx context.Context, repoID int64) ([]string, error) { + return getRunWorkflowIDs(ctx, repoID, builder.NewCond()) +} + +// GetRepoRunWorkflowIDs returns all distinct WorkflowIDs that have at least +// one repo-level ActionRun in the given repo. +func GetRepoRunWorkflowIDs(ctx context.Context, repoID int64) ([]string, error) { + return getRunWorkflowIDs(ctx, repoID, builder.Eq{"is_scoped_run": false}) +} + +func getRunWorkflowIDs(ctx context.Context, repoID int64, extraCond builder.Cond) ([]string, error) { ids := make([]string, 0, 10) + cond := builder.Eq{"repo_id": repoID} return ids, db.GetEngine(ctx).Table("action_run"). - Where(builder.Eq{"repo_id": repoID}). + Where(cond.And(extraCond)). Distinct("workflow_id"). Cols("workflow_id"). Asc("workflow_id"). diff --git a/models/actions/run_list_test.go b/models/actions/run_list_test.go index 74f630bb55..556896d107 100644 --- a/models/actions/run_list_test.go +++ b/models/actions/run_list_test.go @@ -6,10 +6,13 @@ package actions import ( "testing" + "gitea.dev/models/db" "gitea.dev/models/unittest" + "gitea.dev/modules/optional" "gitea.dev/modules/translation" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetRunWorkflowIDs(t *testing.T) { @@ -24,6 +27,46 @@ func TestGetRunWorkflowIDs(t *testing.T) { assert.Empty(t, ids) } +func TestGetRepoRunWorkflowIDs(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + const ( + repoID = int64(4) + repoWorkflowID = "repo-orphan.yaml" + scopedWorkflowID = "scoped-only.yaml" + sharedWorkflowID = "shared-name.yaml" + scopedWorkflowRepo = int64(111) + ) + for _, spec := range []struct { + id int64 + workflowID string + workflowRepoID int64 + isScopedRun bool + }{ + {99811, repoWorkflowID, repoID, false}, + {99812, scopedWorkflowID, scopedWorkflowRepo, true}, + {99813, sharedWorkflowID, repoID, false}, + {99814, sharedWorkflowID, scopedWorkflowRepo, true}, + } { + require.NoError(t, db.Insert(t.Context(), &ActionRun{ + ID: spec.id, + Index: spec.id, + RepoID: repoID, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: spec.workflowID, + WorkflowRepoID: spec.workflowRepoID, + IsScopedRun: spec.isScopedRun, + })) + } + + ids, err := GetRepoRunWorkflowIDs(t.Context(), repoID) + require.NoError(t, err) + assert.Contains(t, ids, repoWorkflowID) + assert.Contains(t, ids, sharedWorkflowID) + assert.NotContains(t, ids, scopedWorkflowID) +} + func TestGetStatusInfoList(t *testing.T) { statusInfoList := GetStatusInfoList(t.Context(), translation.MockLocale{}) @@ -35,3 +78,85 @@ func TestGetStatusInfoList(t *testing.T) { {Status: int(StatusCancelling), StatusName: StatusCancelling.String(), DisplayedStatus: "actions.status.cancelling"}, }, statusInfoList) } + +// TestFindRunOptions_WorkflowRepoID: two runs share the bare WorkflowID but come from different content-source repos; +// the source-aware WorkflowRepoID filter must separate them. +func TestFindRunOptions_WorkflowRepoID(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + const ( + repoID = int64(4) + sourceA = int64(111) + sourceB = int64(222) + workflowID = "u3-shared.yaml" + ) + for _, spec := range []struct{ id, workflowRepoID int64 }{ + {99801, sourceA}, + {99802, sourceB}, + } { + require.NoError(t, db.Insert(t.Context(), &ActionRun{ + ID: spec.id, + Index: spec.id, + RepoID: repoID, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: workflowID, + WorkflowRepoID: spec.workflowRepoID, + IsScopedRun: true, + })) + } + + // no source filter -> both + all, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID}) + require.NoError(t, err) + assert.Len(t, all, 2) + + // filter by source A -> only the run whose content came from A + onlyA, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, WorkflowRepoID: sourceA}) + require.NoError(t, err) + require.Len(t, onlyA, 1) + assert.EqualValues(t, 99801, onlyA[0].ID) + + // filter by source B -> only the run whose content came from B + onlyB, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, WorkflowRepoID: sourceB}) + require.NoError(t, err) + require.Len(t, onlyB, 1) + assert.EqualValues(t, 99802, onlyB[0].ID) +} + +func TestFindRunOptions_IsScopedRun(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + const ( + repoID = int64(4) + workflowID = "scoped-flag.yaml" + ) + for _, spec := range []struct { + id int64 + scoped bool + }{ + {99821, false}, + {99822, true}, + } { + require.NoError(t, db.Insert(t.Context(), &ActionRun{ + ID: spec.id, + Index: spec.id, + RepoID: repoID, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: workflowID, + WorkflowRepoID: repoID, + IsScopedRun: spec.scoped, + })) + } + + repoLevel, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, IsScopedRun: optional.Some(false)}) + require.NoError(t, err) + require.Len(t, repoLevel, 1) + assert.EqualValues(t, 99821, repoLevel[0].ID) + + scoped, err := db.Find[ActionRun](t.Context(), FindRunOptions{RepoID: repoID, WorkflowID: workflowID, IsScopedRun: optional.Some(true)}) + require.NoError(t, err) + require.Len(t, scoped, 1) + assert.EqualValues(t, 99822, scoped[0].ID) +} diff --git a/models/actions/run_test.go b/models/actions/run_test.go index 356ed28a74..08330cd999 100644 --- a/models/actions/run_test.go +++ b/models/actions/run_test.go @@ -13,6 +13,7 @@ import ( "gitea.dev/modules/timeutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestUpdateRepoRunsNumbers(t *testing.T) { @@ -44,3 +45,57 @@ func TestActionRun_Duration_NonNegative(t *testing.T) { } assert.Equal(t, time.Duration(0), run.Duration()) } + +func TestActionRun_WorkflowLink(t *testing.T) { + repo := &repo_model.Repository{OwnerName: "org", Name: "consumer"} + + // a repo-level run links by file name only + repoLevel := &ActionRun{Repo: repo, WorkflowID: "ci.yaml", WorkflowRepoID: repo.ID} + assert.Equal(t, repo.Link()+"/actions/?workflow=ci.yaml", repoLevel.WorkflowLink()) + + // a scoped run carries its source repo id back, so the list stays filtered to that source + scoped := &ActionRun{Repo: repo, WorkflowID: "ci.yaml", WorkflowRepoID: 42, IsScopedRun: true} + assert.Equal(t, repo.Link()+"/actions/?workflow=ci.yaml&scoped_workflow_source_repo_id=42", scoped.WorkflowLink()) +} + +func TestGetWorkflowLatestRun_RepoLevelOnly(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + const ( + repoID = int64(4) + workflowID = "badge-source-aware.yaml" + ref = "refs/heads/main" + ) + require.NoError(t, db.Insert(t.Context(), &ActionRun{ + ID: 99811, + Index: 99811, + RepoID: repoID, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: workflowID, + Ref: ref, + Event: "push", + Status: StatusSuccess, + WorkflowRepoID: repoID, + WorkflowCommitSHA: "repo-level-sha", + })) + require.NoError(t, db.Insert(t.Context(), &ActionRun{ + ID: 99812, + Index: 99812, + RepoID: repoID, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: workflowID, + Ref: ref, + Event: "push", + Status: StatusFailure, + WorkflowRepoID: 111, + WorkflowCommitSHA: "scoped-sha", + IsScopedRun: true, + })) + + run, err := GetWorkflowLatestRun(t.Context(), repoID, workflowID, ref, "push") + require.NoError(t, err) + assert.EqualValues(t, 99811, run.ID) + assert.False(t, run.IsScopedRun) +} diff --git a/models/actions/scoped_workflow.go b/models/actions/scoped_workflow.go new file mode 100644 index 0000000000..310e1a041d --- /dev/null +++ b/models/actions/scoped_workflow.go @@ -0,0 +1,179 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + "fmt" + + "gitea.dev/models/db" + repo_model "gitea.dev/models/repo" + "gitea.dev/modules/timeutil" + "gitea.dev/modules/util" + + "xorm.io/builder" +) + +// ActionScopedWorkflowSource registers a repository as a source of scoped workflows, either for an owner (user/org) or for the whole instance. +type ActionScopedWorkflowSource struct { + ID int64 `xorm:"pk autoincr"` + + // OwnerID is the scope the source applies to: a user/org ID (applies to that owner's repos), or 0 for instance-level (applies to every repo). + OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"` + // SourceRepoID is the source repository providing the workflow files; always non-zero. + SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"` + + // WorkflowConfigs maps a workflow ID (entry name) to its merge-gate config. + WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"` + + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` +} + +// ScopedWorkflowConfig is one scoped workflow's config within a source registration. +type ScopedWorkflowConfig struct { + Required bool `json:"required"` + Patterns []string `json:"patterns"` // the status-check patterns that must be present and pass, only effective when Required is true +} + +func init() { + db.RegisterModel(new(ActionScopedWorkflowSource)) +} + +// IsWorkflowRequired reports whether the given workflow ID (entry name) is marked required in this source. +func (s *ActionScopedWorkflowSource) IsWorkflowRequired(workflowID string) bool { + c, ok := s.WorkflowConfigs[workflowID] + return ok && c.Required +} + +type FindScopedWorkflowSourceOpts struct { + db.ListOptions + OwnerIDs []int64 + SourceRepoID int64 +} + +func (opts FindScopedWorkflowSourceOpts) ToConds() builder.Cond { + cond := builder.NewCond() + if len(opts.OwnerIDs) > 0 { + cond = cond.And(builder.In("owner_id", opts.OwnerIDs)) + } + if opts.SourceRepoID != 0 { + cond = cond.And(builder.Eq{"source_repo_id": opts.SourceRepoID}) + } + return cond +} + +// GetEffectiveScopedWorkflowSources returns the scoped-workflow sources effective for a repo owned by repoOwnerID: +// the owner's own sources plus instance-level (owner_id=0) sources. +func GetEffectiveScopedWorkflowSources(ctx context.Context, repoOwnerID int64) ([]*ActionScopedWorkflowSource, error) { + owners := []int64{0} + if repoOwnerID != 0 { + owners = append(owners, repoOwnerID) + } + return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners}) +} + +// IsScopedWorkflowSourceEffective reports whether sourceRepoID is a scoped-workflow source effective for a repo owned by repoOwnerID. +func IsScopedWorkflowSourceEffective(ctx context.Context, repoOwnerID, sourceRepoID int64) (bool, error) { + owners := []int64{0} + if repoOwnerID != 0 { + owners = append(owners, repoOwnerID) + } + return db.Exist[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: owners, SourceRepoID: sourceRepoID}.ToConds()) +} + +// IsWorkflowRequiredInSources reports whether workflowID from sourceRepoID is required by any of the given sources. +func IsWorkflowRequiredInSources(sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool { + for _, s := range sources { + if s.SourceRepoID == sourceRepoID && s.IsWorkflowRequired(workflowID) { + return true + } + } + return false +} + +// ScopedStatusContextPrefix returns the source-repo prefix that makes a scoped run's commit-status context distinct from same-named workflows. +func ScopedStatusContextPrefix(ctx context.Context, sourceRepoID int64) string { + if sourceRepo, err := repo_model.GetRepositoryByID(ctx, sourceRepoID); err == nil { + return sourceRepo.FullName() + } + return fmt.Sprintf("scoped:%d", sourceRepoID) +} + +// IsScopedWorkflowRequired reports whether workflowID from sourceRepoID is required for a repo owned by consumerOwnerID. +func IsScopedWorkflowRequired(ctx context.Context, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) { + sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID) + if err != nil { + return false, err + } + return IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID), nil +} + +// IsScopedWorkflowOptedOutloads the consumer's effective sources then calls ScopedWorkflowOptedOut +func IsScopedWorkflowOptedOut(ctx context.Context, cfg *repo_model.ActionsConfig, consumerOwnerID, sourceRepoID int64, workflowID string) (bool, error) { + if !cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID) { + return false, nil + } + sources, err := GetEffectiveScopedWorkflowSources(ctx, consumerOwnerID) + if err != nil { + return false, err + } + return ScopedWorkflowOptedOut(cfg, sources, sourceRepoID, workflowID), nil +} + +// ScopedWorkflowOptedOut reports whether a consumer's opt-out of (sourceRepoID, workflowID) is in effect. +func ScopedWorkflowOptedOut(cfg *repo_model.ActionsConfig, sources []*ActionScopedWorkflowSource, sourceRepoID int64, workflowID string) bool { + return !IsWorkflowRequiredInSources(sources, sourceRepoID, workflowID) && cfg.IsScopedWorkflowDisabled(sourceRepoID, workflowID) +} + +// GetScopedWorkflowSourcesByOwner returns the sources an owner (user/org, or 0 for instance) registered. +func GetScopedWorkflowSourcesByOwner(ctx context.Context, ownerID int64) ([]*ActionScopedWorkflowSource, error) { + return db.Find[ActionScopedWorkflowSource](ctx, FindScopedWorkflowSourceOpts{OwnerIDs: []int64{ownerID}}) +} + +// GetScopedWorkflowSource returns the (owner, repo) source registration or a NotExist error. +func GetScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) (*ActionScopedWorkflowSource, error) { + src := &ActionScopedWorkflowSource{} + has, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Get(src) + if err != nil { + return nil, err + } + if !has { + return nil, util.NewNotExistErrorf("scoped workflow source (owner %d, repo %d) does not exist", ownerID, repoID) + } + return src, nil +} + +// AddScopedWorkflowSource registers repoID as a source for ownerID (no-op if already registered). +func AddScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error { + exists, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource)) + if err != nil { + return err + } + if exists { + return nil + } + if err := db.Insert(ctx, &ActionScopedWorkflowSource{OwnerID: ownerID, SourceRepoID: repoID}); err != nil { + // Re-check and treat an already-present row as the intended no-op. + if exists, existErr := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Exist(new(ActionScopedWorkflowSource)); existErr == nil && exists { + return nil + } + return err + } + return nil +} + +// SetScopedWorkflowSourceConfigs replaces the per-workflow merge-gate configs (workflow ID -> config). +func SetScopedWorkflowSourceConfigs(ctx context.Context, ownerID, repoID int64, configs map[string]*ScopedWorkflowConfig) error { + _, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID). + Cols("workflow_configs"). + Update(&ActionScopedWorkflowSource{WorkflowConfigs: configs}) + return err +} + +// RemoveScopedWorkflowSource removes the (owner, repo) source registration. +func RemoveScopedWorkflowSource(ctx context.Context, ownerID, repoID int64) error { + _, err := db.GetEngine(ctx).Where("owner_id = ? AND source_repo_id = ?", ownerID, repoID).Delete(new(ActionScopedWorkflowSource)) + return err +} diff --git a/models/actions/scoped_workflow_test.go b/models/actions/scoped_workflow_test.go new file mode 100644 index 0000000000..f3b90f6cd1 --- /dev/null +++ b/models/actions/scoped_workflow_test.go @@ -0,0 +1,139 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "gitea.dev/models/db" + "gitea.dev/models/unittest" + "gitea.dev/modules/util" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScopedWorkflowSource_IsWorkflowRequired(t *testing.T) { + src := &ActionScopedWorkflowSource{WorkflowConfigs: map[string]*ScopedWorkflowConfig{ + "a.yml": {Required: true, Patterns: []string{"p"}}, + "b.yml": {Required: true, Patterns: []string{"p"}}, + "c.yml": {Required: false, Patterns: []string{"p"}}, // patterns kept as history, not required + }} + assert.True(t, src.IsWorkflowRequired("a.yml")) + assert.True(t, src.IsWorkflowRequired("b.yml")) + assert.False(t, src.IsWorkflowRequired("c.yml"), "config kept as history but not required") + assert.False(t, src.IsWorkflowRequired("d.yml")) + + empty := &ActionScopedWorkflowSource{} + assert.False(t, empty.IsWorkflowRequired("a.yml")) +} + +func TestIsWorkflowRequiredInSources(t *testing.T) { + // repo 100 registered twice (org optional + instance required). + sources := []*ActionScopedWorkflowSource{ + {OwnerID: 2, SourceRepoID: 100, WorkflowConfigs: nil}, + {OwnerID: 0, SourceRepoID: 100, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"a.yml": {Required: true, Patterns: []string{"p"}}}}, + {OwnerID: 0, SourceRepoID: 200, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"b.yml": {Required: true, Patterns: []string{"p"}}}}, + } + + assert.True(t, IsWorkflowRequiredInSources(sources, 100, "a.yml"), "required at instance level wins over org optional") + assert.False(t, IsWorkflowRequiredInSources(sources, 100, "z.yml")) + assert.False(t, IsWorkflowRequiredInSources(sources, 200, "a.yml"), "a.yml is required for repo 100, not repo 200") + assert.True(t, IsWorkflowRequiredInSources(sources, 200, "b.yml")) + assert.False(t, IsWorkflowRequiredInSources(sources, 999, "a.yml"), "unknown source repo") +} + +func TestGetEffectiveScopedWorkflowSources(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + + rows := []*ActionScopedWorkflowSource{ + {OwnerID: 2, SourceRepoID: 100, WorkflowConfigs: nil}, // org 2 registers repo 100 (optional) + {OwnerID: 0, SourceRepoID: 100, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"a.yml": {Required: true, Patterns: []string{"p"}}}}, // instance also registers repo 100 (required) + {OwnerID: 0, SourceRepoID: 200, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"b.yml": {Required: true, Patterns: []string{"p"}}}}, // instance source 200 + {OwnerID: 3, SourceRepoID: 300, WorkflowConfigs: map[string]*ScopedWorkflowConfig{"c.yml": {Required: true, Patterns: []string{"p"}}}}, // a different owner's source + } + for _, r := range rows { + require.NoError(t, db.Insert(ctx, r)) + } + + // owner 2 sees its own sources plus instance-level ones, but not owner 3's. + owner2, err := GetEffectiveScopedWorkflowSources(ctx, 2) + require.NoError(t, err) + assert.Len(t, owner2, 3) + + required, err := IsScopedWorkflowRequired(ctx, 2, 100, "a.yml") + require.NoError(t, err) + assert.True(t, required, "instance marks a.yml required → required for owner 2 even though org left it optional") + + required, err = IsScopedWorkflowRequired(ctx, 2, 100, "x.yml") + require.NoError(t, err) + assert.False(t, required) + + required, err = IsScopedWorkflowRequired(ctx, 2, 200, "b.yml") + require.NoError(t, err) + assert.True(t, required) + + // owner 3's source must not be effective for owner 2. + required, err = IsScopedWorkflowRequired(ctx, 2, 300, "c.yml") + require.NoError(t, err) + assert.False(t, required) + + // IsScopedWorkflowSourceEffective: owner-level and instance-level sources are effective; another owner's is not. + effective, err := IsScopedWorkflowSourceEffective(ctx, 2, 100) + require.NoError(t, err) + assert.True(t, effective, "owner 2's own source") + + effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 200) + require.NoError(t, err) + assert.True(t, effective, "instance-level source is effective for any owner") + + effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 300) + require.NoError(t, err) + assert.False(t, effective, "owner 3's source is not effective for owner 2") + + effective, err = IsScopedWorkflowSourceEffective(ctx, 2, 999) + require.NoError(t, err) + assert.False(t, effective, "unknown source repo") + + effective, err = IsScopedWorkflowSourceEffective(ctx, 3, 300) + require.NoError(t, err) + assert.True(t, effective, "owner 3's own source is effective for owner 3") +} + +func TestScopedWorkflowSourceCRUD(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + + // add is idempotent + require.NoError(t, AddScopedWorkflowSource(ctx, 5, 10)) + require.NoError(t, AddScopedWorkflowSource(ctx, 5, 10)) + sources, err := GetScopedWorkflowSourcesByOwner(ctx, 5) + require.NoError(t, err) + assert.Len(t, sources, 1) + + // set the per-workflow configs (entry name -> {required, patterns}); a.yml required, b.yml kept as history (not required) + configs := map[string]*ScopedWorkflowConfig{ + "a.yml": {Required: true, Patterns: []string{"src: a.yml / *"}}, + "b.yml": {Required: false, Patterns: []string{"src: b.yml / build (push)"}}, + } + require.NoError(t, SetScopedWorkflowSourceConfigs(ctx, 5, 10, configs)) + src, err := GetScopedWorkflowSource(ctx, 5, 10) + require.NoError(t, err) + assert.Equal(t, configs, src.WorkflowConfigs) + + // clearing the configs works + require.NoError(t, SetScopedWorkflowSourceConfigs(ctx, 5, 10, nil)) + src, err = GetScopedWorkflowSource(ctx, 5, 10) + require.NoError(t, err) + assert.Empty(t, src.WorkflowConfigs) + + // remove + require.NoError(t, RemoveScopedWorkflowSource(ctx, 5, 10)) + _, err = GetScopedWorkflowSource(ctx, 5, 10) + assert.ErrorIs(t, err, util.ErrNotExist) + sources, err = GetScopedWorkflowSourcesByOwner(ctx, 5) + require.NoError(t, err) + assert.Empty(t, sources) +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index ab580710f3..f77ce03201 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -419,6 +419,7 @@ func prepareMigrationTasks() []*migration { newMigration(339, "Extend action c_u index to include created_unix for faster dashboard feed queries", v1_27.AddCreatedUnixToActionUserIsDeletedIndex), newMigration(340, "Add ContinueOnError column to ActionRunJob", v1_27.AddContinueOnErrorToActionRunJob), newMigration(341, "Convert legacy MSSQL DATETIME columns to DATETIME2", v1_27.FixLegacyMSSQLDateTimeColumns), + newMigration(342, "Add scoped workflows schema", v1_27.AddScopedWorkflowsSchema), } return preparedMigrations } diff --git a/models/migrations/v1_27/v342.go b/models/migrations/v1_27/v342.go new file mode 100644 index 0000000000..7f57f552f6 --- /dev/null +++ b/models/migrations/v1_27/v342.go @@ -0,0 +1,42 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_27 + +import ( + "gitea.dev/models/db" + "gitea.dev/modules/timeutil" + + "xorm.io/xorm" +) + +func AddScopedWorkflowsSchema(x db.EngineMigration) error { + // Create the action_scoped_workflow_source table + type ScopedWorkflowConfig struct { + Required bool `json:"required"` + Patterns []string `json:"patterns"` + } + type ActionScopedWorkflowSource struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(owner_repo) NOT NULL DEFAULT 0"` + SourceRepoID int64 `xorm:"INDEX UNIQUE(owner_repo) NOT NULL DEFAULT 0"` + WorkflowConfigs map[string]*ScopedWorkflowConfig `xorm:"JSON TEXT 'workflow_configs'"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated"` + } + if err := x.Sync(new(ActionScopedWorkflowSource)); err != nil { + return err + } + + // Add the columns that record where a run's workflow content came from + type ActionRun struct { + WorkflowRepoID int64 `xorm:"NOT NULL DEFAULT 0"` + WorkflowCommitSHA string `xorm:"VARCHAR(64) NOT NULL DEFAULT ''"` + IsScopedRun bool `xorm:"NOT NULL DEFAULT false"` + } + _, err := x.SyncWithOptions(xorm.SyncOptions{ + IgnoreDropIndices: true, + IgnoreConstrains: true, + }, new(ActionRun)) + return err +} diff --git a/models/repo/repo_unit_actions.go b/models/repo/repo_unit_actions.go index da162027b4..3bba5de12a 100644 --- a/models/repo/repo_unit_actions.go +++ b/models/repo/repo_unit_actions.go @@ -70,6 +70,8 @@ func MakeRestrictedPermissions() ActionsTokenPermissions { type ActionsConfig struct { DisabledWorkflows []string + // DisabledScopedWorkflows maps a scoped workflow's source repository ID to the entry names opted out of in this repository. + DisabledScopedWorkflows map[int64][]string // CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos. // Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions. CollaborativeOwnerIDs []int64 @@ -98,6 +100,29 @@ func (cfg *ActionsConfig) DisableWorkflow(file string) { cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file) } +func (cfg *ActionsConfig) IsScopedWorkflowDisabled(sourceRepoID int64, workflowID string) bool { + return slices.Contains(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID) +} + +func (cfg *ActionsConfig) DisableScopedWorkflow(sourceRepoID int64, workflowID string) { + if slices.Contains(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID) { + return + } + if cfg.DisabledScopedWorkflows == nil { + cfg.DisabledScopedWorkflows = make(map[int64][]string) + } + cfg.DisabledScopedWorkflows[sourceRepoID] = append(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID) +} + +func (cfg *ActionsConfig) EnableScopedWorkflow(sourceRepoID int64, workflowID string) { + workflowIDs := util.SliceRemoveAll(cfg.DisabledScopedWorkflows[sourceRepoID], workflowID) + if len(workflowIDs) == 0 { + delete(cfg.DisabledScopedWorkflows, sourceRepoID) + return + } + cfg.DisabledScopedWorkflows[sourceRepoID] = workflowIDs +} + func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) { if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) { cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID) diff --git a/models/repo/repo_unit_actions_test.go b/models/repo/repo_unit_actions_test.go new file mode 100644 index 0000000000..552c090fd9 --- /dev/null +++ b/models/repo/repo_unit_actions_test.go @@ -0,0 +1,51 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestActionsConfig_ScopedWorkflowOptOut(t *testing.T) { + cfg := &ActionsConfig{} + + assert.False(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml")) + + cfg.DisableScopedWorkflow(100, "ci.yml") + assert.True(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml")) + + // idempotent + cfg.DisableScopedWorkflow(100, "ci.yml") + assert.Len(t, cfg.DisabledScopedWorkflows, 1) + + // keyed by source repo: the same filename from a different source repo is independent + assert.False(t, cfg.IsScopedWorkflowDisabled(200, "ci.yml")) + + // must not collide with the repo-level DisabledWorkflows list (bare filename) + assert.False(t, cfg.IsWorkflowDisabled("ci.yml")) + cfg.DisableWorkflow("ci.yml") + assert.True(t, cfg.IsWorkflowDisabled("ci.yml")) + assert.True(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml"), "repo-level disable must not touch the scoped entry") + + cfg.EnableScopedWorkflow(100, "ci.yml") + assert.False(t, cfg.IsScopedWorkflowDisabled(100, "ci.yml")) + assert.True(t, cfg.IsWorkflowDisabled("ci.yml"), "enabling the scoped entry must not touch the repo-level disable") +} + +func TestActionsConfig_ScopedWorkflowSerialization(t *testing.T) { + cfg := &ActionsConfig{} + cfg.DisableScopedWorkflow(100, "ci.yml") + cfg.DisableWorkflow("repo.yml") + + bs, err := cfg.ToDB() + require.NoError(t, err) + + got := &ActionsConfig{} + require.NoError(t, got.FromDB(bs)) + assert.True(t, got.IsScopedWorkflowDisabled(100, "ci.yml")) + assert.True(t, got.IsWorkflowDisabled("repo.yml")) +} diff --git a/modules/actions/jobparser/uses.go b/modules/actions/jobparser/uses.go index d829537c69..3d0e3d44f9 100644 --- a/modules/actions/jobparser/uses.go +++ b/modules/actions/jobparser/uses.go @@ -15,9 +15,11 @@ import ( type UsesKind int const ( - // UsesKindLocalSameRepo is "./.gitea/workflows/foo.yml" - a path inside the calling repository. + // UsesKindLocalSameRepo is ".//foo.yml" - a path inside the calling repository. + // For example: "./.gitea/workflows/foo.yml" UsesKindLocalSameRepo UsesKind = iota + 1 - // UsesKindLocalCrossRepo is "owner/repo/.gitea/workflows/foo.yml@ref" - a workflow in another repo on the same instance. + // UsesKindLocalCrossRepo is "owner/repo//foo.yml@ref" - a workflow in another repo on the same instance. + // For example: "owner/repo/.gitea/workflows/foo.yml@ref" UsesKindLocalCrossRepo ) @@ -31,14 +33,16 @@ type UsesRef struct { } var ( - reLocalSameRepo = regexp.MustCompile(`^\./\.(gitea|github)/workflows/([^@]+\.ya?ml)$`) - reLocalCrossRepo = regexp.MustCompile(`^([-.\w]+)/([-.\w]+)/\.(gitea|github)/workflows/([^@]+\.ya?ml)@(.+)$`) + reLocalSameRepo = regexp.MustCompile(`^\./([^@]+\.ya?ml)$`) + reLocalCrossRepo = regexp.MustCompile(`^([-.\w]+)/([-.\w]+)/([^@]+\.ya?ml)@(.+)$`) ) -// ParseUses parses a reusable workflow "uses:" value. -// Only two forms are supported: -// - "./.gitea/workflows/foo.yml" (UsesKindLocalSameRepo, no @ref) -// - "OWNER/REPO/.gitea/workflows/foo.yml@REF" (UsesKindLocalCrossRepo) +// ParseUses parses the SYNTAX of a reusable workflow "uses:" value into a UsesRef. Two forms are supported: +// - ".//foo.yml" (UsesKindLocalSameRepo, no @ref) +// - "OWNER/REPO//foo.yml@REF" (UsesKindLocalCrossRepo) +// +// It deliberately does NOT validate that is an allowed workflow directory: the allowed directories are instance-configurable (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS). +// The caller (services/actions.ResolveUses) enforces the directory allowlist. The returned Path is the cleaned, repo-relative file path. func ParseUses(s string) (*UsesRef, error) { s = strings.TrimSpace(s) if s == "" { @@ -48,9 +52,9 @@ func ParseUses(s string) (*UsesRef, error) { if strings.HasPrefix(s, "./") { m := reLocalSameRepo.FindStringSubmatch(s) if m == nil { - return nil, fmt.Errorf(`invalid local "uses:" %q (expect ./.gitea/workflows/.yml)`, s) + return nil, fmt.Errorf(`invalid local "uses:" %q (expect .//.yml)`, s) } - p := fmt.Sprintf(".%s/workflows/%s", m[1], m[2]) + p := m[1] if path.Clean(p) != p { return nil, fmt.Errorf("invalid workflow path %q", s) } @@ -59,9 +63,9 @@ func ParseUses(s string) (*UsesRef, error) { m := reLocalCrossRepo.FindStringSubmatch(s) if m == nil { - return nil, fmt.Errorf(`invalid cross-repo "uses:" %q (expect owner/repo/.gitea/workflows/.yml@ref)`, s) + return nil, fmt.Errorf(`invalid cross-repo "uses:" %q (expect owner/repo//.yml@ref)`, s) } - p := fmt.Sprintf(".%s/workflows/%s", m[3], m[4]) + p := m[3] if path.Clean(p) != p { return nil, fmt.Errorf("invalid workflow path %q", s) } @@ -70,6 +74,6 @@ func ParseUses(s string) (*UsesRef, error) { Owner: m[1], Repo: m[2], Path: p, - Ref: m[5], + Ref: m[4], }, nil } diff --git a/modules/actions/jobparser/uses_test.go b/modules/actions/jobparser/uses_test.go index c6692054dc..01f76d67de 100644 --- a/modules/actions/jobparser/uses_test.go +++ b/modules/actions/jobparser/uses_test.go @@ -42,6 +42,17 @@ func TestParseUses(t *testing.T) { in: "./.gitea/workflows/sub/build.yml", want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/workflows/sub/build.yml"}, }, + { + // ParseUses is dir-agnostic; the allowed directories (WORKFLOW_DIRS / SCOPED_WORKFLOW_DIRS) are enforced by ResolveUses. + name: "scoped workflows dir parses", + in: "./.gitea/scoped_workflows/lib.yml", + want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/scoped_workflows/lib.yml"}, + }, + { + name: "non-default dir parses (allowlist enforced downstream)", + in: "./.gitea/custom_workflows/x.yaml", + want: UsesRef{Kind: UsesKindLocalSameRepo, Path: ".gitea/custom_workflows/x.yaml"}, + }, { name: "leading/trailing whitespace is trimmed", in: " ./.gitea/workflows/build.yml ", @@ -118,6 +129,17 @@ func TestParseUses(t *testing.T) { Ref: "v1", }, }, + { + name: "scoped workflows dir parses (allowlist enforced by ResolveUses)", + in: "owner/repo/.gitea/scoped_workflows/lib.yml@v1", + want: UsesRef{ + Kind: UsesKindLocalCrossRepo, + Owner: "owner", + Repo: "repo", + Path: ".gitea/scoped_workflows/lib.yml", + Ref: "v1", + }, + }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { @@ -136,23 +158,20 @@ func TestParseUses(t *testing.T) { {name: "empty string", in: ""}, {name: "whitespace only", in: " "}, - // Same-repo malformed + // Same-repo malformed (note: a wrong *directory* parses and should be rejected by the caller) {name: "same-repo with @ref", in: "./.gitea/workflows/build.yml@v1"}, - {name: "same-repo wrong directory", in: "./not-workflows/build.yml"}, {name: "same-repo wrong extension", in: "./.gitea/workflows/build.txt"}, {name: "same-repo missing extension", in: "./.gitea/workflows/build"}, {name: "same-repo absolute path", in: "/.gitea/workflows/build.yml"}, {name: "same-repo path traversal", in: "./.gitea/workflows/../escape.yml"}, {name: "same-repo double slash", in: "./.gitea/workflows//build.yml"}, {name: "same-repo redundant ./", in: "./.gitea/workflows/./build.yml"}, - {name: "same-repo no filename", in: "./.gitea/workflows/.yml"}, // Cross-repo malformed {name: "cross-repo missing @ref", in: "owner/repo/.gitea/workflows/build.yml"}, {name: "cross-repo empty ref", in: "owner/repo/.gitea/workflows/build.yml@"}, {name: "cross-repo missing owner", in: "/repo/.gitea/workflows/build.yml@v1"}, {name: "cross-repo missing repo", in: "owner//.gitea/workflows/build.yml@v1"}, - {name: "cross-repo wrong workflows dir", in: "owner/repo/workflows/build.yml@v1"}, {name: "cross-repo wrong extension", in: "owner/repo/.gitea/workflows/build.txt@v1"}, {name: "cross-repo path traversal", in: "owner/repo/.gitea/workflows/../escape.yml@v1"}, {name: "cross-repo double slash in path", in: "owner/repo/.gitea/workflows//build.yml@v1"}, diff --git a/modules/actions/scoped_workflows.go b/modules/actions/scoped_workflows.go new file mode 100644 index 0000000000..e528d0d1be --- /dev/null +++ b/modules/actions/scoped_workflows.go @@ -0,0 +1,83 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "gitea.dev/modules/actions/jobparser" + "gitea.dev/modules/git" + "gitea.dev/modules/log" + "gitea.dev/modules/setting" + api "gitea.dev/modules/structs" + webhook_module "gitea.dev/modules/webhook" +) + +// ListScopedWorkflows lists scoped workflow files (under SCOPED_WORKFLOW_DIRS) at the given commit. +func ListScopedWorkflows(commit *git.Commit) (string, git.Entries, error) { + return listWorkflowsInDirs(commit, setting.Actions.ScopedWorkflowDirs) +} + +// ParsedScopedWorkflow is one scoped workflow's source-side parse result +type ParsedScopedWorkflow struct { + EntryName string + DisplayName string // the workflow `name:` or base file name + Content []byte // raw content of the workflow file + Events []*jobparser.Event // decoded `on:` events +} + +// ParseScopedWorkflows lists and parses the scoped workflow files at sourceCommit (under SCOPED_WORKFLOW_DIRS). +func ParseScopedWorkflows(sourceCommit *git.Commit) ([]*ParsedScopedWorkflow, error) { + _, entries, err := ListScopedWorkflows(sourceCommit) + if err != nil { + return nil, err + } + + parsed := make([]*ParsedScopedWorkflow, 0, len(entries)) + for _, entry := range entries { + content, err := GetContentFromEntry(entry) + if err != nil { + return nil, err + } + + // one workflow may have multiple events + events, err := GetEventsFromContent(content) + if err != nil { + log.Warn("ignore invalid scoped workflow %q: %v", entry.Name(), err) + continue + } + parsed = append(parsed, &ParsedScopedWorkflow{ + EntryName: entry.Name(), + DisplayName: WorkflowDisplayName(entry.Name(), content), + Content: content, + Events: events, + }) + } + return parsed, nil +} + +// MatchScopedWorkflows evaluates already-parsed scoped workflows against one consuming event, returning those whose `on:` matches. +func MatchScopedWorkflows( + parsed []*ParsedScopedWorkflow, + consumerGitRepo *git.Repository, + consumerCommit *git.Commit, + triggedEvent webhook_module.HookEventType, + payload api.Payloader, +) []*DetectedWorkflow { + workflows := make([]*DetectedWorkflow, 0, len(parsed)) + for _, p := range parsed { + for _, evt := range p.Events { + if evt.IsSchedule() { + // schedule is a non-target for scoped workflows + continue + } + if detectMatched(consumerGitRepo, consumerCommit, triggedEvent, payload, evt) { + workflows = append(workflows, &DetectedWorkflow{ + EntryName: p.EntryName, + TriggerEvent: evt, + Content: p.Content, + }) + } + } + } + return workflows +} diff --git a/modules/actions/scoped_workflows_test.go b/modules/actions/scoped_workflows_test.go new file mode 100644 index 0000000000..ae7085d26e --- /dev/null +++ b/modules/actions/scoped_workflows_test.go @@ -0,0 +1,62 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsWorkflowInDirs(t *testing.T) { + tests := []struct { + name string + dirs []string + path string + expected bool + }{ + { + name: "default scoped dir with yml", + dirs: []string{".gitea/scoped_workflows", ".github/scoped_workflows"}, + path: ".gitea/scoped_workflows/security.yml", + expected: true, + }, + { + name: "default scoped dir with yaml", + dirs: []string{".gitea/scoped_workflows", ".github/scoped_workflows"}, + path: ".github/scoped_workflows/lint.yaml", + expected: true, + }, + { + name: "normal workflow path is not a scoped workflow", + dirs: []string{".gitea/scoped_workflows"}, + path: ".gitea/workflows/ci.yml", + expected: false, + }, + { + name: "non-yaml file", + dirs: []string{".gitea/scoped_workflows"}, + path: ".gitea/scoped_workflows/readme.md", + expected: false, + }, + { + name: "feature disabled (no scoped dirs)", + dirs: []string{}, + path: ".gitea/scoped_workflows/security.yml", + expected: false, + }, + { + name: "directory boundary", + dirs: []string{".gitea/scoped_workflows"}, + path: ".gitea/scoped_workflows2/security.yml", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isWorkflowInDirs(tt.path, tt.dirs)) + }) + } +} diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 28dc45dc7a..3ea9c4b6cd 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -5,6 +5,8 @@ package actions import ( "bytes" + "fmt" + "path" "slices" "strings" @@ -38,11 +40,20 @@ func init() { } func IsWorkflow(path string) bool { + return isWorkflowInDirs(path, setting.Actions.WorkflowDirs) +} + +// IsWorkflowOrScopedWorkflow reports whether path is a workflow file under WORKFLOW_DIRS or SCOPED_WORKFLOW_DIRS. +func IsWorkflowOrScopedWorkflow(path string) bool { + return isWorkflowInDirs(path, setting.Actions.WorkflowDirs) || isWorkflowInDirs(path, setting.Actions.ScopedWorkflowDirs) +} + +func isWorkflowInDirs(path string, dirs []string) bool { if (!strings.HasSuffix(path, ".yaml")) && (!strings.HasSuffix(path, ".yml")) { return false } - for _, workflowDir := range setting.Actions.WorkflowDirs { + for _, workflowDir := range dirs { if strings.HasPrefix(path, workflowDir+"/") { return true } @@ -51,10 +62,14 @@ func IsWorkflow(path string) bool { } func ListWorkflows(commit *git.Commit) (string, git.Entries, error) { + return listWorkflowsInDirs(commit, setting.Actions.WorkflowDirs) +} + +func listWorkflowsInDirs(commit *git.Commit, dirs []string) (string, git.Entries, error) { var tree *git.Tree var err error var workflowDir string - for _, workflowDir = range setting.Actions.WorkflowDirs { + for _, workflowDir = range dirs { tree, err = commit.SubTree(workflowDir) if err == nil { break @@ -117,6 +132,40 @@ func ValidateWorkflowContent(content []byte) error { return err } +// WorkflowDisplayName returns a workflow's display name: its `name:` if non-blank, otherwise the base file name. +// This is the value used as the workflow segment of its commit-status context. +func WorkflowDisplayName(file string, content []byte) string { + displayName := path.Base(file) + if wfs, err := jobparser.Parse(content); err == nil && len(wfs) > 0 { + if name := strings.TrimSpace(wfs[0].Name); name != "" { + displayName = name + } + } + return displayName +} + +// WorkflowStatusContextName builds a workflow job's commit-status context name: " / ()". +func WorkflowStatusContextName(displayName, jobName, event string) string { + return strings.TrimSpace(fmt.Sprintf("%s / %s (%s)", displayName, jobName, event)) +} + +// ScopedWorkflowStatusContextName prefixes a scoped run's status-check context with its source repo, set off by a colon: ": / ()". +func ScopedWorkflowStatusContextName(prefix, displayName, jobName, event string) string { + return strings.TrimSpace(fmt.Sprintf("%s: %s", prefix, WorkflowStatusContextName(displayName, jobName, event))) +} + +// ShouldEventCreateCommitStatus reports whether a run triggered by the given workflow `on:` event posts a commit status, +// so its context can serve as a required status check. +// TODO: this allowlist duplicates the truth in services/actions.getCommitStatusEventNameAndCommitID, which decides the actual event string and whether a status is posted. +// The two are kept in sync by hand and can drift; unify them into a single source so adding a status-producing event in one place automatically updates the other. +func ShouldEventCreateCommitStatus(event string) bool { + switch event { + case "push", "pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment", "release": + return true + } + return false +} + func DetectWorkflows( gitRepo *git.Repository, commit *git.Commit, diff --git a/modules/setting/actions.go b/modules/setting/actions.go index d8e229267c..69e847f80f 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -29,12 +29,14 @@ var ( AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"` SkipWorkflowStrings []string `ini:"SKIP_WORKFLOW_STRINGS"` WorkflowDirs []string `ini:"WORKFLOW_DIRS"` + ScopedWorkflowDirs []string `ini:"SCOPED_WORKFLOW_DIRS"` MaxRerunAttempts int64 `ini:"MAX_RERUN_ATTEMPTS"` }{ Enabled: true, DefaultActionsURL: defaultActionsURLGitHub, SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"}, WorkflowDirs: []string{".gitea/workflows", ".github/workflows"}, + ScopedWorkflowDirs: []string{".gitea/scoped_workflows"}, MaxRerunAttempts: defaultMaxRerunAttempts, } ) @@ -130,20 +132,39 @@ func loadActionsFrom(rootCfg ConfigProvider) error { return fmt.Errorf("invalid [actions] LOG_COMPRESSION: %q", Actions.LogCompression) } - workflowDirs := make([]string, 0, len(Actions.WorkflowDirs)) - for _, dir := range Actions.WorkflowDirs { + workflowDirs := normalizeWorkflowDirs(Actions.WorkflowDirs) + if len(workflowDirs) == 0 { + return errors.New("[actions] WORKFLOW_DIRS must contain at least one entry") + } + Actions.WorkflowDirs = workflowDirs + + // SCOPED_WORKFLOW_DIRS may be empty (feature disabled), but it must not overlap with WORKFLOW_DIRS: + // a scoped dir nested in (or equal to) a workflow dir would make the same file run both repo-level and scope-level. + Actions.ScopedWorkflowDirs = normalizeWorkflowDirs(Actions.ScopedWorkflowDirs) + for _, scopedDir := range Actions.ScopedWorkflowDirs { + for _, workflowDir := range Actions.WorkflowDirs { + if scopedDir == workflowDir || + strings.HasPrefix(scopedDir, workflowDir+"/") || + strings.HasPrefix(workflowDir, scopedDir+"/") { + return fmt.Errorf("[actions] SCOPED_WORKFLOW_DIRS entry %q overlaps with WORKFLOW_DIRS entry %q", scopedDir, workflowDir) + } + } + } + + return nil +} + +// normalizeWorkflowDirs trims, normalizes separators and drops empty/trailing-slash entries. +func normalizeWorkflowDirs(dirs []string) []string { + normalized := make([]string, 0, len(dirs)) + for _, dir := range dirs { dir = strings.TrimSpace(dir) if dir == "" { continue } dir = strings.ReplaceAll(dir, `\`, `/`) dir = strings.TrimRight(dir, "/") - workflowDirs = append(workflowDirs, dir) + normalized = append(normalized, dir) } - if len(workflowDirs) == 0 { - return errors.New("[actions] WORKFLOW_DIRS must contain at least one entry") - } - Actions.WorkflowDirs = workflowDirs - - return nil + return normalized } diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go index 5c7ab268c1..5b2e5ac3d9 100644 --- a/modules/setting/actions_test.go +++ b/modules/setting/actions_test.go @@ -5,8 +5,11 @@ package setting import ( "path/filepath" + "slices" "testing" + "gitea.dev/modules/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -238,3 +241,71 @@ DEFAULT_ACTIONS_URL = gitea }) } } + +func Test_ScopedWorkflowDirs(t *testing.T) { + defer test.MockVariableValue(&Actions)() + + defaultWorkflowDirs := []string{".gitea/workflows", ".github/workflows"} + defaultScopedDirs := []string{".gitea/scoped_workflows"} + + tests := []struct { + name string + iniStr string + wantScoped []string + wantErr bool + }{ + { + name: "default", + iniStr: `[actions]`, + wantScoped: defaultScopedDirs, + }, + { + name: "custom dir", + iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = .gitea/my-scoped", + wantScoped: []string{".gitea/my-scoped"}, + }, + { + name: "empty disables the feature", + iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = , ,", + wantScoped: []string{}, + }, + { + name: "overlap equal with workflow dir", + iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows\nSCOPED_WORKFLOW_DIRS = .gitea/workflows", + wantErr: true, + }, + { + name: "scoped dir nested under workflow dir", + iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows\nSCOPED_WORKFLOW_DIRS = .gitea/workflows/scoped", + wantErr: true, + }, + { + name: "workflow dir nested under scoped dir", + iniStr: "[actions]\nWORKFLOW_DIRS = .gitea/workflows/ci\nSCOPED_WORKFLOW_DIRS = .gitea/workflows", + wantErr: true, + }, + { + name: "no overlap", + iniStr: "[actions]\nSCOPED_WORKFLOW_DIRS = .gitea/scoped", + wantScoped: []string{".gitea/scoped"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // reset to defaults so MapTo starts clean (absent keys keep the defaults) + Actions.WorkflowDirs = slices.Clone(defaultWorkflowDirs) + Actions.ScopedWorkflowDirs = slices.Clone(defaultScopedDirs) + + cfg, err := NewConfigProviderFromData(tt.iniStr) + require.NoError(t, err) + err = loadActionsFrom(cfg) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantScoped, Actions.ScopedWorkflowDirs) + }) + } +} diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 45b2892481..737b4e7d3c 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3779,6 +3779,7 @@ "actions.runs.commit": "Commit", "actions.runs.run_details": "Run Details", "actions.runs.workflow_file": "Workflow file", + "actions.runs.workflow_file_no_permission": "No permission to view the 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", @@ -3832,6 +3833,32 @@ "actions.workflow.enable": "Enable Workflow", "actions.workflow.enable_success": "Workflow '%s' enabled successfully.", "actions.workflow.disabled": "Workflow is disabled.", + "actions.workflow.scope_owner": "Owner", + "actions.workflow.scope_global": "Global", + "actions.workflow.required": "Required", + "actions.workflow.scoped_required_cannot_disable": "This scoped workflow is required and cannot be disabled.", + "actions.scoped_workflows": "Scoped Workflows", + "actions.scoped_workflows.desc_org": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository of this organization, in that repository's own context.", + "actions.scoped_workflows.desc_user": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository you own, in that repository's own context.", + "actions.scoped_workflows.desc_global": "Register repositories as scoped-workflow sources. Workflow files under the scoped workflow directories of a source repository's default branch run on every repository on this instance, in that repository's own context. Because instance-level sources are evaluated on every repository's events, registering them can add overhead on large instances.", + "actions.scoped_workflows.add_help": "To provide scoped workflows from a repository, commit the workflow files under %s on its default branch, then register the repository as a source below.", + "actions.scoped_workflows.security_note": "A source repository's workflow content is executed in every repository it applies to, and its step scripts and their output are written to that repository's Actions logs and readable by anyone who can view the consuming repository's Actions. Registering a private repository as a source therefore discloses its workflow logic through those logs. Only register repositories whose workflow content may be shared with every consuming repository. If a scoped workflow references a reusable workflow from a private repository, make sure every consuming repository can read it, otherwise the workflow will fail there.", + "actions.scoped_workflows.source.add": "Add source repository", + "actions.scoped_workflows.source.add_success": "Source repository added.", + "actions.scoped_workflows.source.remove_success": "Source repository removed.", + "actions.scoped_workflows.source.not_found": "Repository not found.", + "actions.scoped_workflows.required.update_success": "Required workflows updated.", + "actions.scoped_workflows.required.label": "Mark workflows as required (a required workflow cannot be disabled by repositories):", + "actions.scoped_workflows.required.patterns": "Required status check patterns", + "actions.scoped_workflows.required.patterns_aria": "Required status check patterns for %s", + "actions.scoped_workflows.required.patterns_note": "only enforced while the workflow is required", + "actions.scoped_workflows.required.patterns_hint": "Mark the workflow as required to configure its status check patterns.", + "actions.scoped_workflows.required.patterns_help": "One status check pattern (glob) per line. A consuming pull request can only be merged once a status matching every pattern has passed. This is enforced on any target branch that has a protection rule, even one with its own status checks disabled; a target branch with no protection rule is not gated.", + "actions.scoped_workflows.required.patterns_empty": "Each required workflow needs at least one status check pattern.", + "actions.scoped_workflows.required.missing_file": "file no longer in source", + "actions.scoped_workflows.required.expected_contexts": "Expected status checks (a check that matches a pattern is marked)", + "actions.scoped_workflows.required.no_status_contexts": "This workflow posts no status checks, so marking it required would block every consuming pull request from merging. Uncheck Required.", + "actions.scoped_workflows.no_files": "No scoped workflow files were found on the default branch.", "actions.workflow.run": "Run Workflow", "actions.workflow.create_status_badge": "Create status badge", "actions.workflow.status_badge": "Status Badge", diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 3b920ac551..ddbd5bf59f 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -1024,6 +1024,11 @@ func ActionsListWorkflowRuns(ctx *context.APIContext) { // description: if true, the `pull_requests` field on each returned run is emptied // type: boolean // required: false + // - name: scoped_workflow_source_repo_id + // description: For a scoped workflow, the ID of the source repository providing it; omit or 0 for a repo-level workflow. + // in: query + // type: integer + // format: int64 // - name: page // in: query // description: page number of results to return (1-based) @@ -1043,20 +1048,25 @@ func ActionsListWorkflowRuns(ctx *context.APIContext) { // "$ref": "#/responses/notFound" workflowID := ctx.PathParam("workflow_id") - // Existing runs prove the workflow is/was valid and cover historical workflows - // whose file was later removed. Fall back to a git lookup for never-run workflows. + scopedWorkflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + // Existing runs prove the workflow is/was valid and cover historical workflows whose file was later removed. + // Repo-level never-run workflows fall back to a git lookup; scoped workflows are selected by source repo ID and may return an empty run list. runExists, err := db.Exist[actions_model.ActionRun](ctx, actions_model.FindRunOptions{ - RepoID: ctx.Repo.Repository.ID, - WorkflowID: workflowID, + RepoID: ctx.Repo.Repository.ID, + WorkflowID: workflowID, + WorkflowRepoID: scopedWorkflowSourceRepoID, + IsScopedRun: optional.Some(scopedWorkflowSourceRepoID > 0), }.ToConds()) if err != nil { ctx.APIErrorInternal(err) return } if !runExists { - if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil { - ctx.APIErrorAuto(err) - return + if scopedWorkflowSourceRepoID == 0 { + if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil { + ctx.APIErrorAuto(err) + return + } } } @@ -1141,6 +1151,11 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { // description: Whether the response should include the workflow run ID and URLs. // in: query // type: boolean + // - name: scoped_workflow_source_repo_id + // description: For a scoped workflow, the ID of the source repository providing it; omit or 0 for a repo-level workflow. + // in: query + // type: integer + // format: int64 // responses: // "200": // "$ref": "#/responses/RunDetails" @@ -1162,7 +1177,9 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { return } - runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { + // a non-zero scoped_workflow_source_repo_id dispatches a scoped workflow from that source repo; 0/absent is repo-level. + scopedWorkflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, scopedWorkflowSourceRepoID, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") { // The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string // So we have to manually read the `inputs[key]` from the form diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go index d62d0d3a22..08192b7f3d 100644 --- a/routers/api/v1/shared/action.go +++ b/routers/api/v1/shared/action.go @@ -148,6 +148,11 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string) WorkflowID: workflowID, ListOptions: listOptions, } + if workflowID != "" { + workflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + opts.IsScopedRun = optional.Some(workflowSourceRepoID > 0) + opts.WorkflowRepoID = workflowSourceRepoID + } if event := ctx.FormString("event"); event != "" { opts.TriggerEvent = webhook.HookEventType(event) diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 45c94fea5c..80b8525622 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -103,16 +103,22 @@ func List(ctx *context.Context) { if ctx.Written() { return } - otherWorkflows := prepareOtherWorkflows(ctx, workflows, curWorkflowID) + curWorkflowRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + ctx.Data["CurWorkflowRepoID"] = curWorkflowRepoID + scopedNames := prepareScopedWorkflows(ctx, curWorkflowID, curWorkflowRepoID) if ctx.Written() { return } - prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID) + otherWorkflows := prepareOtherWorkflows(ctx, workflows, scopedNames, curWorkflowID) + if ctx.Written() { + return + } + prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID, curWorkflowRepoID) if ctx.Written() { return } - prepareWorkflowList(ctx, workflows, otherWorkflows) + prepareWorkflowList(ctx, workflows, otherWorkflows, len(scopedNames) > 0) if ctx.Written() { return } @@ -122,7 +128,7 @@ func List(ctx *context.Context) { // prepareOtherWorkflows surfaces historical runs whose workflow file no longer // exists on the default branch (renamed, removed, or only on other branches). -func prepareOtherWorkflows(ctx *context.Context, workflows []WorkflowInfo, curWorkflowID string) []string { +func prepareOtherWorkflows(ctx *context.Context, workflows []WorkflowInfo, scopedNames container.Set[string], curWorkflowID string) []string { listed := make(container.Set[string], len(workflows)) for _, w := range workflows { listed.Add(w.Entry.Name()) @@ -130,9 +136,10 @@ func prepareOtherWorkflows(ctx *context.Context, workflows []WorkflowInfo, curWo var other []string if ctx.Repo.Repository.NumActionRuns > 0 { - ids, err := actions_model.GetRunWorkflowIDs(ctx, ctx.Repo.Repository.ID) + // "Other workflows" lists repo-level orphans only: GetRepoRunWorkflowIDs excludes scoped runs. + ids, err := actions_model.GetRepoRunWorkflowIDs(ctx, ctx.Repo.Repository.ID) if err != nil { - ctx.ServerError("GetRunWorkflowIDs", err) + ctx.ServerError("GetRepoRunWorkflowIDs", err) return nil } other = container.FilterSlice(ids, func(id string) (string, bool) { @@ -141,7 +148,8 @@ func prepareOtherWorkflows(ctx *context.Context, workflows []WorkflowInfo, curWo } ctx.Data["OtherWorkflows"] = other - ctx.Data["CurWorkflowIsListed"] = curWorkflowID == "" || listed.Contains(curWorkflowID) + // A selected workflow counts as "listed" if it is a repo-level file or an active scoped workflow. + ctx.Data["CurWorkflowIsListed"] = curWorkflowID == "" || listed.Contains(curWorkflowID) || scopedNames.Contains(curWorkflowID) return other } @@ -171,7 +179,7 @@ func WorkflowDispatchInputs(ctx *context.Context) { if ctx.Written() { return } - prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID) + prepareWorkflowDispatchTemplate(ctx, workflows, curWorkflowID, ctx.FormInt64("scoped_workflow_source_repo_id")) if ctx.Written() { return } @@ -239,6 +247,7 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow ctx.Data["workflows"] = workflows ctx.Data["RepoLink"] = ctx.Repo.Repository.Link() + ctx.Data["RepoID"] = ctx.Repo.Repository.ID ctx.Data["AllowDisableOrEnableWorkflow"] = ctx.Repo.Permission.IsAdmin() actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() ctx.Data["ActionsConfig"] = actionsConfig @@ -248,21 +257,165 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow return workflows, curWorkflowID } -func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo, curWorkflowID string) { - actionsConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() - if curWorkflowID == "" || !ctx.Repo.Permission.CanWrite(unit.TypeActions) || actionsConfig.IsWorkflowDisabled(curWorkflowID) { +// ScopedWorkflowInfo describes a scoped workflow effective for the current repo, listed under its source group. +type ScopedWorkflowInfo struct { + SourceRepoID int64 + EntryName string + DisplayName string + Required bool + Disabled bool +} + +// ScopedWorkflowSourceGroup groups the scoped workflows contributed by one source repo for the All-Workflows sidebar. +type ScopedWorkflowSourceGroup struct { + SourceRepoID int64 + SourceRepoName string // owner/name of the source repo; shown for instance-level sources and used as the tooltip + SourceRepoShortName string // name only; shown for owner-level sources, where the owner is always the current owner + FromInstance bool // registered at instance level (owner_id == 0) rather than by the owner + IsActive bool // the currently-selected workflow belongs to this source; render the group expanded + Workflows []ScopedWorkflowInfo +} + +// prepareScopedWorkflows lists the scoped workflows effective for the repo's owner (and instance) for the All-Workflows sidebar. +func prepareScopedWorkflows(ctx *context.Context, curWorkflowID string, curWorkflowRepoID int64) container.Set[string] { + scopedNames := make(container.Set[string]) + + repo := ctx.Repo.Repository + sources, err := actions_model.GetEffectiveScopedWorkflowSources(ctx, repo.OwnerID) + if err != nil { + ctx.ServerError("GetEffectiveScopedWorkflowSources", err) + return scopedNames + } + if len(sources) == 0 { + return scopedNames + } + + actionsConfig := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + + groups := make([]ScopedWorkflowSourceGroup, 0, len(sources)) + seen := make(map[int64]bool, len(sources)) + for _, source := range sources { + if seen[source.SourceRepoID] { + continue + } + seen[source.SourceRepoID] = true + + sourceRepo, err := repo_model.GetRepositoryByID(ctx, source.SourceRepoID) + if err != nil { + log.Error("scoped workflows list: load source repo %d: %v", source.SourceRepoID, err) + continue + } + if sourceRepo.IsEmpty { + continue + } + + _, entries, err := actions_service.LoadParsedScopedWorkflows(ctx, sourceRepo) + if err != nil { + log.Error("scoped workflows list: parse %s: %v", sourceRepo.FullName(), err) + continue + } + if len(entries) == 0 { + continue + } + + group := ScopedWorkflowSourceGroup{ + SourceRepoID: sourceRepo.ID, + SourceRepoName: sourceRepo.FullName(), + SourceRepoShortName: sourceRepo.Name, + FromInstance: source.OwnerID == 0, + } + for _, e := range entries { + scopedNames.Add(e.EntryName) + required := actions_model.IsWorkflowRequiredInSources(sources, sourceRepo.ID, e.EntryName) + disabled := actionsConfig.IsScopedWorkflowDisabled(sourceRepo.ID, e.EntryName) + group.Workflows = append(group.Workflows, ScopedWorkflowInfo{ + SourceRepoID: sourceRepo.ID, + EntryName: e.EntryName, + DisplayName: e.DisplayName, + Required: required, + Disabled: disabled, + }) + + if curWorkflowID == e.EntryName && curWorkflowRepoID == sourceRepo.ID { + ctx.Data["CurWorkflowDisabled"] = disabled + ctx.Data["CurWorkflowScopedRepoID"] = sourceRepo.ID + ctx.Data["CurWorkflowRequired"] = required + group.IsActive = true // keep this group expanded so the selected workflow stays visible + } + } + groups = append(groups, group) + } + + ctx.Data["ScopedWorkflowGroups"] = groups + return scopedNames +} + +// loadScopedWorkflowModel reads and parses a scoped workflow's content from its source repo's default branch. +func loadScopedWorkflowModel(ctx *context.Context, repo *repo_model.Repository, sourceRepoID int64, workflowID string) *act_model.Workflow { + effective, err := actions_model.IsScopedWorkflowSourceEffective(ctx, repo.OwnerID, sourceRepoID) + if err != nil { + log.Error("scoped dispatch: IsScopedWorkflowSourceEffective: %v", err) + return nil + } + if !effective { + return nil + } + + sourceRepo, err := repo_model.GetRepositoryByID(ctx, sourceRepoID) + if err != nil || sourceRepo.IsEmpty { + return nil + } + content, err := actions_service.ScopedWorkflowContent(ctx, sourceRepo, workflowID) + if err != nil { + log.Error("scoped dispatch: content of %s in %s: %v", workflowID, sourceRepo.RelativePath(), err) + return nil + } + if content == nil { + return nil // the workflow does not exist on the source's default branch + } + wf, err := act_model.ReadWorkflow(bytes.NewReader(content)) + if err != nil { + return nil + } + return wf +} + +func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []WorkflowInfo, curWorkflowID string, curWorkflowRepoID int64) { + repo := ctx.Repo.Repository + if curWorkflowID == "" || !ctx.Repo.Permission.CanWrite(unit.TypeActions) { + return + } + actionsConfig := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + + isScoped := curWorkflowRepoID > 0 + if isScoped { + // a required scoped workflow can never be opted out, so a stale disabled flag must not hide its dispatch form + optedOut, err := actions_model.IsScopedWorkflowOptedOut(ctx, actionsConfig, repo.OwnerID, curWorkflowRepoID, curWorkflowID) + if err != nil { + log.Error("IsScopedWorkflowOptedOut: %v", err) + return + } + if optedOut { + return + } + } else if actionsConfig.IsWorkflowDisabled(curWorkflowID) { return } var curWorkflow *act_model.Workflow - for _, workflowInfo := range workflowInfos { - if workflowInfo.Entry.Name() == curWorkflowID { - if workflowInfo.Workflow == nil { - log.Debug("CurWorkflowID %s is found but its workflowInfo.Workflow is nil", curWorkflowID) - return + if isScoped { + // a scoped workflow's content lives in its source repo, not in workflowInfos (the consumer's own files) + curWorkflow = loadScopedWorkflowModel(ctx, repo, curWorkflowRepoID, curWorkflowID) + } else { + for _, workflowInfo := range workflowInfos { + if workflowInfo.Entry.Name() == curWorkflowID { + if workflowInfo.Workflow == nil { + log.Debug("CurWorkflowID %s is found but its workflowInfo.Workflow is nil", curWorkflowID) + return + } + curWorkflow = workflowInfo.Workflow + break } - curWorkflow = workflowInfo.Workflow - break } } @@ -303,10 +456,11 @@ func prepareWorkflowDispatchTemplate(ctx *context.Context, workflowInfos []Workf ctx.Data["Tags"] = tags } -func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWorkflows []string) { +func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWorkflows []string, hasScopedWorkflows bool) { actorID := ctx.FormInt64("actor") status := ctx.FormInt("status") workflowID := ctx.FormString("workflow") + scopedWorkflowSourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") branch := ctx.FormString("branch") page := ctx.FormInt("page") if page <= 0 { @@ -327,9 +481,15 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo Page: page, PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), }, - RepoID: ctx.Repo.Repository.ID, - WorkflowID: workflowID, - TriggerUserID: actorID, + RepoID: ctx.Repo.Repository.ID, + WorkflowID: workflowID, + WorkflowRepoID: scopedWorkflowSourceRepoID, + TriggerUserID: actorID, + } + + // Constrain scoped vs repo-level only for a listed workflow, whose link carries scoped_workflow_source_repo_id. + if workflowID != "" && !slices.Contains(otherWorkflows, workflowID) { + opts.IsScopedRun = optional.Some(scopedWorkflowSourceRepoID > 0) } // if status is not StatusUnknown, it means user has selected a status filter @@ -422,7 +582,11 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo } } ctx.Data["WorkflowNames"] = workflowNames - prepareWorkflowBadgeTemplate(ctx, workflowID, workflowDisplayName) + // A scoped workflow has no repo-level badge on this repo (the badge endpoint reads is_scoped_run=false runs), + // so don't offer the "create status badge" entry for it. + if scopedWorkflowSourceRepoID == 0 { + prepareWorkflowBadgeTemplate(ctx, workflowID, workflowDisplayName) + } actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID) if err != nil { @@ -443,7 +607,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo, otherWo pager := context.NewPagination(total, opts.PageSize, opts.Page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager - ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(otherWorkflows) > 0 || len(runs) > 0 + ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(otherWorkflows) > 0 || len(runs) > 0 || hasScopedWorkflows ctx.Data["CanWriteRepoUnitActions"] = ctx.Repo.Permission.CanWrite(unit.TypeActions) } @@ -583,10 +747,11 @@ func decodeNode(node yaml.Node, out any) bool { return true } -func actionsListRedirectURL(repoLink, workflow, actor, status, branch string) string { - return fmt.Sprintf("%s/actions?workflow=%s&actor=%s&status=%s&branch=%s", +func actionsListRedirectURL(repoLink, workflow, scopedWorkflowSourceRepoID, actor, status, branch string) string { + return fmt.Sprintf("%s/actions?workflow=%s&scoped_workflow_source_repo_id=%s&actor=%s&status=%s&branch=%s", repoLink, url.QueryEscape(workflow), + url.QueryEscape(scopedWorkflowSourceRepoID), url.QueryEscape(actor), url.QueryEscape(status), url.QueryEscape(branch), diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 34f8ae4475..5bf5353d30 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -21,12 +21,14 @@ import ( "gitea.dev/models/db" git_model "gitea.dev/models/git" issues_model "gitea.dev/models/issues" + access_model "gitea.dev/models/perm/access" repo_model "gitea.dev/models/repo" "gitea.dev/models/unit" "gitea.dev/modules/actions" "gitea.dev/modules/base" "gitea.dev/modules/cache" "gitea.dev/modules/git" + "gitea.dev/modules/gitrepo" "gitea.dev/modules/httplib" "gitea.dev/modules/json" "gitea.dev/modules/log" @@ -243,6 +245,11 @@ func ViewWorkflowFile(ctx *context_module.Context) { return } + if run.IsScopedRun { + viewScopedWorkflowFile(ctx, run) + return + } + commit, err := ctx.Repo.GitRepo.GetCommit(run.CommitSHA) if err != nil { ctx.NotFoundOrServerError("GetCommit", func(err error) bool { @@ -293,25 +300,26 @@ type ViewResponse struct { // ViewLink is the attempt-aware URL for navigation, e.g. "/owner/repo/actions/runs/123" for the latest attempt // or "/owner/repo/actions/runs/123/attempts/2" for a historical attempt. // Use this when the target should reflect the currently-viewed attempt. - ViewLink string `json:"viewLink"` - Index int64 `json:"index"` // the per-repository run number, displayed as "#N" - Title string `json:"title"` - TitleHTML template.HTML `json:"titleHTML"` - Status string `json:"status"` - CanCancel bool `json:"canCancel"` - CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve - CanRerun bool `json:"canRerun"` - CanRerunFailed bool `json:"canRerunFailed"` - CanDeleteArtifact bool `json:"canDeleteArtifact"` - Done bool `json:"done"` - WorkflowID string `json:"workflowID"` - WorkflowLink string `json:"workflowLink"` - IsSchedule bool `json:"isSchedule"` - RunAttempt int64 `json:"runAttempt"` - Attempts []*ViewRunAttempt `json:"attempts"` - Jobs []*ViewJob `json:"jobs"` - Commit ViewCommit `json:"commit"` - PullRequest *ViewPullRequest `json:"pullRequest,omitempty"` + ViewLink string `json:"viewLink"` + Index int64 `json:"index"` // the per-repository run number, displayed as "#N" + Title string `json:"title"` + TitleHTML template.HTML `json:"titleHTML"` + Status string `json:"status"` + CanCancel bool `json:"canCancel"` + CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve + CanRerun bool `json:"canRerun"` + CanRerunFailed bool `json:"canRerunFailed"` + CanDeleteArtifact bool `json:"canDeleteArtifact"` + Done bool `json:"done"` + WorkflowID string `json:"workflowID"` + WorkflowLink string `json:"workflowLink"` + CanViewWorkflowFile bool `json:"canViewWorkflowFile"` + IsSchedule bool `json:"isSchedule"` + RunAttempt int64 `json:"runAttempt"` + Attempts []*ViewRunAttempt `json:"attempts"` + Jobs []*ViewJob `json:"jobs"` + Commit ViewCommit `json:"commit"` + PullRequest *ViewPullRequest `json:"pullRequest,omitempty"` // Summary view: run duration and trigger time/event Duration string `json:"duration"` TriggeredAt int64 `json:"triggeredAt"` // unix seconds for relative time @@ -600,6 +608,11 @@ func fillViewRunResponseSummary(ctx *context_module.Context, resp *ViewResponse, if isLatestAttempt { resp.State.Run.WorkflowLink = run.WorkflowLink() } + resp.State.Run.CanViewWorkflowFile = true + if run.IsScopedRun { + // For a scoped run the workflow file lives in the source repo; only show its link when the viewer can read that repo. + resp.State.Run.CanViewWorkflowFile = canViewScopedWorkflowFile(ctx, run) + } resp.State.Run.IsSchedule = run.IsSchedule() resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json for _, v := range jobs { @@ -848,7 +861,16 @@ func checkRunRerunAllowed(ctx *context_module.Context, run *actions_model.Action } cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() - if cfg.IsWorkflowDisabled(run.WorkflowID) { + disabled := cfg.IsWorkflowDisabled(run.WorkflowID) + if run.IsScopedRun { + optedOut, err := actions_model.IsScopedWorkflowOptedOut(ctx, cfg, ctx.Repo.Repository.OwnerID, run.WorkflowRepoID, run.WorkflowID) + if err != nil { + ctx.ServerError("IsScopedWorkflowOptedOut", err) + return false + } + disabled = optedOut + } + if disabled { ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled")) return false } @@ -1276,7 +1298,24 @@ func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) { cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() - if isEnable { + scopedRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + if scopedRepoID > 0 { + if !isEnable { + // a required scoped workflow can never be opted out + required, err := actions_model.IsScopedWorkflowRequired(ctx, ctx.Repo.Repository.OwnerID, scopedRepoID, workflow) + if err != nil { + ctx.ServerError("IsScopedWorkflowRequired", err) + return + } + if required { + ctx.JSONError(ctx.Locale.Tr("actions.workflow.scoped_required_cannot_disable")) + return + } + cfg.DisableScopedWorkflow(scopedRepoID, workflow) + } else { + cfg.EnableScopedWorkflow(scopedRepoID, workflow) + } + } else if isEnable { cfg.EnableWorkflow(workflow) } else { cfg.DisableWorkflow(workflow) @@ -1293,13 +1332,13 @@ func disableOrEnableWorkflowFile(ctx *context_module.Context, isEnable bool) { ctx.Flash.Success(ctx.Tr("actions.workflow.disable_success", workflow)) } - redirectURL := actionsListRedirectURL(ctx.Repo.RepoLink, workflow, + redirectURL := actionsListRedirectURL(ctx.Repo.RepoLink, workflow, ctx.FormString("scoped_workflow_source_repo_id"), ctx.FormString("actor"), ctx.FormString("status"), ctx.FormString("branch")) ctx.JSONRedirect(redirectURL) } func Run(ctx *context_module.Context) { - redirectURL := actionsListRedirectURL(ctx.Repo.RepoLink, ctx.FormString("workflow"), + redirectURL := actionsListRedirectURL(ctx.Repo.RepoLink, ctx.FormString("workflow"), ctx.FormString("scoped_workflow_source_repo_id"), ctx.FormString("actor"), ctx.FormString("status"), ctx.FormString("branch")) workflowID := ctx.FormString("workflow") @@ -1313,7 +1352,8 @@ func Run(ctx *context_module.Context) { ctx.ServerError("ref", nil) return } - _, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { + sourceRepoID := ctx.FormInt64("scoped_workflow_source_repo_id") + _, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, sourceRepoID, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { for name, config := range workflowDispatch.Inputs { value := ctx.Req.PostFormValue(name) if config.Type == "boolean" { @@ -1339,3 +1379,65 @@ func Run(ctx *context_module.Context) { ctx.Flash.Success(ctx.Tr("actions.workflow.run_success", workflowID)) ctx.Redirect(redirectURL) } + +// viewScopedWorkflowFile redirects to the scoped workflow file in its SOURCE repo. +func viewScopedWorkflowFile(ctx *context_module.Context, run *actions_model.ActionRun) { + sourceRepo, err := repo_model.GetRepositoryByID(ctx, run.WorkflowRepoID) + if err != nil { + ctx.NotFoundOrServerError("GetRepositoryByID", func(err error) bool { + return errors.Is(err, util.ErrNotExist) + }, err) + return + } + + perm, err := access_model.GetDoerRepoPermission(ctx, sourceRepo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + if !perm.CanRead(unit.TypeCode) { + ctx.NotFound(nil) + return + } + + sourceGitRepo, err := gitrepo.OpenRepository(ctx, sourceRepo) + if err != nil { + ctx.ServerError("OpenRepository", err) + return + } + defer sourceGitRepo.Close() + + commit, err := sourceGitRepo.GetCommit(run.WorkflowCommitSHA) + if err != nil { + ctx.NotFoundOrServerError("GetCommit", func(err error) bool { + return errors.Is(err, util.ErrNotExist) + }, err) + return + } + rpath, entries, err := actions.ListScopedWorkflows(commit) + if err != nil { + ctx.ServerError("ListScopedWorkflows", err) + return + } + for _, entry := range entries { + if entry.Name() == run.WorkflowID { + ctx.Redirect(fmt.Sprintf("%s/src/commit/%s/%s/%s", sourceRepo.Link(), url.PathEscape(run.WorkflowCommitSHA), util.PathEscapeSegments(rpath), util.PathEscapeSegments(run.WorkflowID))) + return + } + } + ctx.NotFound(nil) +} + +// canViewScopedWorkflowFile reports whether the viewer may follow the "Workflow file" link of a scoped run. +func canViewScopedWorkflowFile(ctx *context_module.Context, run *actions_model.ActionRun) bool { + sourceRepo, err := repo_model.GetRepositoryByID(ctx, run.WorkflowRepoID) + if err != nil { + return false + } + perm, err := access_model.GetDoerRepoPermission(ctx, sourceRepo, ctx.Doer) + if err != nil { + log.Error("GetUserRepoPermission: %v", err) + return false + } + return perm.CanRead(unit.TypeCode) +} diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index ac5a3beb03..5774903c19 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -949,7 +949,9 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue * // admin can merge without checks, writer can merge when checks succeed // admin and writer both can make an auto merge schedule (not affected by overridable blockers) - data.hasStatusCheckBlocker = data.enableStatusCheck && !data.StatusCheckData.RequiredChecksState.IsSuccess() + // Required scoped workflow checks gate the merge even when the rule's own status check is disabled (see IsPullCommitStatusPass), + // so block on any required status context, not only when enableStatusCheck is on. + data.hasStatusCheckBlocker = (data.enableStatusCheck || data.hasRequiredStatusContexts) && !data.StatusCheckData.RequiredChecksState.IsSuccess() // this logic is from: // {{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess))}} diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 81283a2538..62b956d06c 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -276,6 +276,10 @@ type pullMergeBoxData struct { enableStatusCheck bool StatusCheckData *pullCommitStatusCheckData ShowStatusCheck bool + // hasRequiredStatusContexts is true when at least one required status-check context must be satisfied: + // the branch protection's own contexts and/or required scoped workflow checks. + // The latter gate the merge even when the rule's own status check is disabled. + hasRequiredStatusContexts bool hasOverridableBlockers bool canMergeNow bool // PR is mergeable, either no blocker, or doer can bypass the blockers @@ -423,6 +427,16 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C if err != nil { log.Error("GetLatestCommitStatus: %v", err) } + + // Effective required contexts = branch-protection contexts + required scoped workflow checks. + requiredContexts := pbRequiredContexts + if effective, err := pull_service.EffectiveRequiredContexts(ctx, ctx.Repo.Repository, prInfo.ProtectedBranchRule); err != nil { + log.Error("EffectiveRequiredContexts: %v", err) + } else { + requiredContexts = effective + } + data.hasRequiredStatusContexts = len(requiredContexts) > 0 + if !ctx.Repo.Permission.CanRead(unit.TypeActions) { git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) } @@ -433,7 +447,9 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C statusCheckData.pullCommitStatusState = combinedCommitStatus.State } - data.ShowStatusCheck = data.enableStatusCheck || len(statusCheckData.PullCommitStatuses) > 0 + // Required scoped workflow checks gate the merge even when the branch protection's own status check is disabled, + // so the status-check section must render when there are any required contexts, not only when enableStatusCheck is on. + data.ShowStatusCheck = data.enableStatusCheck || data.hasRequiredStatusContexts || len(statusCheckData.PullCommitStatuses) > 0 runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses) if err != nil { @@ -449,7 +465,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C } var missingRequiredChecks []string - for _, requiredContext := range pbRequiredContexts { + for _, requiredContext := range requiredContexts { contextFound := false matchesRequiredContext := createRequiredContextMatcher(requiredContext) for _, presentStatus := range commitStatuses { @@ -466,7 +482,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C statusCheckData.MissingRequiredChecks = missingRequiredChecks statusCheckData.IsContextRequired = func(context string) bool { - for _, c := range pbRequiredContexts { + for _, c := range requiredContexts { if c == context { return true } @@ -481,9 +497,9 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.C } return false } - statusCheckData.RequiredChecksState = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pbRequiredContexts) + statusCheckData.RequiredChecksState = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts) - if data.enableStatusCheck { + if data.enableStatusCheck || data.hasRequiredStatusContexts { if statusCheckData.RequiredChecksState.IsError() || statusCheckData.RequiredChecksState.IsFailure() { data.infoProtectionBlockers.AddErrorItem(ctx.Locale.Tr("repo.pulls.required_status_check_failed")) } else if !statusCheckData.RequiredChecksState.IsSuccess() { diff --git a/routers/web/repo/pull_merge_box.go b/routers/web/repo/pull_merge_box.go index 4a167c2912..d76aacde2f 100644 --- a/routers/web/repo/pull_merge_box.go +++ b/routers/web/repo/pull_merge_box.go @@ -63,7 +63,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxIconColor() { showAsWarningColor = showAsWarningColor || statusCheckData.pullCommitStatusState.IsWarning() || statusCheckData.pullCommitStatusState.IsPending() || - (mergeBoxData.enableStatusCheck && (statusCheckData.RequiredChecksState.IsWarning() || statusCheckData.RequiredChecksState.IsPending())) + ((mergeBoxData.enableStatusCheck || mergeBoxData.hasRequiredStatusContexts) && (statusCheckData.RequiredChecksState.IsWarning() || statusCheckData.RequiredChecksState.IsPending())) } hasBlockers := len(mergeBoxData.infoCommitBlockers.items) > 0 || len(mergeBoxData.infoProtectionBlockers.items) > 0 diff --git a/routers/web/shared/actions/scoped_workflows.go b/routers/web/shared/actions/scoped_workflows.go new file mode 100644 index 0000000000..0db7c51819 --- /dev/null +++ b/routers/web/shared/actions/scoped_workflows.go @@ -0,0 +1,368 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "errors" + "net/http" + "slices" + "strings" + + actions_model "gitea.dev/models/actions" + repo_model "gitea.dev/models/repo" + actions_module "gitea.dev/modules/actions" + "gitea.dev/modules/actions/jobparser" + "gitea.dev/modules/container" + "gitea.dev/modules/log" + "gitea.dev/modules/setting" + "gitea.dev/modules/templates" + "gitea.dev/modules/util" + shared_user "gitea.dev/routers/web/shared/user" + actions_service "gitea.dev/services/actions" + "gitea.dev/services/context" +) + +const ( + tplOrgScopedWorkflows templates.TplName = "org/settings/actions" + tplUserScopedWorkflows templates.TplName = "user/settings/actions" + tplAdminScopedWorkflows templates.TplName = "admin/actions" +) + +type scopedWorkflowsCtx struct { + OwnerID int64 // 0 = instance-level + IsOrg bool + IsUser bool + IsGlobal bool + Template templates.TplName + RedirectLink string + // SearchUID is the uid passed to the repo-search box. For org/user it scopes the search to that owner; + // for admin (0) it searches all repos and therefore requires admin access on the route. + SearchUID int64 +} + +func getScopedWorkflowsCtx(ctx *context.Context) (*scopedWorkflowsCtx, error) { + if ctx.Data["PageIsOrgSettings"] == true { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return nil, nil //nolint:nilnil // error is already handled by ctx.ServerError + } + return &scopedWorkflowsCtx{ + OwnerID: ctx.Org.Organization.ID, + IsOrg: true, + Template: tplOrgScopedWorkflows, + RedirectLink: ctx.Org.OrgLink + "/settings/actions/scoped-workflows", + SearchUID: ctx.Org.Organization.ID, + }, nil + } + + if ctx.Data["PageIsUserSettings"] == true { + return &scopedWorkflowsCtx{ + OwnerID: ctx.Doer.ID, + IsUser: true, + Template: tplUserScopedWorkflows, + RedirectLink: setting.AppSubURL + "/user/settings/actions/scoped-workflows", + SearchUID: ctx.Doer.ID, + }, nil + } + + if ctx.Data["PageIsAdmin"] == true { + return &scopedWorkflowsCtx{ + OwnerID: 0, + IsGlobal: true, + Template: tplAdminScopedWorkflows, + RedirectLink: setting.AppSubURL + "/-/admin/actions/scoped-workflows", + SearchUID: 0, + }, nil + } + + return nil, errors.New("unable to set scoped workflows context") +} + +// scopedWorkflowInfo is one scoped workflow shown on the settings page, merged with its stored merge-gate config. +type scopedWorkflowInfo struct { + EntryName string + DisplayName string + Required bool + Patterns string // newline-joined stored status-check patterns (kept even when not required, as history) + Contexts []string // the commit-status contexts this workflow is expected to post, to preview which patterns match + Missing bool // the workflow file no longer exists on the source default branch, but a stored config lingers and must stay clearable +} + +// scopedWorkflowSourceView is the per-source data shown on the settings page. +type scopedWorkflowSourceView struct { + Repo *repo_model.Repository + ScopedWorkflowInfos []scopedWorkflowInfo +} + +func ScopedWorkflows(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("actions.scoped_workflows") + ctx.Data["PageType"] = "scoped-workflows" + ctx.Data["PageIsSharedSettingsScopedWorkflows"] = true + + swCtx, err := getScopedWorkflowsCtx(ctx) + if err != nil { + ctx.ServerError("getScopedWorkflowsCtx", err) + return + } + if ctx.Written() { + return + } + + switch { + case swCtx.IsOrg: + ctx.Data["ScopedWorkflowsDesc"] = ctx.Tr("actions.scoped_workflows.desc_org") + case swCtx.IsUser: + ctx.Data["ScopedWorkflowsDesc"] = ctx.Tr("actions.scoped_workflows.desc_user") + default: // instance-level + ctx.Data["ScopedWorkflowsDesc"] = ctx.Tr("actions.scoped_workflows.desc_global") + } + + sources, err := actions_model.GetScopedWorkflowSourcesByOwner(ctx, swCtx.OwnerID) + if err != nil { + ctx.ServerError("GetScopedWorkflowSourcesByOwner", err) + return + } + + views := make([]*scopedWorkflowSourceView, 0, len(sources)) + for _, src := range sources { + repo, err := repo_model.GetRepositoryByID(ctx, src.SourceRepoID) + if err != nil { + log.Error("scoped workflows settings: load source repo %d: %v", src.SourceRepoID, err) + continue + } + views = append(views, &scopedWorkflowSourceView{ + Repo: repo, + ScopedWorkflowInfos: listSourceScopedWorkflowFiles(ctx, repo, src.WorkflowConfigs), + }) + } + + ctx.Data["ScopedWorkflowSources"] = views + ctx.Data["RepoSearchUID"] = swCtx.SearchUID + // owner/user scopes the repo search to the owner (exclusive); + // instance-level (admin) searches all repos and so must submit owner/name to disambiguate the selection across owners. + ctx.Data["ScopedWorkflowsSearchExclusive"] = !swCtx.IsGlobal + ctx.Data["ScopedWorkflowsSearchFullName"] = swCtx.IsGlobal + ctx.Data["RedirectLink"] = swCtx.RedirectLink + ctx.Data["ScopedWorkflowDirs"] = strings.Join(setting.Actions.ScopedWorkflowDirs, ", ") + ctx.HTML(http.StatusOK, swCtx.Template) +} + +// parsePatternLines splits a textarea value into trimmed, non-empty status-check patterns (one per line). +func parsePatternLines(raw string) []string { + var patterns []string + for line := range strings.SplitSeq(raw, "\n") { + if p := strings.TrimSpace(line); p != "" { + patterns = append(patterns, p) + } + } + return patterns +} + +// deriveScopedStatusContexts returns the commit-status contexts a scoped workflow is expected to post on a consumer: +// ": / ()" for each parsed job (matrix-expanded, matching run creation) and triggering event. +// Job names that depend on run-context expressions cannot resolve here (no run context) and appear as authored; a glob pattern still matches them. +func deriveScopedStatusContexts(prefix, displayName string, content []byte, events []*jobparser.Event) []string { + parsed, err := jobparser.Parse(content) + if err != nil { + return nil + } + eventNames := make([]string, 0, len(events)) + for _, e := range events { + // only events whose runs post a commit status can be a required check; workflow_dispatch, schedule, etc. post none. + if actions_module.ShouldEventCreateCommitStatus(e.Name) { + eventNames = append(eventNames, e.Name) + } + } + seen := make(container.Set[string]) + contexts := make([]string, 0, len(parsed)*len(eventNames)) + for _, sw := range parsed { + _, job := sw.Job() + if job == nil { + continue + } + jobName := util.EllipsisDisplayString(job.Name, 255) // run creation truncates job names the same way + for _, ev := range eventNames { + ctxName := actions_module.ScopedWorkflowStatusContextName(prefix, displayName, jobName, ev) + if seen.Contains(ctxName) { + continue + } + seen.Add(ctxName) + contexts = append(contexts, ctxName) + } + } + return contexts +} + +func listSourceScopedWorkflowFiles(ctx *context.Context, repo *repo_model.Repository, configs map[string]*actions_model.ScopedWorkflowConfig) []scopedWorkflowInfo { + rendered := make(container.Set[string], len(configs)) + files := make([]scopedWorkflowInfo, 0, len(configs)) + + // An empty source repo (or one that fails to parse) has no live workflow files, but a previously-saved config may still linger; + // fall through to surface those as orphan rows below so they remain clearable. + if !repo.IsEmpty { + _, parsed, err := actions_service.LoadParsedScopedWorkflows(ctx, repo) + if err != nil { + log.Error("scoped workflows settings: parse %s: %v", repo.RelativePath(), err) + } else { + for _, p := range parsed { + info := scopedWorkflowInfo{ + EntryName: p.EntryName, + DisplayName: p.DisplayName, + Contexts: deriveScopedStatusContexts(repo.FullName(), p.DisplayName, p.Content, p.Events), + } + if cfg := configs[p.EntryName]; cfg != nil { + info.Required = cfg.Required + info.Patterns = strings.Join(cfg.Patterns, "\n") + } + rendered.Add(p.EntryName) + files = append(files, info) + } + } + } + + // Surface configs whose workflow file no longer exists on the source default branch as orphan rows. + // A required orphan still gates merges (must-present), so the owner/admin must be able to see and clear it; + // otherwise the only escape would be removing the whole source registration. + orphans := make([]scopedWorkflowInfo, 0, len(configs)) + for name, cfg := range configs { + if cfg == nil || rendered.Contains(name) { + continue + } + orphans = append(orphans, scopedWorkflowInfo{ + EntryName: name, + DisplayName: name, + Required: cfg.Required, + Patterns: strings.Join(cfg.Patterns, "\n"), + Missing: true, + }) + } + // map iteration order is random; sort orphans for a stable settings page + slices.SortFunc(orphans, func(a, b scopedWorkflowInfo) int { return strings.Compare(a.EntryName, b.EntryName) }) + return append(files, orphans...) +} + +func ScopedWorkflowAdd(ctx *context.Context) { + swCtx, err := getScopedWorkflowsCtx(ctx) + if err != nil { + ctx.ServerError("getScopedWorkflowsCtx", err) + return + } + if ctx.Written() { + return + } + + repoName := ctx.FormString("repo_name") + var repo *repo_model.Repository + if swCtx.IsGlobal { + // instance-level: the source may be any repo on the instance, identified by owner/name + ownerName, name, ok := strings.Cut(repoName, "/") + if !ok { + ctx.JSONError(ctx.Tr("actions.scoped_workflows.source.not_found")) + return + } + repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ownerName, name) + } else { + // owner-level: resolve within the owner, which also enforces that the source is one of the owner's own repositories + repo, err = repo_model.GetRepositoryByName(ctx, swCtx.OwnerID, repoName) + } + if err != nil { + ctx.JSONError(ctx.Tr("actions.scoped_workflows.source.not_found")) + return + } + + if err := actions_model.AddScopedWorkflowSource(ctx, swCtx.OwnerID, repo.ID); err != nil { + ctx.ServerError("AddScopedWorkflowSource", err) + return + } + ctx.Flash.Success(ctx.Tr("actions.scoped_workflows.source.add_success")) + ctx.JSONRedirect(swCtx.RedirectLink) +} + +func ScopedWorkflowSetRequired(ctx *context.Context) { + swCtx, err := getScopedWorkflowsCtx(ctx) + if err != nil { + ctx.ServerError("getScopedWorkflowsCtx", err) + return + } + if ctx.Written() { + return + } + + repoID := ctx.FormInt64("repo_id") + + // the source must be registered for this owner + if _, err := actions_model.GetScopedWorkflowSource(ctx, swCtx.OwnerID, repoID); err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.JSONError(ctx.Tr("actions.scoped_workflows.source.not_found")) + } else { + ctx.ServerError("GetScopedWorkflowSource", err) + } + return + } + + // Live workflow entry names on the source default branch, used to distinguish orphan configs (whose workflow file no longer exists) from live ones. + sourceRepo, err := repo_model.GetRepositoryByID(ctx, repoID) + if err != nil { + ctx.ServerError("GetRepositoryByID", err) + return + } + liveSet := make(container.Set[string]) + if !sourceRepo.IsEmpty { // an empty source has no live workflows + _, parsed, err := actions_service.LoadParsedScopedWorkflows(ctx, sourceRepo) + if err != nil { + ctx.ServerError("LoadParsedScopedWorkflows", err) + return + } + for _, p := range parsed { + liveSet.Add(p.EntryName) + } + } + + // Every workflow row submits its ID in workflow_ids and its patterns (one per line) in required_patterns[]; + // checked rows additionally submit their ID in required_workflow_ids. + // A required workflow must have at least one pattern. + requiredSet := make(container.Set[string]) + for _, workflowID := range ctx.FormStrings("required_workflow_ids") { + requiredSet.Add(workflowID) + } + configs := make(map[string]*actions_model.ScopedWorkflowConfig) + for _, workflowID := range ctx.FormStrings("workflow_ids") { + patterns := parsePatternLines(ctx.FormString("required_patterns[" + workflowID + "]")) + required := requiredSet.Contains(workflowID) + if required && len(patterns) == 0 { + ctx.JSONError(ctx.Tr("actions.scoped_workflows.required.patterns_empty")) + return + } + // Keep a config only if it is required, or it is a still-existing. + // An orphan (file no longer in the source) that is not required is dropped. + if required || (liveSet.Contains(workflowID) && len(patterns) > 0) { + configs[workflowID] = &actions_model.ScopedWorkflowConfig{Required: required, Patterns: patterns} + } + } + if err := actions_model.SetScopedWorkflowSourceConfigs(ctx, swCtx.OwnerID, repoID, configs); err != nil { + ctx.ServerError("SetScopedWorkflowSourceConfigs", err) + return + } + ctx.Flash.Success(ctx.Tr("actions.scoped_workflows.required.update_success")) + ctx.JSONRedirect(swCtx.RedirectLink) +} + +func ScopedWorkflowRemove(ctx *context.Context) { + swCtx, err := getScopedWorkflowsCtx(ctx) + if err != nil { + ctx.ServerError("getScopedWorkflowsCtx", err) + return + } + if ctx.Written() { + return + } + + repoID := ctx.FormInt64("repo_id") + if err := actions_model.RemoveScopedWorkflowSource(ctx, swCtx.OwnerID, repoID); err != nil { + ctx.ServerError("RemoveScopedWorkflowSource", err) + return + } + ctx.Flash.Success(ctx.Tr("actions.scoped_workflows.source.remove_success")) + ctx.JSONRedirect(swCtx.RedirectLink) +} diff --git a/routers/web/shared/actions/scoped_workflows_test.go b/routers/web/shared/actions/scoped_workflows_test.go new file mode 100644 index 0000000000..7d96c431ee --- /dev/null +++ b/routers/web/shared/actions/scoped_workflows_test.go @@ -0,0 +1,75 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + actions_module "gitea.dev/modules/actions" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeriveScopedStatusContexts(t *testing.T) { + t.Run("jobs x events; job name is its name: or its id", func(t *testing.T) { + content := []byte(`name: CI +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - run: echo + build: + name: Build It + runs-on: ubuntu-latest + steps: + - run: echo +`) + events, err := actions_module.GetEventsFromContent(content) + require.NoError(t, err) + got := deriveScopedStatusContexts("org/src", "CI", content, events) + assert.ElementsMatch(t, []string{ + "org/src: CI / lint (push)", + "org/src: CI / lint (pull_request)", + "org/src: CI / Build It (push)", + "org/src: CI / Build It (pull_request)", + }, got) + }) + + t.Run("only status-producing events; workflow_dispatch/schedule/workflow_call skipped", func(t *testing.T) { + content := []byte(`name: CI +on: + push: + workflow_dispatch: + workflow_call: + schedule: + - cron: "0 0 * * *" +jobs: + j: + runs-on: ubuntu-latest + steps: + - run: echo +`) + events, err := actions_module.GetEventsFromContent(content) + require.NoError(t, err) + got := deriveScopedStatusContexts("org/src", "CI", content, events) + assert.Equal(t, []string{"org/src: CI / j (push)"}, got) // only push posts a commit status + }) + + t.Run("a workflow_dispatch-only workflow has no expected contexts", func(t *testing.T) { + content := []byte(`name: Deploy +on: workflow_dispatch +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - run: echo +`) + events, err := actions_module.GetEventsFromContent(content) + require.NoError(t, err) + got := deriveScopedStatusContexts("org/src", "Deploy", content, events) + assert.Empty(t, got) // workflow_dispatch posts no commit status -> nothing to preview (and it cannot be a required check) + }) +} diff --git a/routers/web/web.go b/routers/web/web.go index 4525516f5a..0bfae44e86 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -500,6 +500,15 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { }) } + addSettingsScopedWorkflowsRoutes := func() { + m.Group("/scoped-workflows", func() { + m.Get("", shared_actions.ScopedWorkflows) + m.Post("/add", shared_actions.ScopedWorkflowAdd) + m.Post("/required", shared_actions.ScopedWorkflowSetRequired) + m.Post("/remove", shared_actions.ScopedWorkflowRemove) + }) + } + // FIXME: not all routes need go through same middleware. // Especially some AJAX requests, we can reduce middleware number to improve performance. @@ -702,6 +711,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { addSettingsRunnersRoutes() addSettingsSecretsRoutes() addSettingsVariablesRoutes() + addSettingsScopedWorkflowsRoutes() }, actions.MustEnableActions) m.Get("/organization", user_setting.Organization) @@ -865,6 +875,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { addSettingsRunnersRoutes() m.Post("/runners/bulk", shared_actions.RunnerBulkActionPost) addSettingsVariablesRoutes() + addSettingsScopedWorkflowsRoutes() }) }, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled)) // ***** END: Admin ***** @@ -1022,6 +1033,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { addSettingsRunnersRoutes() addSettingsSecretsRoutes() addSettingsVariablesRoutes() + addSettingsScopedWorkflowsRoutes() }, actions.MustEnableActions) m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRenamePost) diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 2b8ed19f92..1fa3fd027e 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -7,8 +7,6 @@ import ( "context" "errors" "fmt" - "path" - "strings" actions_model "gitea.dev/models/actions" "gitea.dev/models/db" @@ -16,7 +14,6 @@ import ( repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" actions_module "gitea.dev/modules/actions" - "gitea.dev/modules/actions/jobparser" "gitea.dev/modules/commitstatus" "gitea.dev/modules/log" "gitea.dev/modules/util" @@ -45,8 +42,14 @@ func CreateCommitStatusForRunJobs(ctx context.Context, run *actions_model.Action return } + // Compute the scoped source-repo prefix once per run; it is identical for every job. + var scopedPrefix string + if run.IsScopedRun { + scopedPrefix = actions_model.ScopedStatusContextPrefix(ctx, run.WorkflowRepoID) + } + for _, job := range jobs { - if err = createCommitStatus(ctx, run.Repo, event, commitID, run, job); err != nil { + if err = createCommitStatus(ctx, run.Repo, event, commitID, scopedPrefix, run, job); err != nil { log.Error("Failed to create commit status for job %d: %v", job.ID, err) } } @@ -136,16 +139,15 @@ func getCommitStatusEventNameAndCommitID(run *actions_model.ActionRun) (event, c return event, commitID, nil } -func createCommitStatus(ctx context.Context, repo *repo_model.Repository, event, commitID string, run *actions_model.ActionRun, job *actions_model.ActionRunJob) error { - // TODO: store workflow name as a field in ActionRun to avoid parsing - runName := path.Base(run.WorkflowID) - // fall back to the file name when the workflow has no non-blank `name:` - if wfs, err := jobparser.Parse(job.WorkflowPayload); err == nil && len(wfs) > 0 { - if name := strings.TrimSpace(wfs[0].Name); name != "" { - runName = name - } +func createCommitStatus(ctx context.Context, repo *repo_model.Repository, event, commitID, scopedPrefix string, run *actions_model.ActionRun, job *actions_model.ActionRunJob) error { + displayName := actions_module.WorkflowDisplayName(run.WorkflowID, job.WorkflowPayload) + ctxName := actions_module.WorkflowStatusContextName(displayName, job.Name, event) // git_model.NewCommitStatus also trims spaces + if run.IsScopedRun { + // A scoped run is prefixed with its source repo (set off by a colon) so it stays distinct from a same-named repo-level workflow. + // scopedPrefix is computed once per run by the caller. The settings page derives the same string to preview expected checks. + ctxName = actions_module.ScopedWorkflowStatusContextName(scopedPrefix, displayName, job.Name, event) } - ctxName := strings.TrimSpace(fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)) // git_model.NewCommitStatus also trims spaces + // Mix the workflow file path into the hash so two workflow files that // share the same `name:` and job name produce distinct commit statuses // even though they render identically — matching GitHub's behavior diff --git a/services/actions/commit_status_test.go b/services/actions/commit_status_test.go index e20dcf8511..af71d16e20 100644 --- a/services/actions/commit_status_test.go +++ b/services/actions/commit_status_test.go @@ -72,7 +72,7 @@ func TestCreateCommitStatus_Dedupe(t *testing.T) { expectedContext := "status-dedupe-test.yaml / status-dedupe-job (push)" expectedTargetURL := run.Link() + "/jobs/99002" - require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), "", run, job)) statuses := findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext) require.Len(t, statuses, 1) @@ -81,7 +81,7 @@ func TestCreateCommitStatus_Dedupe(t *testing.T) { assert.Equal(t, expectedTargetURL, statuses[0].TargetURL) job.Status = actions_model.StatusRunning - require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), "", run, job)) statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext) require.Len(t, statuses, 2) @@ -90,12 +90,12 @@ func TestCreateCommitStatus_Dedupe(t *testing.T) { assert.Equal(t, "In progress", statuses[1].Description) assert.Equal(t, expectedTargetURL, statuses[1].TargetURL) - require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), "", run, job)) statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext) assert.Len(t, statuses, 2) job.Status = actions_model.StatusSuccess - require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", commit.ID.String(), "", run, job)) statuses = findCommitStatusesForContext(t, repo.ID, commit.ID.String(), expectedContext) require.Len(t, statuses, 3) assert.Equal(t, commitstatus.CommitStatusSuccess, statuses[2].State) @@ -126,7 +126,7 @@ func TestGetCommitActionsStatusMap(t *testing.T) { RunID: run.ID, RepoID: repo.ID, OwnerID: repo.OwnerID, Name: tc.jobName, Status: tc.status, } require.NoError(t, db.Insert(t.Context(), job)) - require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, "", run, job)) } statuses, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, branch.CommitID, db.ListOptionsAll) @@ -185,7 +185,7 @@ jobs: WorkflowPayload: payload, } require.NoError(t, db.Insert(t.Context(), job)) - require.NoError(t, createCommitStatus(t.Context(), repo, "pull_request", branch.CommitID, run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "pull_request", branch.CommitID, "", run, job)) } statuses, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, branch.CommitID, db.ListOptionsAll) @@ -242,7 +242,7 @@ func TestCreateCommitStatus_LegacyHashRecovery(t *testing.T) { Name: "my-job", Status: actions_model.StatusSuccess, } require.NoError(t, db.Insert(t.Context(), job)) - require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, "", run, job)) latest, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, branch.CommitID, db.ListOptionsAll) require.NoError(t, err) @@ -292,7 +292,7 @@ func TestCreateCommitStatus_UnnamedWorkflowUsesFileName(t *testing.T) { `), } require.NoError(t, db.Insert(t.Context(), job)) - require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, run, job)) + require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, "", run, job)) statuses := findCommitStatusesForContext(t, repo.ID, branch.CommitID, tc.workflowID+" / my-test (push)") require.Len(t, statuses, 1) @@ -300,6 +300,67 @@ func TestCreateCommitStatus_UnnamedWorkflowUsesFileName(t *testing.T) { } } +// TestCreateCommitStatus_ScopedSourcePrefix: a scoped run's commit status Context is prefixed with the source repo's full name, +// so it is distinct (display AND hash) from a same-named repo-level workflow. +func TestCreateCommitStatus_ScopedSourcePrefix(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + consumer := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + source := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + branch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: consumer.ID, Name: consumer.DefaultBranch}) + + payload := []byte(`name: ci +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo hi +`) + + // A repo-level run and a scoped run share the same workflow name and job name; + // only the scoped one points its content source at another repo (WorkflowRepoID=source.ID, IsScopedRun=true). + for _, spec := range []struct { + runID, jobID int64 + scoped bool + }{ + {99501, 99511, false}, + {99502, 99512, true}, + } { + workflowRepoID := consumer.ID + if spec.scoped { + workflowRepoID = source.ID + } + run := &actions_model.ActionRun{ + ID: spec.runID, Index: spec.runID, RepoID: consumer.ID, Repo: consumer, OwnerID: consumer.OwnerID, TriggerUserID: consumer.OwnerID, + WorkflowID: "ci.yaml", CommitSHA: branch.CommitID, + WorkflowRepoID: workflowRepoID, WorkflowCommitSHA: branch.CommitID, IsScopedRun: spec.scoped, + } + require.NoError(t, db.Insert(t.Context(), run)) + job := &actions_model.ActionRunJob{ + ID: spec.jobID, RunID: run.ID, RepoID: consumer.ID, OwnerID: consumer.OwnerID, + Name: "build", Status: actions_model.StatusWaiting, WorkflowPayload: payload, + } + require.NoError(t, db.Insert(t.Context(), job)) + // mirror CreateCommitStatusForRunJobs: compute the scoped prefix once per run + scopedPrefix := "" + if run.IsScopedRun { + scopedPrefix = actions_model.ScopedStatusContextPrefix(t.Context(), run.WorkflowRepoID) + } + require.NoError(t, createCommitStatus(t.Context(), consumer, "push", branch.CommitID, scopedPrefix, run, job)) + } + + // repo-level Context is the bare " / ()"; the scoped one is the same but sets off the source repo with a colon, + // so the two stay distinct (and have different hashes) despite the same `name:`. + repoStatuses := findCommitStatusesForContext(t, consumer.ID, branch.CommitID, "ci / build (push)") + require.Len(t, repoStatuses, 1) + scopedStatuses := findCommitStatusesForContext(t, consumer.ID, branch.CommitID, source.FullName()+": ci / build (push)") + require.Len(t, scopedStatuses, 1) + + assert.NotEqual(t, repoStatuses[0].ContextHash, scopedStatuses[0].ContextHash, + "scoped status must not collide with the same-named repo-level workflow") +} + func findCommitStatusesForContext(t *testing.T, repoID int64, sha, context string) []*git_model.CommitStatus { t.Helper() diff --git a/services/actions/context_test.go b/services/actions/context_test.go index 252399a37d..8100f1e238 100644 --- a/services/actions/context_test.go +++ b/services/actions/context_test.go @@ -63,16 +63,18 @@ jobs: `) run := &actions_model.ActionRun{ - Title: "before parse", - RepoID: 4, - OwnerID: 1, - WorkflowID: "expr-runid.yaml", - TriggerUserID: 1, - Ref: "refs/heads/master", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - EventPayload: "{}", + Title: "before parse", + RepoID: 4, + OwnerID: 1, + WorkflowID: "expr-runid.yaml", + TriggerUserID: 1, + Ref: "refs/heads/master", + CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", + Event: "push", + TriggerEvent: "push", + EventPayload: "{}", + WorkflowRepoID: 4, + WorkflowCommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", } require.NoError(t, PrepareRunAndInsert(ctx, content, run, nil)) require.Positive(t, run.ID) diff --git a/services/actions/notifier.go b/services/actions/notifier.go index 5670c2a634..ae8ff2b0e7 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -16,7 +16,6 @@ import ( repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" "gitea.dev/modules/git" - "gitea.dev/modules/gitrepo" "gitea.dev/modules/log" "gitea.dev/modules/repository" "gitea.dev/modules/setting" @@ -802,21 +801,17 @@ func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *rep status := convert.ToWorkflowRunAction(run.Status) - gitRepo, err := gitrepo.OpenRepository(ctx, repo) + convertedWorkflow, err := convert.ResolveActionWorkflowForRun(ctx, repo, run) if err != nil { - log.Error("OpenRepository: %v", err) + if errors.Is(err, util.ErrNotExist) { + // The workflow definition is gone (e.g. a scoped source repo/file was deleted, or the file no longer exists at the recorded commit), skip + log.Debug("WorkflowRunStatusUpdate: workflow %q for run %d not found: %v", run.WorkflowID, run.ID, err) + return + } + log.Error("WorkflowRunStatusUpdate resolve workflow: %v", err) return } - defer gitRepo.Close() - convertedWorkflow, err := convert.GetActionWorkflowByRef(ctx, gitRepo, repo, run.WorkflowID, git.RefName(run.Ref)) - if err != nil && errors.Is(err, util.ErrNotExist) { - convertedWorkflow, err = convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID) - } - if err != nil { - log.Error("GetActionWorkflow: %v", err) - return - } run.Repo = repo convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false) if err != nil { diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index e67d1028f7..7a8b5bd67e 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -19,6 +19,7 @@ import ( unit_model "gitea.dev/models/unit" user_model "gitea.dev/models/user" actions_module "gitea.dev/modules/actions" + "gitea.dev/modules/container" "gitea.dev/modules/git" "gitea.dev/modules/gitrepo" "gitea.dev/modules/json" @@ -239,7 +240,11 @@ func notify(ctx context.Context, input *notifyInput) error { } } - return handleWorkflows(ctx, detectedWorkflows, commit, input, ref) + if err := handleWorkflows(ctx, detectedWorkflows, commit, input, ref); err != nil { + return err + } + + return detectAndHandleScopedWorkflows(ctx, input, ref, gitRepo, commit) } func skipWorkflows(ctx context.Context, input *notifyInput, commit *git.Commit) bool { @@ -303,51 +308,63 @@ func handleWorkflows( return fmt.Errorf("json.Marshal: %w", err) } - isForkPullRequest := false - if pr := input.PullRequest; pr != nil { - switch pr.Flow { - case issues_model.PullRequestFlowGithub: - isForkPullRequest = pr.IsFromFork() - case issues_model.PullRequestFlowAGit: - // There is no fork concept in agit flow, anyone with read permission can push refs/for// to the repo. - // So we can treat it as a fork pull request because it may be from an untrusted user - isForkPullRequest = true - default: - // unknown flow, assume it's a fork pull request to be safe - isForkPullRequest = true - } - } + isForkPullRequest := isForkPullRequestInput(input) for _, dwf := range detectedWorkflows { - run := &actions_model.ActionRun{ - Title: commit.MessageTitle(), - RepoID: input.Repo.ID, - Repo: input.Repo, - OwnerID: input.Repo.OwnerID, - WorkflowID: dwf.EntryName, - TriggerUserID: input.Doer.ID, - TriggerUser: input.Doer, - Ref: ref.String(), - CommitSHA: commit.ID.String(), - IsForkPullRequest: isForkPullRequest, - Event: input.Event, - EventPayload: string(p), - TriggerEvent: dwf.TriggerEvent.Name, - Status: actions_model.StatusWaiting, - } - - need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer) - if err != nil { - log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err) + // repo-level run: the workflow content is this repo at this commit + if err := buildApproveAndInsertRun(ctx, input, ref, commit, string(p), isForkPullRequest, dwf, input.Repo.ID, commit.ID.String(), false); err != nil { + log.Error("repo %s: %v", input.Repo.RelativePath(), err) continue } + } + return nil +} - run.NeedApproval = need +// buildApproveAndInsertRun assembles an ActionRun for a detected workflow, runs the +// fork-PR approval gate, and inserts it. Repo-level and scoped runs share this path so +// run construction and the approval flow have a single implementation that can't drift. +// workflowRepoID/workflowCommitSHA point at the repo+commit the workflow content comes +// from (the repo itself for repo-level runs, the source repo for scoped runs). +func buildApproveAndInsertRun( + ctx context.Context, + input *notifyInput, + ref git.RefName, + commit *git.Commit, + payload string, + isForkPullRequest bool, + dwf *actions_module.DetectedWorkflow, + workflowRepoID int64, + workflowCommitSHA string, + isScopedRun bool, +) error { + run := &actions_model.ActionRun{ + Title: commit.MessageTitle(), + RepoID: input.Repo.ID, + Repo: input.Repo, + OwnerID: input.Repo.OwnerID, + WorkflowID: dwf.EntryName, + TriggerUserID: input.Doer.ID, + TriggerUser: input.Doer, + Ref: ref.String(), + CommitSHA: commit.ID.String(), + IsForkPullRequest: isForkPullRequest, + Event: input.Event, + EventPayload: payload, + TriggerEvent: dwf.TriggerEvent.Name, + Status: actions_model.StatusWaiting, + WorkflowRepoID: workflowRepoID, + WorkflowCommitSHA: workflowCommitSHA, + IsScopedRun: isScopedRun, + } - if err := PrepareRunAndInsert(ctx, dwf.Content, run, nil); err != nil { - log.Error("PrepareRunAndInsert: %v", err) - continue - } + need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer) + if err != nil { + return fmt.Errorf("check if need approval for user %d: %w", input.Doer.ID, err) + } + run.NeedApproval = need + + if err := PrepareRunAndInsert(ctx, dwf.Content, run, nil); err != nil { + return fmt.Errorf("PrepareRunAndInsert: %w", err) } return nil } @@ -551,3 +568,113 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository) return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, git.RefNameFromBranch(repo.DefaultBranch)) } + +// isForkPullRequestInput reports whether the run should be treated as a fork pull request. +func isForkPullRequestInput(input *notifyInput) bool { + pr := input.PullRequest + if pr == nil { + return false + } + switch pr.Flow { + case issues_model.PullRequestFlowGithub: + return pr.IsFromFork() + case issues_model.PullRequestFlowAGit: + // There is no fork concept in agit flow, anyone with read permission can push refs/for// to the repo. + // So we can treat it as a fork pull request because it may be from an untrusted user + return true + default: + // unknown flow, assume it's a fork pull request to be safe + return true + } +} + +// detectAndHandleScopedWorkflows detects scoped workflows registered for the consuming repo +func detectAndHandleScopedWorkflows( + ctx context.Context, + input *notifyInput, + ref git.RefName, + consumerGitRepo *git.Repository, + consumerCommit *git.Commit, +) error { + // TODO: support workflow_run and schedule + if input.Event == webhook_module.HookEventWorkflowRun || input.Event == webhook_module.HookEventSchedule { + return nil + } + + sources, err := actions_model.GetEffectiveScopedWorkflowSources(ctx, input.Repo.OwnerID) + if err != nil { + return fmt.Errorf("GetEffectiveScopedWorkflowSources: %w", err) + } + if len(sources) == 0 { + return nil + } + + p, err := json.Marshal(input.Payload) + if err != nil { + return fmt.Errorf("json.Marshal: %w", err) + } + isForkPullRequest := isForkPullRequestInput(input) + actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig() + + // The same source repo may be registered at both the owner and instance level; dedup + // the IDs and batch-load them in one query instead of one round-trip per source. + seen := make(container.Set[int64], len(sources)) + for _, source := range sources { + seen.Add(source.SourceRepoID) + } + sourceRepoIDs := seen.Values() + + sourceRepos, err := repo_model.GetRepositoriesMapByIDs(ctx, sourceRepoIDs) + if err != nil { + return fmt.Errorf("GetRepositoriesMapByIDs: %w", err) + } + + for _, sourceRepoID := range sourceRepoIDs { + sourceRepo := sourceRepos[sourceRepoID] + if sourceRepo == nil { + // don't abort the other effective sources for this event + log.Error("scoped workflows: source repo %d for consumer %s not found", sourceRepoID, input.Repo.RelativePath()) + continue + } + if sourceRepo.IsEmpty { + continue + } + + sourceCommitSHA, detected, err := detectScopedWorkflowsForSource(ctx, input, consumerGitRepo, consumerCommit, sourceRepo) + if err != nil { + log.Error("scoped workflows: source %d for consumer %s: %v", sourceRepoID, input.Repo.RelativePath(), err) + continue + } + + for _, dwf := range detected { + // A consuming repo can opt out of a non-required scoped workflow. + // A required workflow (marked required at any effective level) can never be opted out. + if actions_model.ScopedWorkflowOptedOut(actionsConfig, sources, sourceRepo.ID, dwf.EntryName) { + continue + } + + if err := buildApproveAndInsertRun(ctx, input, ref, consumerCommit, string(p), isForkPullRequest, dwf, sourceRepo.ID, sourceCommitSHA, true); err != nil { + log.Error("scoped workflows: source %s workflow %s: %v", sourceRepo.RelativePath(), dwf.EntryName, err) + continue + } + } + } + + return nil +} + +// detectScopedWorkflowsForSource detects the scoped workflows from the source repo at its default branch +func detectScopedWorkflowsForSource( + ctx context.Context, + input *notifyInput, + consumerGitRepo *git.Repository, + consumerCommit *git.Commit, + sourceRepo *repo_model.Repository, +) (sourceCommitSHA string, detected []*actions_module.DetectedWorkflow, err error) { + // scoped workflow content is always taken from the source repo's default branch; the parse is cached per (source, default-branch SHA) and reused across consuming repos/events + sourceCommitSHA, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo) + if err != nil { + return "", nil, err + } + return sourceCommitSHA, actions_module.MatchScopedWorkflows(parsed, consumerGitRepo, consumerCommit, input.Event, input.Payload), nil +} diff --git a/services/actions/rerun.go b/services/actions/rerun.go index 17076a594c..6ec2463438 100644 --- a/services/actions/rerun.go +++ b/services/actions/rerun.go @@ -88,7 +88,16 @@ func validateRerun(ctx context.Context, run *actions_model.ActionRun, repo *repo } cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() - if cfg.IsWorkflowDisabled(run.WorkflowID) { + if run.IsScopedRun { + // a required scoped workflow can never be opted out, so a stale disabled flag must not block rerun + optedOut, err := actions_model.IsScopedWorkflowOptedOut(ctx, cfg, repo.OwnerID, run.WorkflowRepoID, run.WorkflowID) + if err != nil { + return err + } + if optedOut { + return util.NewInvalidArgumentErrorf("scoped workflow %s is disabled", run.WorkflowID) + } + } else if cfg.IsWorkflowDisabled(run.WorkflowID) { return util.NewInvalidArgumentErrorf("workflow %s is disabled", run.WorkflowID) } diff --git a/services/actions/reusable_workflow.go b/services/actions/reusable_workflow.go index cf77824c4d..2e220cd5ea 100644 --- a/services/actions/reusable_workflow.go +++ b/services/actions/reusable_workflow.go @@ -13,6 +13,7 @@ import ( perm_model "gitea.dev/models/perm" access_model "gitea.dev/models/perm/access" repo_model "gitea.dev/models/repo" + actions_module "gitea.dev/modules/actions" "gitea.dev/modules/actions/jobparser" "gitea.dev/modules/container" "gitea.dev/modules/gitrepo" @@ -60,6 +61,11 @@ func loadReusableWorkflowSource(ctx context.Context, run *actions_model.ActionRu return nil, 0, "", err } if !ok { + if run.IsScopedRun { + // A scoped workflow's cross-repo "uses:" is resolved with the consuming repo's read permission, + // so the referenced repo must be readable by every consumer. Make that explicit in the failure. + return nil, 0, "", fmt.Errorf("no permission to read reusable workflow %s/%s: a scoped workflow's cross-repo \"uses:\" is resolved with the consuming repository %q read permission", ref.Owner, ref.Repo, run.Repo.RelativePath()) + } return nil, 0, "", fmt.Errorf("no permission to read reusable workflow from %s/%s", ref.Owner, ref.Repo) } bytes, resolvedSHA, err := readWorkflowFromRepo(ctx, repo, ref.Ref, ref.Path) @@ -359,5 +365,13 @@ func ResolveUses(ctx context.Context, uses string) (*jobparser.UsesRef, error) { // RoutePath is the instance-relative path (AppSubURL already stripped), e.g. "/owner/repo/.gitea/workflows/file.yml@ref". uses = strings.TrimPrefix(gsu.RoutePath, "/") } - return jobparser.ParseUses(uses) + ref, err := jobparser.ParseUses(uses) + if err != nil { + return nil, err + } + // jobparser only validates syntax; enforce the (instance-configurable) directory allowlist here. + if !actions_module.IsWorkflowOrScopedWorkflow(ref.Path) { + return nil, fmt.Errorf(`"uses:" path %q must be under a configured workflow directory (WORKFLOW_DIRS or SCOPED_WORKFLOW_DIRS)`, ref.Path) + } + return ref, nil } diff --git a/services/actions/reusable_workflow_test.go b/services/actions/reusable_workflow_test.go index a7bb41ba8a..28b6696c36 100644 --- a/services/actions/reusable_workflow_test.go +++ b/services/actions/reusable_workflow_test.go @@ -139,6 +139,8 @@ func buildCallerChain(t *testing.T, callerUses ...string) []*actions_model.Actio func TestResolveUses(t *testing.T) { defer test.MockVariableValue(&setting.AppURL, "https://gitea.example.com/sub/")() defer test.MockVariableValue(&setting.AppSubURL, "/sub")() + defer test.MockVariableValue(&setting.Actions.WorkflowDirs, []string{".gitea/workflows", ".github/workflows"})() + defer test.MockVariableValue(&setting.Actions.ScopedWorkflowDirs, []string{".gitea/scoped_workflows"})() ctx := t.Context() t.Run("LocalForms", func(t *testing.T) { @@ -152,6 +154,34 @@ func TestResolveUses(t *testing.T) { assert.Equal(t, jobparser.UsesRef{Kind: jobparser.UsesKindLocalCrossRepo, Owner: "owner", Repo: "repo", Path: ".gitea/workflows/build.yml", Ref: "v1"}, *ref) }) + t.Run("DirectoryAllowlist", func(t *testing.T) { + // SCOPED_WORKFLOW_DIRS is allowed (local and cross-repo). + ref, err := ResolveUses(ctx, "./.gitea/scoped_workflows/lib.yml") + require.NoError(t, err) + assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) + + ref, err = ResolveUses(ctx, "owner/repo/.gitea/scoped_workflows/lib.yml@v1") + require.NoError(t, err) + assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) + + // A directory that is neither WORKFLOW_DIRS nor SCOPED_WORKFLOW_DIRS parses but is rejected by the allowlist. + _, err = ResolveUses(ctx, "./not-workflows/build.yml") + require.Error(t, err) + _, err = ResolveUses(ctx, "owner/repo/lib/build.yml@v1") + require.Error(t, err) + }) + + t.Run("ConfigurableWorkflowDirs", func(t *testing.T) { + // A non-default WORKFLOW_DIRS is honored (the hardcoded ".gitea/workflows" is no longer special). + defer test.MockVariableValue(&setting.Actions.WorkflowDirs, []string{".gitea/ci"})() + ref, err := ResolveUses(ctx, "./.gitea/ci/build.yml") + require.NoError(t, err) + assert.Equal(t, ".gitea/ci/build.yml", ref.Path) + + _, err = ResolveUses(ctx, "./.gitea/workflows/build.yml") // no longer a configured dir + require.Error(t, err) + }) + t.Run("LocalInstanceURL", func(t *testing.T) { // An absolute URL on this instance (incl. AppSubURL) resolves to the equivalent cross-repo ref. ref, err := ResolveUses(ctx, "https://gitea.example.com/sub/owner/repo/.gitea/workflows/ci.yml@refs/heads/main") diff --git a/services/actions/run.go b/services/actions/run.go index 58e50de0c2..fc2631e19d 100644 --- a/services/actions/run.go +++ b/services/actions/run.go @@ -21,6 +21,10 @@ import ( // It parses the workflow content, evaluates concurrency if needed, and inserts the run and its jobs into the database. // The title will be cut off at 255 characters if it's longer than 255 characters. func PrepareRunAndInsert(ctx context.Context, content []byte, run *actions_model.ActionRun, inputsWithDefaults map[string]any) error { + if run.WorkflowRepoID == 0 { + return fmt.Errorf("WorkflowRepoID must be set before insert (repo %d, workflow %q)", run.RepoID, run.WorkflowID) + } + if err := run.LoadAttributes(ctx); err != nil { return fmt.Errorf("LoadAttributes: %w", err) } @@ -162,8 +166,8 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte Needs: needs, RunsOn: job.RunsOn(), Status: util.Iif(shouldBlockJob, actions_model.StatusBlocked, actions_model.StatusWaiting), - WorkflowSourceRepoID: run.RepoID, - WorkflowSourceCommitSHA: run.CommitSHA, + WorkflowSourceRepoID: run.WorkflowRepoID, + WorkflowSourceCommitSHA: run.WorkflowCommitSHA, ContinueOnError: job.GetContinueOnError(), } // Parse workflow/job permissions (no clamping here) diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index 5995d003be..d67bf8dd29 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -118,6 +118,9 @@ func CreateScheduleTask(ctx context.Context, spec *actions_model.ActionScheduleS TriggerEvent: string(webhook_module.HookEventSchedule), ScheduleID: cron.ID, Status: actions_model.StatusWaiting, + // schedule runs the repo's own workflow at the recorded commit + WorkflowRepoID: cron.RepoID, + WorkflowCommitSHA: cron.CommitSHA, } // FIXME cron.Content might be outdated if the workflow file has been changed. diff --git a/services/actions/scoped_workflow_cache.go b/services/actions/scoped_workflow_cache.go new file mode 100644 index 0000000000..3dcfb44d8a --- /dev/null +++ b/services/actions/scoped_workflow_cache.go @@ -0,0 +1,84 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + "fmt" + + git_model "gitea.dev/models/git" + repo_model "gitea.dev/models/repo" + actions_module "gitea.dev/modules/actions" + "gitea.dev/modules/gitrepo" + "gitea.dev/modules/log" + + lru "github.com/hashicorp/golang-lru/v2" +) + +// cachedScopedWorkflows is one source repo's parsed scoped workflows together with the default-branch SHA they were parsed at. +type cachedScopedWorkflows struct { + sha string + parsed []*actions_module.ParsedScopedWorkflow +} + +// scopedWorkflowCache caches each scoped-workflow source repo's parsed workflows, keyed by source repo ID. +// There is exactly one entry per source: a default-branch update is detected by SHA mismatch and overwrites the entry, so stale parses never accumulate. +var scopedWorkflowCache *lru.Cache[int64, *cachedScopedWorkflows] + +const defaultScopedWorkflowCacheSize = 1024 + +func init() { + c, err := lru.New[int64, *cachedScopedWorkflows](defaultScopedWorkflowCacheSize) + if err != nil { + log.Fatal("failed to new scopedWorkflowCache, err: %v", err) + } + scopedWorkflowCache = c +} + +// LoadParsedScopedWorkflows returns the source repo's parsed scoped workflows at its current default-branch HEAD. +func LoadParsedScopedWorkflows(ctx context.Context, sourceRepo *repo_model.Repository) (sha string, parsed []*actions_module.ParsedScopedWorkflow, err error) { + branch, err := git_model.GetBranch(ctx, sourceRepo.ID, sourceRepo.DefaultBranch) + if err != nil { + return "", nil, fmt.Errorf("get source default branch: %w", err) + } + sha = branch.CommitID + + if v, ok := scopedWorkflowCache.Get(sourceRepo.ID); ok && v.sha == sha { + // cache hit at the current default-branch HEAD + return sha, v.parsed, nil + } + + // cache miss: open the source repo at the exact SHA we keyed on + sourceGitRepo, err := gitrepo.OpenRepository(ctx, sourceRepo) + if err != nil { + return "", nil, fmt.Errorf("open source repo: %w", err) + } + defer sourceGitRepo.Close() + + sourceCommit, err := sourceGitRepo.GetCommit(sha) + if err != nil { + return "", nil, fmt.Errorf("get source commit %s: %w", sha, err) + } + parsed, err = actions_module.ParseScopedWorkflows(sourceCommit) + if err != nil { + return "", nil, err + } + // overwrite this source's single entry (a stale entry from a previous HEAD is replaced, not accumulated) + scopedWorkflowCache.Add(sourceRepo.ID, &cachedScopedWorkflows{sha: sha, parsed: parsed}) + return sha, parsed, nil +} + +// ScopedWorkflowContent returns one scoped workflow's raw content (by entry name) at the source repo's current default-branch HEAD, or nil if no such workflow exists there. +func ScopedWorkflowContent(ctx context.Context, sourceRepo *repo_model.Repository, entryName string) ([]byte, error) { + _, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo) + if err != nil { + return nil, err + } + for _, p := range parsed { + if p.EntryName == entryName { + return p.Content, nil + } + } + return nil, nil +} diff --git a/services/actions/workflow.go b/services/actions/workflow.go index a748b0859c..5b5628cff1 100644 --- a/services/actions/workflow.go +++ b/services/actions/workflow.go @@ -43,7 +43,9 @@ func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnabl return repo_model.UpdateRepoUnitConfig(ctx, cfgUnit) } -func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) (runID int64, _ error) { +// DispatchActionWorkflow manually triggers a workflow_dispatch run. +// scopedWorkflowSourceRepoID selects the workflow source: 0 means a repo-level workflow in this repo; a non-zero value is the source repo of a scoped workflow. +func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, scopedWorkflowSourceRepoID int64, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) (runID int64, _ error) { if workflowID == "" { return 0, util.ErrorWrapTranslatable( util.NewNotExistErrorf("workflowID is empty"), @@ -58,10 +60,20 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re ) } - // can not rerun job when workflow is disabled + isScoped := scopedWorkflowSourceRepoID > 0 + cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() - if cfg.IsWorkflowDisabled(workflowID) { + var workflowDisabled bool + if isScoped { + var err error + if workflowDisabled, err = actions_model.IsScopedWorkflowOptedOut(ctx, cfg, repo.OwnerID, scopedWorkflowSourceRepoID, workflowID); err != nil { + return 0, err + } + } else { + workflowDisabled = cfg.IsWorkflowDisabled(workflowID) + } + if workflowDisabled { return 0, util.ErrorWrapTranslatable( util.NewPermissionDeniedErrorf("workflow is disabled"), "actions.workflow.disabled", @@ -87,15 +99,6 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re ) } - // get workflow entry from runTargetCommit - _, entries, err := actions.ListWorkflows(runTargetCommit) - if err != nil { - return 0, err - } - - // find workflow from commit - var entry *git.TreeEntry - run := &actions_model.ActionRun{ Title: runTargetCommit.MessageTitle(), RepoID: repo.ID, @@ -110,24 +113,13 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re Event: "workflow_dispatch", TriggerEvent: "workflow_dispatch", Status: actions_model.StatusWaiting, + // local dispatch: own repo at the target commit; the scoped path overrides these below + WorkflowRepoID: repo.ID, + WorkflowCommitSHA: runTargetCommit.ID.String(), } - for _, e := range entries { - if e.Name() != workflowID { - continue - } - entry = e - break - } - - if entry == nil { - return 0, util.ErrorWrapTranslatable( - util.NewNotExistErrorf("workflow %q doesn't exist", workflowID), - "actions.workflow.not_found", workflowID, - ) - } - - content, err := actions.GetContentFromEntry(entry) + // resolve the workflow content and record its source on the run (scoped runs read from the source repo) + content, err := resolveDispatchWorkflowContent(ctx, repo, runTargetCommit, workflowID, scopedWorkflowSourceRepoID, isScoped, run) if err != nil { return 0, err } @@ -176,3 +168,62 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re } return run.ID, nil } + +// resolveDispatchWorkflowContent returns the YAML for a dispatched workflow and records its source on the run. +// - Repo-level: from the consumer's runTargetCommit. +// - Scoped: from the source repo's default branch. +func resolveDispatchWorkflowContent(ctx reqctx.RequestContext, repo *repo_model.Repository, runTargetCommit *git.Commit, workflowID string, sourceRepoID int64, isScoped bool, run *actions_model.ActionRun) ([]byte, error) { + if isScoped { + return resolveScopedDispatchContent(ctx, repo, sourceRepoID, workflowID, run) + } + + _, entries, err := actions.ListWorkflows(runTargetCommit) + if err != nil { + return nil, err + } + for _, e := range entries { + if e.Name() == workflowID { + return actions.GetContentFromEntry(e) + } + } + return nil, util.ErrorWrapTranslatable( + util.NewNotExistErrorf("workflow %q doesn't exist", workflowID), + "actions.workflow.not_found", workflowID, + ) +} + +func resolveScopedDispatchContent(ctx reqctx.RequestContext, repo *repo_model.Repository, sourceRepoID int64, workflowID string, run *actions_model.ActionRun) ([]byte, error) { + // the source must be an effective scoped source for this consumer repo + effective, err := actions_model.IsScopedWorkflowSourceEffective(ctx, repo.OwnerID, sourceRepoID) + if err != nil { + return nil, err + } + if !effective { + return nil, util.ErrorWrapTranslatable( + util.NewNotExistErrorf("scoped workflow source %d is not effective for this repository", sourceRepoID), + "actions.workflow.not_found", workflowID, + ) + } + + sourceRepo, err := repo_model.GetRepositoryByID(ctx, sourceRepoID) + if err != nil { + return nil, err + } + + sha, parsed, err := LoadParsedScopedWorkflows(ctx, sourceRepo) + if err != nil { + return nil, err + } + for _, p := range parsed { + if p.EntryName == workflowID { + run.WorkflowRepoID = sourceRepo.ID + run.WorkflowCommitSHA = sha + run.IsScopedRun = true + return p.Content, nil + } + } + return nil, util.ErrorWrapTranslatable( + util.NewNotExistErrorf("scoped workflow %q doesn't exist", workflowID), + "actions.workflow.not_found", workflowID, + ) +} diff --git a/services/convert/convert.go b/services/convert/convert.go index 357217908f..745cdd9897 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -7,6 +7,7 @@ package convert import ( "bytes" "context" + "errors" "fmt" "net/url" "path" @@ -29,6 +30,7 @@ import ( "gitea.dev/modules/actions" "gitea.dev/modules/container" "gitea.dev/modules/git" + "gitea.dev/modules/gitrepo" "gitea.dev/modules/httplib" "gitea.dev/modules/log" "gitea.dev/modules/setting" @@ -641,6 +643,64 @@ func getActionWorkflowFromCommit(ctx context.Context, repo *repo_model.Repositor return nil, util.NewNotExistErrorf("workflow %q not found", workflowID) } +// GetScopedActionWorkflow resolves a scoped workflow definition (under SCOPED_WORKFLOW_DIRS) from the source repo at commitSHA. +func GetScopedActionWorkflow(ctx context.Context, sourceGitRepo *git.Repository, sourceRepo *repo_model.Repository, workflowID, commitSHA string) (*api.ActionWorkflow, error) { + commit, err := sourceGitRepo.GetCommit(commitSHA) + if err != nil { + return nil, err + } + + folder, entries, err := actions.ListScopedWorkflows(commit) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if entry.Name() == workflowID { + // An empty ref pins HTMLURL to commit (the run's WorkflowCommitSHA) rather than the moving default branch. + wf := getActionWorkflowEntry(ctx, sourceRepo, commit, git.RefName(""), folder, entry) + // TODO: a scoped workflow has no repo-level representation on the source: the workflow API scans WORKFLOW_DIRS (not SCOPED_WORKFLOW_DIRS), + // and the badge only reflects the source's repo-level runs, so neither link resolves a scoped workflow. + // Blank them for now and populate once a scoped-aware workflow/badge endpoint exists. + wf.URL = "" + wf.BadgeURL = "" + return wf, nil + } + } + + return nil, util.NewNotExistErrorf("scoped workflow %q not found", workflowID) +} + +// ResolveActionWorkflowForRun returns the api.ActionWorkflow describing a run's workflow definition. +// For a scoped run the definition lives in the source repo (run.WorkflowRepoID @ run.WorkflowCommitSHA) under SCOPED_WORKFLOW_DIRS, +// not in the consuming repo, so it is resolved against the source repo. +func ResolveActionWorkflowForRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun) (*api.ActionWorkflow, error) { + if run.IsScopedRun { + sourceRepo, err := repo_model.GetRepositoryByID(ctx, run.WorkflowRepoID) + if err != nil { + return nil, err + } + sourceGitRepo, err := gitrepo.OpenRepository(ctx, sourceRepo) + if err != nil { + return nil, err + } + defer sourceGitRepo.Close() + return GetScopedActionWorkflow(ctx, sourceGitRepo, sourceRepo, run.WorkflowID, run.WorkflowCommitSHA) + } + + gitRepo, err := gitrepo.OpenRepository(ctx, repo) + if err != nil { + return nil, err + } + defer gitRepo.Close() + + convertedWorkflow, err := GetActionWorkflowByRef(ctx, gitRepo, repo, run.WorkflowID, git.RefName(run.Ref)) + if err != nil && errors.Is(err, util.ErrNotExist) { + convertedWorkflow, err = GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID) + } + return convertedWorkflow, err +} + // ToActionArtifact convert a actions_model.ActionArtifact to an api.ActionArtifact func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArtifact) (*api.ActionArtifact, error) { url := fmt.Sprintf("%s/actions/artifacts/%d", repo.APIURL(), art.ID) diff --git a/services/org/org.go b/services/org/org.go index 2f2ee95031..e2596ec26c 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -39,6 +39,7 @@ func deleteOrganization(ctx context.Context, org *org_model.Organization) error &user_model.Blocking{BlockerID: org.ID}, &actions_model.ActionRunner{OwnerID: org.ID}, &actions_model.ActionRunnerToken{OwnerID: org.ID}, + &actions_model.ActionScopedWorkflowSource{OwnerID: org.ID}, ); err != nil { return fmt.Errorf("DeleteBeans: %w", err) } diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go index 6fafcf3b51..948e26aaa5 100644 --- a/services/pull/commit_status.go +++ b/services/pull/commit_status.go @@ -8,11 +8,15 @@ import ( "context" "errors" "fmt" + "slices" + actions_model "gitea.dev/models/actions" "gitea.dev/models/db" git_model "gitea.dev/models/git" issues_model "gitea.dev/models/issues" + repo_model "gitea.dev/models/repo" "gitea.dev/modules/commitstatus" + "gitea.dev/modules/container" "gitea.dev/modules/gitrepo" "gitea.dev/modules/glob" "gitea.dev/modules/log" @@ -69,11 +73,25 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, func IsPullCommitStatusPass(ctx context.Context, pr *issues_model.PullRequest) (bool, error) { pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) if err != nil { - return false, fmt.Errorf("GetLatestCommitStatus: %w", err) + return false, fmt.Errorf("GetFirstMatchProtectedBranchRule: %w", err) } - if pb == nil || !pb.EnableStatusCheck { + if pb == nil { return true, nil } + if !pb.EnableStatusCheck { + // The branch's own status check is off, but required scoped checks (mandated by the owner or instance admin) still gate the merge. + if err := pr.LoadBaseRepo(ctx); err != nil { + return false, err + } + required, err := EffectiveRequiredContexts(ctx, pr.BaseRepo, pb) + if err != nil { + return false, err + } + if len(required) == 0 { + // With none in effect there is nothing to enforce, so don't block + return true, nil + } + } state, err := GetPullRequestCommitStatusState(ctx, pr) if err != nil { @@ -130,10 +148,57 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR if err != nil { return "", fmt.Errorf("LoadProtectedBranch: %w", err) } - var requiredContexts []string - if pb != nil { - requiredContexts = pb.StatusCheckContexts + requiredContexts, err := EffectiveRequiredContexts(ctx, pr.BaseRepo, pb) + if err != nil { + return "", err } return MergeRequiredContextsCommitStatus(commitStatuses, requiredContexts), nil } + +// EffectiveRequiredContexts returns the required status-check contexts for a PR head: +// 1. every required scoped workflow's status-check patterns effective for the repo (always) +// 2. the branch protection's own configured contexts, only when its status check is enabled +func EffectiveRequiredContexts(ctx context.Context, repo *repo_model.Repository, pb *git_model.ProtectedBranch) ([]string, error) { + if pb == nil { + return nil, nil + } + + sources, err := actions_model.GetEffectiveScopedWorkflowSources(ctx, repo.OwnerID) + if err != nil { + return nil, fmt.Errorf("GetEffectiveScopedWorkflowSources: %w", err) + } + + // Every required scoped workflow's admin-authored status-check patterns, matched must-present-and-pass: + // a required scoped check that posts no matching status blocks the merge. + seen := make(container.Set[string]) + var scoped []string + for _, source := range sources { + for _, cfg := range source.WorkflowConfigs { + if !cfg.Required { + continue + } + for _, p := range cfg.Patterns { + if seen.Add(p) { + scoped = append(scoped, p) + } + } + } + } + + slices.Sort(scoped) // sort for stable output + + // With the branch protection's own status check disabled, only the required scoped checks (mandated by the owner or instance admin) gate the merge. + if !pb.EnableStatusCheck { + return scoped, nil + } + + // Status check enabled: the rule's configured contexts, then the scoped patterns not already among them. + required := slices.Clone(pb.StatusCheckContexts) + for _, p := range scoped { + if !slices.Contains(pb.StatusCheckContexts, p) { + required = append(required, p) + } + } + return required, nil +} diff --git a/services/pull/commit_status_test.go b/services/pull/commit_status_test.go index 612ae268cb..2fda2f256a 100644 --- a/services/pull/commit_status_test.go +++ b/services/pull/commit_status_test.go @@ -7,10 +7,15 @@ package pull import ( "testing" + actions_model "gitea.dev/models/actions" + "gitea.dev/models/db" git_model "gitea.dev/models/git" + repo_model "gitea.dev/models/repo" + "gitea.dev/models/unittest" "gitea.dev/modules/commitstatus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMergeRequiredContextsCommitStatus(t *testing.T) { @@ -90,3 +95,62 @@ func TestMergeRequiredContextsCommitStatus(t *testing.T) { assert.Equal(t, c.expected, MergeRequiredContextsCommitStatus(c.commitStatuses, c.requiredContexts), "case %d", i) } } + +// TestEffectiveRequiredContexts: every required scoped workflow's stored status-check patterns are appended to the +// branch protection's configured contexts unconditionally (must-present; the matching is done downstream). +func TestEffectiveRequiredContexts(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + consumer := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // owned by user5 + pbOn := &git_model.ProtectedBranch{EnableStatusCheck: true, StatusCheckContexts: []string{"configured/check"}} + + t.Run("nil protected branch: nil", func(t *testing.T) { + got, err := EffectiveRequiredContexts(t.Context(), consumer, nil) + require.NoError(t, err) + assert.Nil(t, got) + }) + + t.Run("status checks disabled, no required scoped: nothing required", func(t *testing.T) { + pbOff := &git_model.ProtectedBranch{EnableStatusCheck: false, StatusCheckContexts: []string{"configured/check"}} + got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOff) + require.NoError(t, err) + assert.Empty(t, got) // the rule's own status check is off and no required scoped workflow applies -> nothing gates + }) + + t.Run("owner with no scoped sources: configured contexts unchanged", func(t *testing.T) { + noSourceRepo := &repo_model.Repository{ID: consumer.ID, OwnerID: 99999} + got, err := EffectiveRequiredContexts(t.Context(), noSourceRepo, pbOn) + require.NoError(t, err) + assert.Equal(t, []string{"configured/check"}, got) + }) + + t.Run("required workflow patterns appended", func(t *testing.T) { + require.NoError(t, db.Insert(t.Context(), &actions_model.ActionScopedWorkflowSource{ + OwnerID: consumer.OwnerID, + SourceRepoID: 1, + WorkflowConfigs: map[string]*actions_model.ScopedWorkflowConfig{ + "ci.yaml": {Required: true, Patterns: []string{"org/src: ci.yaml / build (pull_request)", "org/src: ci.yaml / lint (pull_request)"}}, + "old.yaml": {Required: false, Patterns: []string{"org/src: old.yaml / *"}}, // kept as history, must NOT be enforced + }, + })) + // No status is passed/needed: required patterns are enforced even though nothing has posted them yet (must-present). + got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOn) + require.NoError(t, err) + assert.ElementsMatch(t, []string{ + "configured/check", + "org/src: ci.yaml / build (pull_request)", + "org/src: ci.yaml / lint (pull_request)", + }, got) + assert.NotContains(t, got, "org/src: old.yaml / *", "a non-required (history) config must not be enforced") + }) + + t.Run("status checks disabled, with required scoped: only the scoped patterns gate", func(t *testing.T) { + pbOff := &git_model.ProtectedBranch{EnableStatusCheck: false, StatusCheckContexts: []string{"configured/check"}} + got, err := EffectiveRequiredContexts(t.Context(), consumer, pbOff) + require.NoError(t, err) + // "configured/check" is dropped (the rule's own status check is off); only the required scoped patterns remain. + assert.ElementsMatch(t, []string{ + "org/src: ci.yaml / build (pull_request)", + "org/src: ci.yaml / lint (pull_request)", + }, got) + }) +} diff --git a/services/repository/delete.go b/services/repository/delete.go index 0666a1616b..db84307610 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -179,6 +179,7 @@ func DeleteRepositoryDirectly(ctx context.Context, repoID int64, ignoreOrgTeams &actions_model.ActionArtifact{RepoID: repoID}, &actions_model.ActionRunJobSummary{RepoID: repoID}, &actions_model.ActionRunnerToken{RepoID: repoID}, + &actions_model.ActionScopedWorkflowSource{SourceRepoID: repoID}, &issues_model.IssuePin{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) diff --git a/services/user/delete.go b/services/user/delete.go index 8679afaf12..5592ac7c4b 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -95,6 +95,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) &user_model.Blocking{BlockerID: u.ID}, &user_model.Blocking{BlockeeID: u.ID}, &actions_model.ActionRunnerToken{OwnerID: u.ID}, + &actions_model.ActionScopedWorkflowSource{OwnerID: u.ID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index b9aaf52f85..2586d09798 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -17,7 +17,6 @@ import ( repo_model "gitea.dev/models/repo" user_model "gitea.dev/models/user" "gitea.dev/modules/git" - "gitea.dev/modules/gitrepo" "gitea.dev/modules/httplib" "gitea.dev/modules/log" "gitea.dev/modules/repository" @@ -1029,19 +1028,14 @@ func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_ status := convert.ToWorkflowRunAction(run.Status) - gitRepo, err := gitrepo.OpenRepository(ctx, repo) + // Resolve the workflow definition from its source repo. + convertedWorkflow, err := convert.ResolveActionWorkflowForRun(ctx, repo, run) if err != nil { - log.Error("OpenRepository: %v", err) - return - } - defer gitRepo.Close() - - convertedWorkflow, err := convert.GetActionWorkflowByRef(ctx, gitRepo, repo, run.WorkflowID, git.RefName(run.Ref)) - if err != nil && errors.Is(err, util.ErrNotExist) { - convertedWorkflow, err = convert.GetActionWorkflow(ctx, gitRepo, repo, run.WorkflowID) - } - if err != nil { - log.Error("GetActionWorkflow: %v", err) + if errors.Is(err, util.ErrNotExist) { + log.Debug("WorkflowRunStatusUpdate: workflow %q for run %d not found: %v", run.WorkflowID, run.ID, err) + return + } + log.Error("ResolveActionWorkflowForRun: %v", err) return } diff --git a/templates/admin/actions.tmpl b/templates/admin/actions.tmpl index 1bddb674c0..6be9bdf73b 100644 --- a/templates/admin/actions.tmpl +++ b/templates/admin/actions.tmpl @@ -6,5 +6,8 @@ {{if eq .PageType "variables"}} {{template "shared/variables/variable_list" .}} {{end}} + {{if eq .PageType "scoped-workflows"}} + {{template "shared/actions/scoped_workflows" .}} + {{end}} {{template "admin/layout_footer" .}} diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index b6c9f155e9..a54fa530e2 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -72,7 +72,7 @@ {{end}} {{end}} {{if .EnableActions}} -
+
{{ctx.Locale.Tr "actions.actions"}}
{{end}} diff --git a/templates/org/settings/actions.tmpl b/templates/org/settings/actions.tmpl index 1abf895627..87ea0cd65e 100644 --- a/templates/org/settings/actions.tmpl +++ b/templates/org/settings/actions.tmpl @@ -6,6 +6,8 @@ {{template "shared/secrets/add_list" .}} {{else if eq .PageType "variables"}} {{template "shared/variables/variable_list" .}} + {{else if eq .PageType "scoped-workflows"}} + {{template "shared/actions/scoped_workflows" .}} {{end}} {{template "org/settings/layout_footer" .}} diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index b14b4248fc..5239a20326 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -26,7 +26,7 @@ {{end}} {{if .EnableActions}} -
+
{{ctx.Locale.Tr "actions.actions"}}
{{end}} diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl index 2aac08d61b..eb87abe747 100644 --- a/templates/repo/actions/list.tmpl +++ b/templates/repo/actions/list.tmpl @@ -10,8 +10,8 @@ - + {{ctx.Locale.Tr "actions.runs.actors_no_select"}} {{range .Actors}} - + {{ctx.AvatarUtils.Avatar . 20}} {{.GetDisplayName}} {{end}} @@ -73,11 +91,11 @@ {{svg "octicon-search"}} - + {{ctx.Locale.Tr "actions.runs.status_no_select"}} {{range .StatusInfoList}} - + {{template "repo/icons/action_status" (dict "Status" .StatusName)}} {{.DisplayedStatus}} @@ -95,11 +113,11 @@ {{svg "octicon-search"}} - + {{ctx.Locale.Tr "actions.runs.branches_no_select"}} {{range .RunBranches}} - + {{.}} {{end}} @@ -116,8 +134,8 @@ {{end}} {{if and .AllowDisableOrEnableWorkflow .CurWorkflowIsListed $.CurWorkflow}} - - {{if .CurWorkflowDisabled}}{{ctx.Locale.Tr "actions.workflow.enable"}}{{else}}{{ctx.Locale.Tr "actions.workflow.disable"}}{{end}} + + {{if .CurWorkflowRequired}}{{ctx.Locale.Tr "actions.workflow.disable"}}{{else if .CurWorkflowDisabled}}{{ctx.Locale.Tr "actions.workflow.enable"}}{{else}}{{ctx.Locale.Tr "actions.workflow.disable"}}{{end}} {{end}} diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl index 0d508e69e5..fbaf31838d 100644 --- a/templates/repo/actions/view_component.tmpl +++ b/templates/repo/actions/view_component.tmpl @@ -26,6 +26,7 @@ data-locale-total-duration="{{ctx.Locale.Tr "actions.runs.total_duration"}}" data-locale-run-details="{{ctx.Locale.Tr "actions.runs.run_details"}}" data-locale-workflow-file="{{ctx.Locale.Tr "actions.runs.workflow_file"}}" + data-locale-workflow-file-no-permission="{{ctx.Locale.Tr "actions.runs.workflow_file_no_permission"}}" data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}" data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}" data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}" diff --git a/templates/repo/actions/workflow_dispatch.tmpl b/templates/repo/actions/workflow_dispatch.tmpl index ad9b0e94cc..92e6b75503 100644 --- a/templates/repo/actions/workflow_dispatch.tmpl +++ b/templates/repo/actions/workflow_dispatch.tmpl @@ -7,7 +7,7 @@ -
- - -
-
- - -
- - - - {{end}} {{else}} @@ -201,4 +162,53 @@ {{end}} + +{{if $showCreateWorkflowBadge}} + +{{end}} + {{template "base/footer" .}} diff --git a/web_src/js/features/repo-actions.test.ts b/web_src/js/features/repo-actions.test.ts index bcb2063fb7..38873241a6 100644 --- a/web_src/js/features/repo-actions.test.ts +++ b/web_src/js/features/repo-actions.test.ts @@ -5,8 +5,7 @@ test('updateWorkflowBadgeFields updates badge snippets for selected branch', ()
diff --git a/web_src/js/features/repo-actions.ts b/web_src/js/features/repo-actions.ts index 8a204c3532..89ed94e078 100644 --- a/web_src/js/features/repo-actions.ts +++ b/web_src/js/features/repo-actions.ts @@ -1,36 +1,35 @@ import {createApp} from 'vue'; import RepoActionView from '../components/RepoActionView.vue'; -import {queryElems} from '../utils/dom.ts'; - -function escapeHTMLAttribute(value: string): string { - return value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>'); -} +import {registerGlobalInitFunc} from '../modules/observer.ts'; +import {html} from '../utils/html.ts'; export function updateWorkflowBadgeFields(form: HTMLElement, branch: string): void { - const badgeURL = new URL(form.getAttribute('data-badge-url')!); - badgeURL.searchParams.set('branch', branch); + const badgeURLParsed = new URL(form.getAttribute('data-badge-url')!); + badgeURLParsed.searchParams.set('branch', branch); - const badgeURLString = badgeURL.href; + const badgeURL = badgeURLParsed.href; const workflowURL = form.getAttribute('data-workflow-url')!; - const markdownAltText = form.getAttribute('data-markdown-alt-text')!; - const htmlAltText = form.getAttribute('data-html-alt-text')!; + const displayName = form.getAttribute('data-workflow-display-name')!; + const markdownAltText = displayName.replaceAll(/[\\[\]]/g, (c) => `\\${c}`); - form.querySelector('[data-workflow-badge-image]')!.src = badgeURLString; - form.querySelector('#workflow-badge-url')!.value = badgeURLString; - form.querySelector('#workflow-badge-markdown')!.value = `[![${markdownAltText}](${badgeURLString})](${workflowURL})`; - form.querySelector('#workflow-badge-html')!.value = `${escapeHTMLAttribute(htmlAltText)}`; + form.querySelector('[data-workflow-badge-image]')!.src = badgeURL; + form.querySelector('#workflow-badge-url')!.value = badgeURL; + form.querySelector('#workflow-badge-markdown')!.value = `[![${markdownAltText}](${badgeURL})](${workflowURL})`; + form.querySelector('#workflow-badge-html')!.value = html`${displayName}`; } -function initWorkflowBadgeBranchSelection(): void { - queryElems(document, '[data-workflow-badge-form]', (form) => { - const branchInput = form.querySelector('[data-workflow-badge-branch]')!; - branchInput.addEventListener('change', () => updateWorkflowBadgeFields(form, branchInput.value)); - }); +function initWorkflowBadgeForm(form: HTMLElement): void { + const branchInput = form.querySelector('[data-workflow-badge-branch]')!; + branchInput.addEventListener('change', () => updateWorkflowBadgeFields(form, branchInput.value)); + updateWorkflowBadgeFields(form, branchInput.value); } -export function initRepositoryActionView() { - initWorkflowBadgeBranchSelection(); +export function initRepositoryActions() { + registerGlobalInitFunc('initWorkflowBadgeForm', initWorkflowBadgeForm); + initRepositoryActionsView(); +} +function initRepositoryActionsView() { const el = document.querySelector('#repo-action-view'); if (!el) return; diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 0ca1deb0d3..3289238b0d 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -42,7 +42,7 @@ import {initCommonOrganization} from './features/common-organization.ts'; import {initRepoWikiForm} from './features/repo-wiki.ts'; import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts'; import {initCaptcha} from './features/captcha.ts'; -import {initRepositoryActionView} from './features/repo-actions.ts'; +import {initRepositoryActions} from './features/repo-actions.ts'; import {initGlobalTooltips} from './modules/tippy.ts'; import {initGiteaFomantic} from './modules/fomantic.ts'; import {initRepoIssueList} from './features/repo-issue-list.ts'; @@ -138,7 +138,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoViewFileTree, initRepoWikiForm, initRepository, - initRepositoryActionView, + initRepositoryActions, initRepositorySearch, initRepoContributors, initRepoCodeFrequency, -- 2.54.0 From ce8cf22af9be12b43e6eb0ce98c1e96ccefefba8 Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 28 Jun 2026 13:37:16 +0200 Subject: [PATCH 039/169] fix(actions): don't swallow HTML entities into linkified URLs (#38239) In the Actions log viewer, a double-quoted URL renders with a stray extra `;` after it. Reported in `gitea/runner#1046` Remove the buggy AI slop `linkifyURLs` and use new approach to process URLs in text --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- web_src/js/components/ActionRunView.ts | 6 +-- web_src/js/modules/codeeditor/utils.test.ts | 39 +------------- web_src/js/modules/codeeditor/utils.ts | 18 ++----- web_src/js/render/ansi.test.ts | 15 ++++-- web_src/js/render/ansi.ts | 43 +++++++++++++-- web_src/js/utils/url.test.ts | 59 ++++++++++++--------- web_src/js/utils/url.ts | 41 +++++--------- 7 files changed, 104 insertions(+), 117 deletions(-) diff --git a/web_src/js/components/ActionRunView.ts b/web_src/js/components/ActionRunView.ts index 540274dfb3..52fcdf56cd 100644 --- a/web_src/js/components/ActionRunView.ts +++ b/web_src/js/components/ActionRunView.ts @@ -1,5 +1,5 @@ import {createElementFromAttrs} from '../utils/dom.ts'; -import {renderAnsi} from '../render/ansi.ts'; +import {renderAnsiInto} from '../render/ansi.ts'; import {reactive} from 'vue'; import type {ActionsArtifact, ActionsJob, ActionsRun, ActionsStatus} from '../modules/gitea-actions.ts'; import type {IntervalId} from '../types.ts'; @@ -80,10 +80,10 @@ export function createLogLineMessage(line: LogLine, cmd: LogLineCommand | null) if (label) { logMsg.append(createElementFromAttrs('span', {class: 'log-msg-label'}, `${label}:`)); const msgSpan = document.createElement('span'); - msgSpan.innerHTML = ` ${renderAnsi(msgContent.trimStart())}`; + renderAnsiInto(msgSpan, ` ${msgContent.trimStart()}`); logMsg.append(msgSpan); } else { - logMsg.innerHTML = renderAnsi(msgContent); + renderAnsiInto(logMsg, msgContent); } return logMsg; } diff --git a/web_src/js/modules/codeeditor/utils.test.ts b/web_src/js/modules/codeeditor/utils.test.ts index 2bf3e74967..dd903fa78c 100644 --- a/web_src/js/modules/codeeditor/utils.test.ts +++ b/web_src/js/modules/codeeditor/utils.test.ts @@ -1,41 +1,4 @@ -import {findUrlAtPosition, trimUrlPunctuation, urlRawRegex} from './utils.ts'; - -function matchUrls(text: string): string[] { - return Array.from(text.matchAll(urlRawRegex), (m) => trimUrlPunctuation(m[0])); -} - -test('matchUrls', () => { - expect(matchUrls('visit https://example.com for info')).toEqual(['https://example.com']); - expect(matchUrls('see https://example.com.')).toEqual(['https://example.com']); - expect(matchUrls('see https://example.com, and')).toEqual(['https://example.com']); - expect(matchUrls('see https://example.com; and')).toEqual(['https://example.com']); - expect(matchUrls('(https://example.com)')).toEqual(['https://example.com']); - expect(matchUrls('"https://example.com"')).toEqual(['https://example.com']); - expect(matchUrls('https://example.com/path?q=1&b=2#hash')).toEqual(['https://example.com/path?q=1&b=2#hash']); - expect(matchUrls('https://example.com/path?q=1&b=2#hash.')).toEqual(['https://example.com/path?q=1&b=2#hash']); - expect(matchUrls('https://x.co')).toEqual(['https://x.co']); - expect(matchUrls('https://example.com/path_(wiki)')).toEqual(['https://example.com/path_(wiki)']); - expect(matchUrls('https://en.wikipedia.org/wiki/Rust_(programming_language)')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']); - expect(matchUrls('(https://en.wikipedia.org/wiki/Rust_(programming_language))')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']); - expect(matchUrls('http://example.com')).toEqual(['http://example.com']); - expect(matchUrls('no url here')).toEqual([]); - expect(matchUrls('https://a.com and https://b.com')).toEqual(['https://a.com', 'https://b.com']); - expect(matchUrls('[![](https://img.shields.io/npm/v/pkg.svg?style=flat)](https://www.npmjs.org/package/pkg)')).toEqual(['https://img.shields.io/npm/v/pkg.svg?style=flat', 'https://www.npmjs.org/package/pkg']); -}); - -test('trimUrlPunctuation', () => { - expect(trimUrlPunctuation('https://example.com.')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com,')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com;')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com:')).toEqual('https://example.com'); - expect(trimUrlPunctuation("https://example.com'")).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com"')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com.,;')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://example.com/path')).toEqual('https://example.com/path'); - expect(trimUrlPunctuation('https://example.com/path_(wiki)')).toEqual('https://example.com/path_(wiki)'); - expect(trimUrlPunctuation('https://example.com)')).toEqual('https://example.com'); - expect(trimUrlPunctuation('https://en.wikipedia.org/wiki/Rust_(lang))')).toEqual('https://en.wikipedia.org/wiki/Rust_(lang)'); -}); +import {findUrlAtPosition} from './utils.ts'; test('findUrlAtPosition', () => { const doc = 'visit https://example.com for info'; diff --git a/web_src/js/modules/codeeditor/utils.ts b/web_src/js/modules/codeeditor/utils.ts index a40cf191fe..108c2a5b49 100644 --- a/web_src/js/modules/codeeditor/utils.ts +++ b/web_src/js/modules/codeeditor/utils.ts @@ -1,5 +1,6 @@ import type {EditorView, ViewUpdate} from '@codemirror/view'; import type {CodemirrorModules} from './main.ts'; +import {trimUrlPunctuation, urlRawRegex} from '../../utils/url.ts'; /** Remove trailing whitespace from all lines in the editor. */ export function trimTrailingWhitespaceFromView(view: EditorView): void { @@ -15,22 +16,9 @@ export function trimTrailingWhitespaceFromView(view: EditorView): void { if (changes.length) view.dispatch({changes}); } -/** Matches URLs, excluding characters that are never valid unencoded in URLs per RFC 3986. */ -export const urlRawRegex = /\bhttps?:\/\/[^\s<>[\]]+/gi; - -/** Strip trailing punctuation that is likely not part of the URL. */ -export function trimUrlPunctuation(url: string): string { - url = url.replace(/[.,;:'"]+$/, ''); - // Strip trailing closing parens only if unbalanced (not part of the URL like Wikipedia links) - while (url.endsWith(')') && (url.match(/\(/g) || []).length < (url.match(/\)/g) || []).length) { - url = url.slice(0, -1); - } - return url; -} - /** Find the URL at the given character position in a document string, or null if none. */ export function findUrlAtPosition(doc: string, pos: number): string | null { - for (const match of doc.matchAll(urlRawRegex)) { + for (const match of doc.matchAll(urlRawRegex())) { const url = trimUrlPunctuation(match[0]); if (match.index !== undefined && pos >= match.index && pos < match.index + url.length) { return url; @@ -67,7 +55,7 @@ export function goToDefinitionAt(cm: CodemirrorModules, view: EditorView, pos: n export function clickableUrls(cm: CodemirrorModules) { const urlMark = cm.view.Decoration.mark({class: 'cm-url'}); const urlDecorator = new cm.view.MatchDecorator({ - regexp: urlRawRegex, + regexp: urlRawRegex(), decorate: (add, from, _to, match) => { const trimmed = trimUrlPunctuation(match[0]); add(from, from + trimmed.length, urlMark); diff --git a/web_src/js/render/ansi.test.ts b/web_src/js/render/ansi.test.ts index 2d9b8ede00..e7e7af8928 100644 --- a/web_src/js/render/ansi.test.ts +++ b/web_src/js/render/ansi.test.ts @@ -1,6 +1,12 @@ -import {renderAnsi} from './ansi.ts'; +import {renderAnsiInto} from './ansi.ts'; test('renderAnsi', () => { + const renderAnsi = (line: string) => { + const el = document.createElement('div'); + renderAnsiInto(el, line); + return el.innerHTML; + }; + expect(renderAnsi('abc')).toEqual('abc'); expect(renderAnsi('abc\n')).toEqual('abc'); expect(renderAnsi('abc\r\n')).toEqual('abc'); @@ -20,6 +26,9 @@ test('renderAnsi', () => { // URLs in ANSI output become clickable links const link = (url: string) => `${url}`; - expect(renderAnsi('Downloading https://github.com/actions/upload-artifact/releases')).toEqual(`Downloading ${link('https://github.com/actions/upload-artifact/releases')}`); - expect(renderAnsi('\x1b[32mhttps://proxy.golang.org/cached-only\x1b[0m')).toEqual(`${link('https://proxy.golang.org/cached-only')}`); + expect(renderAnsi('foo https://example.com bar')).toEqual(`foo ${link('https://example.com')} bar`); + expect(renderAnsi('')).toEqual(`<${link('https://example.com?a=b&c=d#h')}>`); + expect(renderAnsi('open https://example.com.')).toEqual(`open ${link('https://example.com')}.`); + expect(renderAnsi('"https://example.com"')).toEqual(`"${link('https://example.com')}"`); + expect(renderAnsi('\x1b[32mhttps://example.com\x1b[0m')).toEqual(`${link('https://example.com')}`); }); diff --git a/web_src/js/render/ansi.ts b/web_src/js/render/ansi.ts index 4625e54233..abab3a212c 100644 --- a/web_src/js/render/ansi.ts +++ b/web_src/js/render/ansi.ts @@ -1,5 +1,5 @@ import {AnsiUp} from 'ansi_up'; -import {linkifyURLs} from '../utils/url.ts'; +import {trimUrlPunctuation, urlRawRegex} from '../utils/url.ts'; const replacements: Array<[RegExp, string]> = [ [/\x1b\[\d+[A-H]/g, ''], // Move cursor, treat them as no-op @@ -7,7 +7,7 @@ const replacements: Array<[RegExp, string]> = [ ]; // render ANSI to HTML -export function renderAnsi(line: string): string { +export function renderAnsiInto(el: HTMLElement, line: string) { // create a fresh ansi_up instance because otherwise previous renders can influence // the output of future renders, because ansi_up is stateful and remembers things like // unclosed opening tags for colors. @@ -44,5 +44,42 @@ export function renderAnsi(line: string): string { result = lines.join('\n'); } - return linkifyURLs(result); + el.innerHTML = result; + // at the moment, only need to do post-process when there are potential URL links + if (result.includes('://')) renderAnsiPostProcessNode(el); +} + +function renderAnsiProcessText(node: ChildNode): ChildNode { + const text = node.textContent!; + const match = urlRawRegex().exec(text); + if (!match || match.index === undefined) return node; + + const before = text.slice(0, match.index); + const urlMatched = match[0]; + const urlTrimmed = trimUrlPunctuation(urlMatched); + const after = text.slice(match.index + urlMatched.length - (urlMatched.length - urlTrimmed.length)); + + const link = document.createElement('a'); + link.setAttribute('href', urlTrimmed); + link.setAttribute('target', '_blank'); + link.textContent = urlTrimmed; + + const newNodes: Array = []; + if (before) newNodes.push(before); + newNodes.push(link); + if (after) newNodes.push(after); + + node.replaceWith(...newNodes); + return link; +} + +function renderAnsiPostProcessNode(el: ChildNode) { + for (let node = el.firstChild; node; node = node.nextSibling) { + if (node.nodeName === 'A') continue; + if (node.nodeType !== Node.TEXT_NODE) { + renderAnsiPostProcessNode(node); + continue; + } + node = renderAnsiProcessText(node); + } } diff --git a/web_src/js/utils/url.test.ts b/web_src/js/utils/url.test.ts index 0a7e27ca61..580b03eccc 100644 --- a/web_src/js/utils/url.test.ts +++ b/web_src/js/utils/url.test.ts @@ -1,4 +1,4 @@ -import {linkifyURLs, pathEscape, pathEscapeSegments, urlQueryEscape} from './url.ts'; +import {pathEscape, pathEscapeSegments, trimUrlPunctuation, urlQueryEscape, urlRawRegex} from './url.ts'; describe('escape', () => { const queryNonAscii = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; @@ -19,29 +19,36 @@ describe('escape', () => { }); }); -test('linkifyURLs', () => { - const link = (url: string) => `${url}`; - expect(linkifyURLs('https://example.com')).toEqual(link('https://example.com')); - expect(linkifyURLs('https://dl.google.com/go/go1.23.6.linux-amd64.tar.gz')).toEqual(link('https://dl.google.com/go/go1.23.6.linux-amd64.tar.gz')); - expect(linkifyURLs('https://example.com/path?query=1&b=2#frag')).toEqual(link('https://example.com/path?query=1&b=2#frag')); - expect(linkifyURLs('visit https://example.com/repo for info')).toEqual(`visit ${link('https://example.com/repo')} for info`); - expect(linkifyURLs('See https://example.com.')).toEqual(`See ${link('https://example.com')}.`); - expect(linkifyURLs('https://example.com, and more')).toEqual(`${link('https://example.com')}, and more`); - expect(linkifyURLs('https://proxy.golang.org/cached-only')).toEqual(`${link('https://proxy.golang.org/cached-only')}`); - expect(linkifyURLs('https://registry.npmjs.org/@types/node')).toEqual(`${link('https://registry.npmjs.org/@types/node')}`); - expect(linkifyURLs('https://a.com and https://b.org')).toEqual(`${link('https://a.com')} and ${link('https://b.org')}`); - expect(linkifyURLs('no urls here')).toEqual('no urls here'); - expect(linkifyURLs('http://example.com/path')).toEqual(link('http://example.com/path')); - expect(linkifyURLs('http://localhost:3000/repo')).toEqual(link('http://localhost:3000/repo')); - expect(linkifyURLs('https://')).toEqual('https://'); - expect(linkifyURLs('Click here')).toEqual('Click here'); - expect(linkifyURLs('Click here')).toEqual('Click here'); - expect(linkifyURLs('https://example.com')).toEqual('https://example.com'); - expect(linkifyURLs('https://evil.com/')).toEqual(`${link('https://evil.com/')}`); - expect(linkifyURLs('https://evil.com/"onmouseover="alert(1)')).toEqual(`${link('https://evil.com/')}"onmouseover="alert(1)`); - expect(linkifyURLs('javascript:alert(1)')).toEqual('javascript:alert(1)'); // eslint-disable-line no-script-url - expect(linkifyURLs("https://evil.com/'onclick='alert(1)")).toEqual(`${link('https://evil.com/')}'onclick='alert(1)`); - expect(linkifyURLs('data:text/html,')).toEqual('data:text/html,'); - expect(linkifyURLs('https://evil.com/\nonclick=alert(1)')).toEqual(`${link('https://evil.com/')}\nonclick=alert(1)`); - expect(linkifyURLs('https://evil.com/"onmouseover=alert(1)')).toEqual(`${link('https://evil.com/"onmouseover=alert')}(1)`); +test('matchUrls', () => { + const matchUrls = (text: string) => Array.from(text.matchAll(urlRawRegex()), (m) => trimUrlPunctuation(m[0])); + expect(matchUrls('visit https://example.com for info')).toEqual(['https://example.com']); + expect(matchUrls('see https://example.com.')).toEqual(['https://example.com']); + expect(matchUrls('see https://example.com, and')).toEqual(['https://example.com']); + expect(matchUrls('see https://example.com; and')).toEqual(['https://example.com']); + expect(matchUrls('(https://example.com)')).toEqual(['https://example.com']); + expect(matchUrls('"https://example.com"')).toEqual(['https://example.com']); + expect(matchUrls('https://example.com/path?q=1&b=2#hash')).toEqual(['https://example.com/path?q=1&b=2#hash']); + expect(matchUrls('https://example.com/path?q=1&b=2#hash.')).toEqual(['https://example.com/path?q=1&b=2#hash']); + expect(matchUrls('https://x.co')).toEqual(['https://x.co']); + expect(matchUrls('https://example.com/path_(wiki)')).toEqual(['https://example.com/path_(wiki)']); + expect(matchUrls('https://en.wikipedia.org/wiki/Rust_(programming_language)')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']); + expect(matchUrls('(https://en.wikipedia.org/wiki/Rust_(programming_language))')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']); + expect(matchUrls('http://example.com')).toEqual(['http://example.com']); + expect(matchUrls('no url here')).toEqual([]); + expect(matchUrls('https://a.com and https://b.com')).toEqual(['https://a.com', 'https://b.com']); + expect(matchUrls('[![](https://img.shields.io/npm/v/pkg.svg?style=flat)](https://www.npmjs.org/package/pkg)')).toEqual(['https://img.shields.io/npm/v/pkg.svg?style=flat', 'https://www.npmjs.org/package/pkg']); +}); + +test('trimUrlPunctuation', () => { + expect(trimUrlPunctuation('https://example.com.')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com,')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com;')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com:')).toEqual('https://example.com'); + expect(trimUrlPunctuation("https://example.com'")).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com"')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com.,;')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://example.com/path')).toEqual('https://example.com/path'); + expect(trimUrlPunctuation('https://example.com/path_(wiki)')).toEqual('https://example.com/path_(wiki)'); + expect(trimUrlPunctuation('https://example.com)')).toEqual('https://example.com'); + expect(trimUrlPunctuation('https://en.wikipedia.org/wiki/Rust_(lang))')).toEqual('https://en.wikipedia.org/wiki/Rust_(lang)'); }); diff --git a/web_src/js/utils/url.ts b/web_src/js/utils/url.ts index 06e1aa2951..441c1e26e1 100644 --- a/web_src/js/utils/url.ts +++ b/web_src/js/utils/url.ts @@ -1,4 +1,15 @@ -import {html, htmlRaw} from './html.ts'; +/** Matches URLs, excluding characters that are never valid unencoded in URLs per RFC 3986. */ +export const urlRawRegex = () => /\bhttps?:\/\/[^\s<>[\]]+/gi; // JS regexp has internal states, so always use a new instance + +/** Strip trailing punctuation that is likely not part of the URL. */ +export function trimUrlPunctuation(url: string): string { + url = url.replace(/[.,;:'"]+$/, ''); + // Strip trailing closing parens only if unbalanced (not part of the URL like Wikipedia links) + while (url.endsWith(')') && (url.match(/\(/g) || []).length < (url.match(/\)/g) || []).length) { + url = url.slice(0, -1); + } + return url; +} export function urlQueryEscape(s: string) { // See "TestQueryEscape" in backend @@ -31,31 +42,3 @@ export function pathEscapeSegments(s: string): string { // The same as backend's PathEscapeSegments return s.split('/').map(pathEscape).join('/'); } - -// Match HTML tags (to skip) or URLs (to linkify) in HTML content -const urlLinkifyPattern = /(<([-\w]+)[^>]*>)|(<\/([-\w]+)[^>]*>)|(https?:\/\/[^\s<>"'`|(){}[\]]+)/gi; -const trailingPunctPattern = /[.,;:!?]+$/; - -// Convert URLs to clickable links in HTML, preserving existing HTML tags -export function linkifyURLs(htmlString: string): string { - let inAnchor = false; - return htmlString.replace(urlLinkifyPattern, (match, _openTagFull, openTag, _closeTagFull, closeTag, url) => { - // skip URLs inside existing tags - if (openTag === 'a') { - inAnchor = true; - return match; - } else if (closeTag === 'a') { - inAnchor = false; - return match; - } - if (inAnchor || !url) { - return match; - } - - const trailingPunct = url.match(trailingPunctPattern); - const cleanUrl = trailingPunct ? url.slice(0, -trailingPunct[0].length) : url; - const trailing = trailingPunct ? trailingPunct[0] : ''; - // safe because regexp only matches valid URLs (no quotes or angle brackets) - return html`${htmlRaw(cleanUrl)}${htmlRaw(trailing)}`; - }); -} -- 2.54.0 From 1c718da16c0d2420465066807996b10db993959b Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 28 Jun 2026 14:14:39 +0200 Subject: [PATCH 040/169] fix(api): support HEAD requests on all API GET endpoints (#38245) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #38226 ## Summary Add `chi_middleware.GetHead` as the first `BeforeRouting` middleware on the API router. This makes every API `GET` endpoint automatically handle `HEAD` requests, as required by RFC 9110 §9.3.2. Previously, `HEAD` requests to endpoints like `GET /repos/{owner}/{repo}/git/commits/{sha}` returned `405 Method Not Allowed`. The web router already used this same middleware (see `routers/web/web.go:261`), so this aligns API behaviour with the web router. ## Changes - `routers/api/v1/api.go`: add `chi_middleware.GetHead` middleware to the API router - `tests/integration/api_repo_git_commits_test.go`: add `TestAPIReposGitCommitsHEAD` verifying HEAD returns 200 on a valid ref and 404 (not 405) on a missing ref --- routers/api/v1/api.go | 4 ++++ tests/integration/api_repo_git_commits_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 50135c0d7a..6d78121190 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -99,6 +99,7 @@ import ( _ "gitea.dev/routers/api/v1/swagger" // for swagger generation "gitea.com/go-chi/binding" + chi_middleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) @@ -950,6 +951,9 @@ func checkDeprecatedAuthMethods(ctx *context.APIContext) { func Routes() *web.Router { m := web.NewRouter() + // redirect HEAD requests to GET if no HEAD handler is defined (RFC 9110 §9.3.2) + m.BeforeRouting(chi_middleware.GetHead) + if setting.CORSConfig.Enabled { m.BeforeRouting(cors.Handler(cors.Options{ AllowedOrigins: setting.CORSConfig.AllowDomain, diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index 504d8e933c..9d460a20c0 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -56,6 +56,23 @@ func TestAPIReposGitCommits(t *testing.T) { } } +func TestAPIReposGitCommitsHEAD(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // HEAD on a valid ref must return 200 (RFC 9110 §9.3.2) + req := NewRequestf(t, "HEAD", "/api/v1/repos/%s/repo1/git/commits/master", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + // HEAD on a missing sha must return 404, not 405 + req = NewRequestf(t, "HEAD", "/api/v1/repos/%s/repo1/git/commits/12345", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) +} + func TestAPIReposGitCommitList(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) -- 2.54.0 From cc1df1976be3593c93cdcd28b9b4fba6cb23a6ec Mon Sep 17 00:00:00 2001 From: bircni Date: Sun, 28 Jun 2026 20:29:34 +0200 Subject: [PATCH 041/169] fix: codemirror regressions (#38248) --- web_src/css/easymde.css | 27 +++++++------------------- web_src/css/modules/codeeditor.css | 31 ++++++------------------------ 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/web_src/css/easymde.css b/web_src/css/easymde.css index 9de6a1d431..0d513cb8e2 100644 --- a/web_src/css/easymde.css +++ b/web_src/css/easymde.css @@ -441,25 +441,12 @@ padding: 5px; } -.EasyMDEContainer .CodeMirror .cm-keyword { color: var(--color-syntax-keyword); } -.EasyMDEContainer .CodeMirror .cm-atom { color: var(--color-syntax-keyword); } -.EasyMDEContainer .CodeMirror .cm-number { color: var(--color-syntax-number); } -.EasyMDEContainer .CodeMirror .cm-def { color: var(--color-syntax-name); } -.EasyMDEContainer .CodeMirror .cm-variable-2 { color: var(--color-syntax-name); } -.EasyMDEContainer .CodeMirror .cm-variable-3 { color: var(--color-syntax-type); } -.EasyMDEContainer .CodeMirror .cm-property { color: var(--color-syntax-name); } -.EasyMDEContainer .CodeMirror .cm-comment { color: var(--color-syntax-comment); } -.EasyMDEContainer .CodeMirror .cm-string { color: var(--color-syntax-string); } -.EasyMDEContainer .CodeMirror .cm-string-2 { color: var(--color-syntax-regexp); } -.EasyMDEContainer .CodeMirror .cm-meta { color: var(--color-syntax-control); } -.EasyMDEContainer .CodeMirror .cm-qualifier { color: var(--color-syntax-control); } -.EasyMDEContainer .CodeMirror .cm-builtin { color: var(--color-syntax-keyword); } -.EasyMDEContainer .CodeMirror .cm-bracket { color: var(--color-syntax-operator); } -.EasyMDEContainer .CodeMirror .cm-tag { color: var(--color-syntax-type); } .EasyMDEContainer .CodeMirror .cm-attribute { color: var(--color-syntax-name); } -.EasyMDEContainer .CodeMirror .cm-header { color: var(--color-syntax-type); } -.EasyMDEContainer .CodeMirror .cm-quote { color: var(--color-syntax-comment); } -.EasyMDEContainer .CodeMirror .cm-hr { color: var(--color-syntax-operator); } -.EasyMDEContainer .CodeMirror .cm-url { color: var(--color-syntax-link); } -.EasyMDEContainer .CodeMirror .cm-link { color: var(--color-syntax-link); } +.EasyMDEContainer .CodeMirror .cm-bracket { color: var(--color-syntax-operator); } +.EasyMDEContainer .CodeMirror .cm-comment { color: var(--color-syntax-comment); } .EasyMDEContainer .CodeMirror .cm-error { color: var(--color-syntax-invalid); } +.EasyMDEContainer .CodeMirror .cm-hr { color: var(--color-syntax-operator); } +.EasyMDEContainer .CodeMirror .cm-link { color: var(--color-syntax-link); } +.EasyMDEContainer .CodeMirror .cm-quote { color: var(--color-syntax-comment); } +.EasyMDEContainer .CodeMirror .cm-tag { color: var(--color-syntax-type); } +.EasyMDEContainer .CodeMirror .cm-url { color: var(--color-syntax-link); } diff --git a/web_src/css/modules/codeeditor.css b/web_src/css/modules/codeeditor.css index 3212c04c83..d4be96845b 100644 --- a/web_src/css/modules/codeeditor.css +++ b/web_src/css/modules/codeeditor.css @@ -17,6 +17,11 @@ border: 1px solid var(--color-secondary); } +.code-editor-container { + position: relative; + min-height: 90vh; +} + /* editor layout */ .code-editor-container .cm-editor { color: var(--color-text); @@ -24,8 +29,6 @@ font-family: var(--fonts-monospace); font-size: 12px; max-height: 90vh; - flex: 1; - min-height: 0; } .code-editor-container .cm-editor, @@ -33,20 +36,8 @@ border-radius: 0 0 var(--border-radius) var(--border-radius); } -.code-editor-container .cm-scroller { - overflow: auto; - line-height: var(--line-height-code); - flex: 1; - min-height: 0; -} - .code-editor-container .cm-content { - align-self: stretch; - padding: 0; -} - -.code-editor-container .cm-content * { - caret-color: inherit; + caret-color: var(--color-caret); } .code-editor-container .cm-cursor, @@ -54,10 +45,6 @@ border-left-color: var(--color-caret); } -.code-editor-container .cm-editor.cm-focused { - outline: none; -} - .code-editor-container .cm-editor.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .code-editor-container .cm-selectionBackground { background-color: var(--color-primary-alpha-30); @@ -336,12 +323,6 @@ } /* command palette */ -.code-editor-container { - position: relative; - min-height: 90vh; - display: flex; - flex-direction: column; -} .cm-command-palette { position: absolute; -- 2.54.0 From 98c61942aa433342eacf08e4040ded80b1d0efe1 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 28 Jun 2026 21:18:12 +0200 Subject: [PATCH 042/169] build(sign): move to sigstore (#38250) drops signing with gpg in favor of sigstore based artifact signing --- .github/workflows/release-nightly.yml | 14 ++++---------- .github/workflows/release-tag-rc.yml | 14 ++++---------- .github/workflows/release-tag-version.yml | 14 ++++---------- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index d982dd525b..483f4b7731 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -13,6 +13,7 @@ jobs: runs-on: namespace-profile-gitea-release-binary permissions: contents: read + id-token: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -33,19 +34,12 @@ jobs: - run: make release env: TAGS: bindata - - name: import gpg key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 - with: - gpg_private_key: ${{ secrets.GPGSIGN_KEY }} - passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: sign binaries - env: - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do - echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" + cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes done # clean branch name to get the folder name in S3 - name: Get cleaned branch name diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 9e796c47d4..91b6168330 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -14,6 +14,7 @@ jobs: runs-on: namespace-profile-gitea-release-binary permissions: contents: read + id-token: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -34,19 +35,12 @@ jobs: - run: make release env: TAGS: bindata - - name: import gpg key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 - with: - gpg_private_key: ${{ secrets.GPGSIGN_KEY }} - passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: sign binaries - env: - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do - echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" + cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes done # clean branch name to get the folder name in S3 - name: Get cleaned branch name diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 81a309757b..d4e3f1da00 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -17,6 +17,7 @@ jobs: permissions: contents: read packages: write # to publish to ghcr.io + id-token: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -37,19 +38,12 @@ jobs: - run: make release env: TAGS: bindata - - name: import gpg key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 - with: - gpg_private_key: ${{ secrets.GPGSIGN_KEY }} - passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - name: sign binaries - env: - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do - echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" + cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes done # clean branch name to get the folder name in S3 - name: Get cleaned branch name -- 2.54.0 From 4812e354866a066dcb899af667b0fad5fa094065 Mon Sep 17 00:00:00 2001 From: Augusto Xavier Date: Sun, 28 Jun 2026 16:58:25 -0300 Subject: [PATCH 043/169] fix(api): respect since/until when counting commits for X-Total-Count (#38204) The repository commits API (`GET /repos/{owner}/{repo}/commits`) accepts `since` and `until` query parameters and filters the returned page of commits by commit date. However, the `X-Total-Count` and `X-Total` response headers reported the *unfiltered* total number of commits, so the advertised total could be far larger than the number of commits actually returned for the requested date range. With a range that matches no commits, the page is correctly empty while the headers still claim the full repository total. ## Root cause `gitrepo.CommitsCount` declared `Since` and `Until` options and the API handler populated them, but the function never appended `--since`/`--until` to the underlying `git rev-list --count` invocation. The date filters were silently dropped, so the count always reflected the entire revision history. ## Fix Pass the `Since`/`Until` options through to `git rev-list`, mirroring the existing commit-listing path (`commitsByRangeWithTime`). The reported total now matches the filtered range used to build the page. ## Testing Added `TestCommitsCountWithSinceUntil` in `modules/gitrepo/commit_test.go`, a table-driven unit test against the `repo1_bare` fixture covering `since`, `until`, and a bounded `since`+`until` range. It fails on the pre-fix code (every case returns the full count of 3) and passes after the change. Existing `CommitsCount` tests remain green. ## Notes - No new settings, no default changes; this corrects an incorrect header value and is backward compatible. Clients that depend on `since`/`until` already filter the returned commits, and the headers now agree with that filtering. Fixes #35886. --- *AI-assistance disclosure:* this change was developed with the assistance of Claude Code (Claude Opus 4.8). I have reviewed and understand the change and take responsibility for it. --- modules/gitrepo/commit.go | 8 ++++++++ modules/gitrepo/commit_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/modules/gitrepo/commit.go b/modules/gitrepo/commit.go index 38785dcb97..24df90c015 100644 --- a/modules/gitrepo/commit.go +++ b/modules/gitrepo/commit.go @@ -32,6 +32,14 @@ func CommitsCount(ctx context.Context, repo Repository, opts CommitsCountOptions cmd.AddOptionValues("--not", opts.Not) } + if opts.Since != "" { + cmd.AddOptionFormat("--since=%s", opts.Since) + } + + if opts.Until != "" { + cmd.AddOptionFormat("--until=%s", opts.Until) + } + if len(opts.RelPath) > 0 { cmd.AddDashesAndList(opts.RelPath...) } diff --git a/modules/gitrepo/commit_test.go b/modules/gitrepo/commit_test.go index 05cedc39ef..638795a985 100644 --- a/modules/gitrepo/commit_test.go +++ b/modules/gitrepo/commit_test.go @@ -34,6 +34,37 @@ func TestCommitsCountWithoutBase(t *testing.T) { assert.Equal(t, int64(2), commitsCount) } +func TestCommitsCountWithSinceUntil(t *testing.T) { + bareRepo1 := &mockRepository{path: "repo1_bare"} + revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"} + + // The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19. + cases := []struct { + name string + since string + until string + expected int64 + }{ + {name: "no filter", expected: 3}, + {name: "since keeps newer commits", since: "2018-01-01", expected: 1}, + {name: "until keeps older commits", until: "2018-01-01", expected: 2}, + {name: "since and until bound the range", since: "2017-12-19T22:16:00-08:00", until: "2018-01-01", expected: 1}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + commitsCount, err := CommitsCount(t.Context(), bareRepo1, + CommitsCountOptions{ + Revision: revision, + Since: tc.since, + Until: tc.until, + }) + + assert.NoError(t, err) + assert.Equal(t, tc.expected, commitsCount) + }) + } +} + func TestGetLatestCommitTime(t *testing.T) { bareRepo1 := &mockRepository{path: "repo1_bare"} lct, err := GetLatestCommitTime(t.Context(), bareRepo1) -- 2.54.0 From 4f41ad7b91de05045a98e2407e95f95db02c84ed Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 28 Jun 2026 22:44:26 +0200 Subject: [PATCH 044/169] revert(sign): restore gpg (#38251) partially revert sigstore signing to avoid causing breaking change for v1.27 --- .github/workflows/release-nightly.yml | 10 ++++++++++ .github/workflows/release-tag-rc.yml | 10 ++++++++++ .github/workflows/release-tag-version.yml | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 483f4b7731..e3997c8123 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -36,10 +36,20 @@ jobs: TAGS: bindata - name: Install Cosign uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} - name: sign binaries + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes + echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" done # clean branch name to get the folder name in S3 - name: Get cleaned branch name diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 91b6168330..03f17e8c23 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -37,10 +37,20 @@ jobs: TAGS: bindata - name: Install Cosign uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} - name: sign binaries + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes + echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" done # clean branch name to get the folder name in S3 - name: Get cleaned branch name diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index d4e3f1da00..09f9a73971 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -40,10 +40,20 @@ jobs: TAGS: bindata - name: Install Cosign uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} - name: sign binaries + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GPG_PASSPHRASE: ${{ secrets.GPGSIGN_PASSPHRASE }} run: | for f in dist/release/*; do cosign sign-blob "$f" --bundle "$f.sigstore.json" --yes + echo "$GPG_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u "$GPG_FINGERPRINT" --output "$f.asc" "$f" done # clean branch name to get the folder name in S3 - name: Get cleaned branch name -- 2.54.0 From c6b2394585618461ca2d8da99e8b27433f0cbeb3 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:36:28 -0700 Subject: [PATCH 045/169] fix(actions): authenticate snapcraft before nightly remote build (#38252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `release-nightly-snapcraft` workflow’s `build-and-publish` job was failing because `snapcraft remote-build` fell back to interactive Launchpad authorization in CI. This change makes authentication explicit and non-interactive before the remote build step. - **Workflow change** - Add an `Authenticate snapcraft` step before `Remote build`. - Run `snapcraft login --with` using the existing `SNAPCRAFT_STORE_CREDENTIALS` secret. - Pin that step to `shell: bash` to support process substitution. - **Why this fixes the failure** - Prevents CI from entering browser-based Launchpad auth flow. - Ensures `remote-build` runs with preloaded credentials. ```yaml - name: Authenticate snapcraft shell: bash env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} run: snapcraft login --with <(printf '%s' "$SNAPCRAFT_STORE_CREDENTIALS") ``` --------- Signed-off-by: Lunny Xiao Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Lunny Xiao Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release-nightly-snapcraft.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release-nightly-snapcraft.yml b/.github/workflows/release-nightly-snapcraft.yml index 7afca350c2..f89654e40b 100644 --- a/.github/workflows/release-nightly-snapcraft.yml +++ b/.github/workflows/release-nightly-snapcraft.yml @@ -22,6 +22,10 @@ jobs: - name: Install snapcraft run: sudo snap install snapcraft --classic + - name: Authenticate snapcraft + shell: bash + run: snapcraft login --with <(printf '%s' "$SNAPCRAFT_STORE_CREDENTIALS") + - name: Remote build run: | snapcraft remote-build \ -- 2.54.0 From 8343c47bd112570f9c3537c61cbcbe1a124b0e6a Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Mon, 29 Jun 2026 01:20:03 +0000 Subject: [PATCH 046/169] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.json | 3 +++ options/locale/locale_de-DE.json | 3 +++ options/locale/locale_el-GR.json | 3 +++ options/locale/locale_es-ES.json | 2 ++ options/locale/locale_fa-IR.json | 2 ++ options/locale/locale_fi-FI.json | 1 + options/locale/locale_fr-FR.json | 3 +++ options/locale/locale_ga-IE.json | 3 +++ options/locale/locale_hu-HU.json | 1 + options/locale/locale_id-ID.json | 1 + options/locale/locale_is-IS.json | 4 +++- options/locale/locale_it-IT.json | 2 ++ options/locale/locale_ja-JP.json | 6 ++++++ options/locale/locale_ko-KR.json | 3 +++ options/locale/locale_lv-LV.json | 3 +++ options/locale/locale_nl-NL.json | 2 ++ options/locale/locale_pl-PL.json | 2 ++ options/locale/locale_pt-BR.json | 2 ++ options/locale/locale_pt-PT.json | 7 +++++++ options/locale/locale_ru-RU.json | 3 +++ options/locale/locale_si-LK.json | 2 ++ options/locale/locale_sk-SK.json | 4 +++- options/locale/locale_sv-SE.json | 3 ++- options/locale/locale_tr-TR.json | 3 +++ options/locale/locale_uk-UA.json | 2 ++ options/locale/locale_zh-CN.json | 3 +++ options/locale/locale_zh-TW.json | 3 +++ 27 files changed, 73 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_cs-CZ.json b/options/locale/locale_cs-CZ.json index 3ee280dff4..1b563c085a 100644 --- a/options/locale/locale_cs-CZ.json +++ b/options/locale/locale_cs-CZ.json @@ -3238,6 +3238,9 @@ "actions.workflow.enable": "Povolit pracovní postup", "actions.workflow.enable_success": "Pracovní postup „%s“ byl úspěšně aktivován.", "actions.workflow.disabled": "Pracovní postup je zakázán.", + "actions.workflow.scope_owner": "Vlastník", + "actions.workflow.scope_global": "Globální", + "actions.workflow.required": "Požadováno", "actions.workflow.run": "Spustit pracovní postup", "actions.workflow.not_found": "Pracovní postup „%s“ nebyl nalezen.", "actions.workflow.run_success": "Pracovní postup „%s“ proběhl úspěšně.", diff --git a/options/locale/locale_de-DE.json b/options/locale/locale_de-DE.json index 64ec247dde..643634cfce 100644 --- a/options/locale/locale_de-DE.json +++ b/options/locale/locale_de-DE.json @@ -3832,6 +3832,9 @@ "actions.workflow.enable": "Workflow aktivieren", "actions.workflow.enable_success": "Workflow '%s' erfolgreich aktiviert.", "actions.workflow.disabled": "Workflow ist deaktiviert.", + "actions.workflow.scope_owner": "Besitzer", + "actions.workflow.scope_global": "Global", + "actions.workflow.required": "Erforderlich", "actions.workflow.run": "Workflow ausführen", "actions.workflow.not_found": "Workflow '%s' wurde nicht gefunden.", "actions.workflow.run_success": "Workflow '%s' erfolgreich ausgeführt.", diff --git a/options/locale/locale_el-GR.json b/options/locale/locale_el-GR.json index a24635258c..ad0012c7a4 100644 --- a/options/locale/locale_el-GR.json +++ b/options/locale/locale_el-GR.json @@ -2951,6 +2951,9 @@ "actions.workflow.enable": "Ενεργοποίηση Ροής Εργασίας", "actions.workflow.enable_success": "Η ροή εργασίας '%s' ενεργοποιήθηκε επιτυχώς.", "actions.workflow.disabled": "Η ροή εργασιών είναι απενεργοποιημένη.", + "actions.workflow.scope_owner": "Ιδιοκτήτης", + "actions.workflow.scope_global": "Γενικό", + "actions.workflow.required": "Απαιτείται", "actions.need_approval_desc": "Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork.", "actions.variables": "Μεταβλητές", "actions.variables.management": "Διαχείριση Μεταβλητών", diff --git a/options/locale/locale_es-ES.json b/options/locale/locale_es-ES.json index ae3d7f2120..6dc1065cdb 100644 --- a/options/locale/locale_es-ES.json +++ b/options/locale/locale_es-ES.json @@ -2910,6 +2910,8 @@ "actions.workflow.enable": "Activar flujo de trabajo", "actions.workflow.enable_success": "Flujo de trabajo '%s' habilitado con éxito.", "actions.workflow.disabled": "El flujo de trabajo está deshabilitado.", + "actions.workflow.scope_owner": "Propietario", + "actions.workflow.required": "Obligatorio", "actions.need_approval_desc": "Necesita aprobación para ejecutar flujos de trabajo para el pull request del fork.", "actions.variables.management": "Gestión de variables", "actions.variables.creation": "Añadir variable", diff --git a/options/locale/locale_fa-IR.json b/options/locale/locale_fa-IR.json index 2b48c0038b..5c0b9c30fa 100644 --- a/options/locale/locale_fa-IR.json +++ b/options/locale/locale_fa-IR.json @@ -2201,5 +2201,7 @@ "actions.runners.version": "نسخه", "actions.runs.commit": "کامیت", "actions.runs.summary": "چکیده", + "actions.workflow.scope_owner": "مالک", + "actions.workflow.required": "الزامی", "actions.general.token_permissions.mode.restricted": "محصور" } diff --git a/options/locale/locale_fi-FI.json b/options/locale/locale_fi-FI.json index f3dc847897..78f29f5d20 100644 --- a/options/locale/locale_fi-FI.json +++ b/options/locale/locale_fi-FI.json @@ -1463,5 +1463,6 @@ "actions.runners.version": "Versio", "actions.runs.branch": "Haara", "actions.runs.summary": "Yhteenveto", + "actions.workflow.scope_owner": "Omistaja", "actions.general.token_permissions.mode.restricted": "Rajoitettu" } diff --git a/options/locale/locale_fr-FR.json b/options/locale/locale_fr-FR.json index 51506c6dbe..de8ac24cc0 100644 --- a/options/locale/locale_fr-FR.json +++ b/options/locale/locale_fr-FR.json @@ -3814,6 +3814,9 @@ "actions.workflow.enable": "Activer le flux de travail", "actions.workflow.enable_success": "Le flux de travail « %s » a bien été activé.", "actions.workflow.disabled": "Le flux de travail est désactivé.", + "actions.workflow.scope_owner": "Propriétaire", + "actions.workflow.scope_global": "Global", + "actions.workflow.required": "Requis", "actions.workflow.run": "Exécuter le flux de travail", "actions.workflow.not_found": "Flux de travail « %s » introuvable.", "actions.workflow.run_success": "Le flux de travail « %s » s’est correctement exécuté.", diff --git a/options/locale/locale_ga-IE.json b/options/locale/locale_ga-IE.json index c1e11779d1..436f9ba416 100644 --- a/options/locale/locale_ga-IE.json +++ b/options/locale/locale_ga-IE.json @@ -3832,6 +3832,9 @@ "actions.workflow.enable": "Cumasaigh sreabhadh oibre", "actions.workflow.enable_success": "Cumasaíodh sreabhadh oibre '%s' go rathúil.", "actions.workflow.disabled": "Tá sreabhadh oibre díchumasaithe", + "actions.workflow.scope_owner": "Úinéir", + "actions.workflow.scope_global": "Domhanda", + "actions.workflow.required": "Riachtanach", "actions.workflow.run": "Rith Sreabhadh Oibre", "actions.workflow.not_found": "Níor aimsíodh sreabhadh oibre '%s'.", "actions.workflow.run_success": "Ritheann sreabhadh oibre '%s' go rathúil.", diff --git a/options/locale/locale_hu-HU.json b/options/locale/locale_hu-HU.json index 95b32580fe..a811901a1e 100644 --- a/options/locale/locale_hu-HU.json +++ b/options/locale/locale_hu-HU.json @@ -1371,5 +1371,6 @@ "actions.runners.status.active": "Aktív", "actions.runners.version": "Verzió", "actions.runs.summary": "Összefoglaló", + "actions.workflow.scope_owner": "Tulajdonos", "actions.general.token_permissions.mode.restricted": "Korlátozott" } diff --git a/options/locale/locale_id-ID.json b/options/locale/locale_id-ID.json index 4f675c04a0..a3d7b76d6e 100644 --- a/options/locale/locale_id-ID.json +++ b/options/locale/locale_id-ID.json @@ -1180,6 +1180,7 @@ "actions.workflow.enable": "Aktifkan Alur Kerja", "actions.workflow.enable_success": "Alur kerja '%s' berhasil diaktifkan.", "actions.workflow.disabled": "Alur kerja dinonaktifkan.", + "actions.workflow.scope_owner": "Pemilik", "actions.need_approval_desc": "Butuh persetujuan untuk menjalankan alur kerja untuk pull request fork.", "actions.variables": "Variabel", "actions.variables.management": "Managemen Variabel", diff --git a/options/locale/locale_is-IS.json b/options/locale/locale_is-IS.json index 81971d6866..b64cfecca3 100644 --- a/options/locale/locale_is-IS.json +++ b/options/locale/locale_is-IS.json @@ -1111,5 +1111,7 @@ "actions.runners.version": "Útgáfa", "actions.runs.commit": "Framlag", "actions.runs.branch": "Grein", - "actions.runs.summary": "Yfirlit" + "actions.runs.summary": "Yfirlit", + "actions.workflow.scope_owner": "Eigandi", + "actions.workflow.required": "Nauðsynlegt" } diff --git a/options/locale/locale_it-IT.json b/options/locale/locale_it-IT.json index b9de3c0bee..c8106f1ce2 100644 --- a/options/locale/locale_it-IT.json +++ b/options/locale/locale_it-IT.json @@ -2352,5 +2352,7 @@ "actions.runners.version": "Versione", "actions.runs.branch": "Ramo", "actions.runs.summary": "Riepilogo", + "actions.workflow.scope_owner": "Proprietario", + "actions.workflow.required": "Richiesto", "actions.general.token_permissions.mode.restricted": "Limitato" } diff --git a/options/locale/locale_ja-JP.json b/options/locale/locale_ja-JP.json index 9df9a0dd12..81b01d6d2e 100644 --- a/options/locale/locale_ja-JP.json +++ b/options/locale/locale_ja-JP.json @@ -2867,8 +2867,11 @@ "org.teams.all_repositories_admin_permission_desc": "このチームはすべてのリポジトリ管理者アクセス権を持ちます: メンバーはリポジトリの読み取り、プッシュ、共同作業者の追加が可能です。", "org.teams.visibility": "公開/非公開", "org.teams.visibility_private": "プライベート", + "org.teams.visibility_private_helper": "チームメンバーと組織のオーナーにのみ表示されます。", "org.teams.visibility_limited": "限定", + "org.teams.visibility_limited_helper": "この組織のすべてのメンバーに表示されます。", "org.teams.visibility_public": "公開", + "org.teams.visibility_public_helper": "サインインしているすべてのユーザーに表示されます。", "org.teams.invite.title": "あなたは組織 %[2]s 内のチーム %[1]s への参加に招待されました。", "org.teams.invite.by": "%s からの招待", "org.teams.invite.description": "下のボタンをクリックしてチームに参加してください。", @@ -3828,6 +3831,9 @@ "actions.workflow.enable": "ワークフローを有効にする", "actions.workflow.enable_success": "ワークフロー '%s' が有効になりました。", "actions.workflow.disabled": "ワークフローは無効です。", + "actions.workflow.scope_owner": "オーナー", + "actions.workflow.scope_global": "グローバル", + "actions.workflow.required": "必須", "actions.workflow.run": "ワークフローを実行", "actions.workflow.not_found": "ワークフロー '%s' が見つかりません。", "actions.workflow.run_success": "ワークフロー '%s' は正常に実行されました。", diff --git a/options/locale/locale_ko-KR.json b/options/locale/locale_ko-KR.json index e09eca7b3d..9fae3f6985 100644 --- a/options/locale/locale_ko-KR.json +++ b/options/locale/locale_ko-KR.json @@ -3817,6 +3817,9 @@ "actions.workflow.enable": "워크플로 활성화", "actions.workflow.enable_success": "워크플로 '%s'가 성공적으로 활성화되었습니다.", "actions.workflow.disabled": "워크플로가 비활성화되었습니다.", + "actions.workflow.scope_owner": "소유자", + "actions.workflow.scope_global": "글로벌", + "actions.workflow.required": "필수 항목", "actions.workflow.run": "워크플로 실행", "actions.workflow.not_found": "워크플로 '%s'를 찾을 수 없습니다.", "actions.workflow.run_success": "워크플로 '%s'가 성공적으로 실행되었습니다.", diff --git a/options/locale/locale_lv-LV.json b/options/locale/locale_lv-LV.json index a151050e63..bfc3efc8ed 100644 --- a/options/locale/locale_lv-LV.json +++ b/options/locale/locale_lv-LV.json @@ -2991,6 +2991,9 @@ "actions.workflow.enable": "Iespējot darbplūsmu", "actions.workflow.enable_success": "Darbplūsma '%s' ir veiksmīgi iespējota.", "actions.workflow.disabled": "Darbplūsma ir atspējota.", + "actions.workflow.scope_owner": "Īpašnieks", + "actions.workflow.scope_global": "Globāls", + "actions.workflow.required": "Obligāts", "actions.need_approval_desc": "Nepieciešams apstiprinājums, lai izpildītu izmaiņu pieprasījumu darbaplūsmas no atdalītiem repozitorijiem.", "actions.variables": "Mainīgie", "actions.variables.management": "Mainīgo pārvaldība", diff --git a/options/locale/locale_nl-NL.json b/options/locale/locale_nl-NL.json index f79e66e924..cc2f26cdd8 100644 --- a/options/locale/locale_nl-NL.json +++ b/options/locale/locale_nl-NL.json @@ -2070,5 +2070,7 @@ "actions.runners.status.active": "Actief", "actions.runners.version": "Versie", "actions.runs.summary": "Overzicht", + "actions.workflow.scope_owner": "Eigenaar", + "actions.workflow.required": "Vereist", "actions.general.token_permissions.mode.restricted": "Beperkt" } diff --git a/options/locale/locale_pl-PL.json b/options/locale/locale_pl-PL.json index f6906c31f4..bdaac8d898 100644 --- a/options/locale/locale_pl-PL.json +++ b/options/locale/locale_pl-PL.json @@ -2085,5 +2085,7 @@ "actions.runners.status.active": "Aktywne", "actions.runners.version": "Wersja", "actions.runs.summary": "Podsumowanie", + "actions.workflow.scope_owner": "Właściciel", + "actions.workflow.required": "Wymagane", "actions.general.token_permissions.mode.restricted": "Ograniczone" } diff --git a/options/locale/locale_pt-BR.json b/options/locale/locale_pt-BR.json index 08ce9b45ca..8d42a84e5c 100644 --- a/options/locale/locale_pt-BR.json +++ b/options/locale/locale_pt-BR.json @@ -3228,6 +3228,8 @@ "actions.workflow.enable": "Habilitar Workflow", "actions.workflow.enable_success": "Workflow '%s' habilitado com sucesso.", "actions.workflow.disabled": "Workflow está desativado.", + "actions.workflow.scope_owner": "Proprietário", + "actions.workflow.required": "Obrigatário", "actions.need_approval_desc": "Precisa de aprovação para executar workflows para pull request do fork.", "actions.variables": "Variáveis", "actions.variables.management": "Gerenciamento de Variáveis", diff --git a/options/locale/locale_pt-PT.json b/options/locale/locale_pt-PT.json index 6393abe4a7..3d3627cf58 100644 --- a/options/locale/locale_pt-PT.json +++ b/options/locale/locale_pt-PT.json @@ -639,6 +639,7 @@ "user.block.blocked": "Bloqueou este utilizador.", "user.block.title": "Bloquear um utilizador", "user.block.info": "Bloquear um utilizador impede que este interaja com os repositórios, tal como abrir ou comentar um pedido de integração ou uma questão.", + "user.block.info.docs": "Saiba mais sobre como bloquear um utilizador.", "user.block.user_to_block": "Utilizador a bloquear", "user.block.note": "Nota", "user.block.note.title": "Nota opcional:", @@ -3828,7 +3829,13 @@ "actions.workflow.enable": "Habilitar sequência de trabalho", "actions.workflow.enable_success": "A sequência de trabalho '%s' foi habilitada com sucesso.", "actions.workflow.disabled": "A sequência de trabalho está desabilitada.", + "actions.workflow.scope_owner": "Proprietário(a)", + "actions.workflow.scope_global": "Global", + "actions.workflow.required": "Obrigatório", "actions.workflow.run": "Executar sequência de trabalho", + "actions.workflow.create_status_badge": "Criar distintivo de estado", + "actions.workflow.status_badge": "Distintivo de estado", + "actions.workflow.status_badge_url": "URL do distintivo", "actions.workflow.not_found": "A sequência de trabalho '%s' não foi encontrada.", "actions.workflow.run_success": "A sequência de trabalho '%s' foi executada com sucesso.", "actions.workflow.from_ref": "Usar sequência de trabalho de", diff --git a/options/locale/locale_ru-RU.json b/options/locale/locale_ru-RU.json index 1eceaa73e5..8850172d0b 100644 --- a/options/locale/locale_ru-RU.json +++ b/options/locale/locale_ru-RU.json @@ -2934,6 +2934,9 @@ "actions.workflow.enable": "Включить рабочий поток", "actions.workflow.enable_success": "Рабочий поток «%s» успешно включен.", "actions.workflow.disabled": "Рабочий поток выключен.", + "actions.workflow.scope_owner": "Владелец", + "actions.workflow.scope_global": "Глобально", + "actions.workflow.required": "Требуется", "actions.need_approval_desc": "Требуется одобрение, чтобы запустить рабочие потоки для запроса на слияние.", "actions.variables": "Переменные", "actions.variables.management": "Управление переменными", diff --git a/options/locale/locale_si-LK.json b/options/locale/locale_si-LK.json index 3f79375823..42e60aed11 100644 --- a/options/locale/locale_si-LK.json +++ b/options/locale/locale_si-LK.json @@ -2162,5 +2162,7 @@ "actions.runners.version": "අනුවාදය", "actions.runs.commit": "කැප", "actions.runs.summary": "සාරාංශය", + "actions.workflow.scope_owner": "හිමිකරු", + "actions.workflow.required": "ඇවැසිය", "actions.general.token_permissions.mode.restricted": "සීමා" } diff --git a/options/locale/locale_sk-SK.json b/options/locale/locale_sk-SK.json index e4e548591b..263c4180bf 100644 --- a/options/locale/locale_sk-SK.json +++ b/options/locale/locale_sk-SK.json @@ -1149,5 +1149,7 @@ "actions.runners.task_list.repository": "Repozitár", "actions.runners.status.unspecified": "Neznámy", "actions.runners.version": "Verzia", - "actions.runs.branch": "Vetva" + "actions.runs.branch": "Vetva", + "actions.workflow.scope_owner": "Vlastník", + "actions.workflow.scope_global": "Globálne" } diff --git a/options/locale/locale_sv-SE.json b/options/locale/locale_sv-SE.json index 08520a9ee9..3da29e3dc7 100644 --- a/options/locale/locale_sv-SE.json +++ b/options/locale/locale_sv-SE.json @@ -1720,5 +1720,6 @@ "actions.runners.task_list.run": "Kör", "actions.runners.task_list.repository": "Utvecklingskatalog", "actions.runners.status.active": "Aktiv", - "actions.runs.summary": "Översikt" + "actions.runs.summary": "Översikt", + "actions.workflow.scope_owner": "Ägare" } diff --git a/options/locale/locale_tr-TR.json b/options/locale/locale_tr-TR.json index c758d25340..d1aeed537b 100644 --- a/options/locale/locale_tr-TR.json +++ b/options/locale/locale_tr-TR.json @@ -3665,6 +3665,9 @@ "actions.workflow.enable": "İş Akışını Etkinleştir", "actions.workflow.enable_success": "'%s' iş akışı başarıyla etkinleştirildi.", "actions.workflow.disabled": "İş akışı devre dışı.", + "actions.workflow.scope_owner": "Sahibi", + "actions.workflow.scope_global": "Genel", + "actions.workflow.required": "Gerekli", "actions.workflow.run": "İş Akışını Çalıştır", "actions.workflow.not_found": "'%s' iş akışı bulunamadı.", "actions.workflow.run_success": "'%s' iş akışı başarıyla çalıştırıldı.", diff --git a/options/locale/locale_uk-UA.json b/options/locale/locale_uk-UA.json index 0006b069d0..0371ea91fe 100644 --- a/options/locale/locale_uk-UA.json +++ b/options/locale/locale_uk-UA.json @@ -3115,6 +3115,8 @@ "actions.workflow.enable": "Увімкнути робочий процес", "actions.workflow.enable_success": "Робочий процес '%s' успішно ввімкнено.", "actions.workflow.disabled": "Робочий процес вимкнений.", + "actions.workflow.scope_owner": "Власник", + "actions.workflow.required": "Обов'язково", "actions.workflow.run": "Запустити робочий процес", "actions.workflow.not_found": "Робочий процес '%s' не знайдено.", "actions.workflow.run_success": "Робочий процес '%s' завершився успішно.", diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index 7c3d45f528..5a12426639 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -3832,6 +3832,9 @@ "actions.workflow.enable": "启用工作流", "actions.workflow.enable_success": "工作流「%s」已成功启用。", "actions.workflow.disabled": "工作流已禁用。", + "actions.workflow.scope_owner": "拥有者", + "actions.workflow.scope_global": "全局", + "actions.workflow.required": "必须", "actions.workflow.run": "运行工作流", "actions.workflow.not_found": "未找到工作流「%s」。", "actions.workflow.run_success": "工作流「%s」已成功运行。", diff --git a/options/locale/locale_zh-TW.json b/options/locale/locale_zh-TW.json index 00e40b74fe..ef25bbe25d 100644 --- a/options/locale/locale_zh-TW.json +++ b/options/locale/locale_zh-TW.json @@ -3233,6 +3233,9 @@ "actions.workflow.enable": "啟用工作流程", "actions.workflow.enable_success": "已成功啟用工作流程「%s」。", "actions.workflow.disabled": "工作流程已停用。", + "actions.workflow.scope_owner": "擁有者", + "actions.workflow.scope_global": "全域", + "actions.workflow.required": "必要", "actions.workflow.run": "執行工作流程", "actions.workflow.not_found": "找不到工作流程「%s」。", "actions.workflow.run_success": "工作流程「%s」執行成功。", -- 2.54.0 From 8ff71a5e523af959e8709af417b158fc1fb189e8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 29 Jun 2026 14:01:12 +0800 Subject: [PATCH 047/169] fix: flex divided list item shrink (#38255) don't make the items shrink since they are used as list items. fix #38220 --- web_src/css/shared/flex-list.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_src/css/shared/flex-list.css b/web_src/css/shared/flex-list.css index b224fe667c..99515c1342 100644 --- a/web_src/css/shared/flex-list.css +++ b/web_src/css/shared/flex-list.css @@ -20,10 +20,12 @@ .flex-divided-list > .item { padding: 10px 0; + flex-shrink: 0; } .flex-divided-list > .divider { margin: 0; + flex-shrink: 0; } .flex-divided-list > .item + .item { -- 2.54.0 From 762c674bc5cb5383ecfd7039e758671477e8a56b Mon Sep 17 00:00:00 2001 From: Giteabot Date: Sun, 28 Jun 2026 23:24:18 -0700 Subject: [PATCH 048/169] chore(deps): update python dependencies (#38256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [djlint](https://redirect.github.com/djlint/djLint) | `==1.39.2` → `==1.39.4` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/djlint/1.39.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/djlint/1.39.2/1.39.4?slim=true) | | [zizmor](https://docs.zizmor.sh) ([source](https://redirect.github.com/zizmorcore/zizmor)) | `==1.25.2` → `==1.26.1` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/zizmor/1.26.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/zizmor/1.25.2/1.26.1?slim=true) | --- ### Release Notes
djlint/djLint (djlint) ### [`v1.39.4`](https://redirect.github.com/djlint/djLint/blob/HEAD/CHANGELOG.md#1394---2026-06-24) [Compare Source](https://redirect.github.com/djlint/djLint/compare/v1.39.3...v1.39.4) ##### Fix - Fix crashes in mypyc-compiled wheels. ### [`v1.39.3`](https://redirect.github.com/djlint/djLint/blob/HEAD/CHANGELOG.md#1393---2026-06-23) [Compare Source](https://redirect.github.com/djlint/djLint/compare/v1.39.2...v1.39.3) ##### Fix - Use Click instead of tqdm for progress output, send progress to stderr, respect `--quiet`, and honor `NO_COLOR`. Remove direct `colorama` and `tqdm` dependencies now that Click handles CLI colors and progress. - Avoid false H025 reports after self-closing tags in Django templates. - Avoid false H025 reports for multiline Go template attributes. - Keep Django child-template reformatting idempotent when inline control blocks also appear inside HTML attributes. - Respect whitespace-control dashes when applying `blank_line_after_tag` and `blank_line_before_tag`.
zizmorcore/zizmor (zizmor) ### [`v1.26.1`](https://redirect.github.com/zizmorcore/zizmor/releases/tag/v1.26.1) [Compare Source](https://redirect.github.com/zizmorcore/zizmor/compare/v1.26.0...v1.26.1) This is a small corrective release for [1.26.0](https://docs.zizmor.sh/release-notes/#​1260). ### [`v1.26.0`](https://redirect.github.com/zizmorcore/zizmor/releases/tag/v1.26.0) [Compare Source](https://redirect.github.com/zizmorcore/zizmor/compare/v1.25.2...v1.26.0) #### New Features 🌈[🔗](https://docs.zizmor.sh/release-notes/#new-features) - New audit: [typosquat-uses](https://docs.zizmor.sh/audits/#typosquat-uses) detects uses: clauses that reference likely typoed actions ([#​1985](https://redirect.github.com/zizmorcore/zizmor/issues/1985)) Many thanks to [@​andrew](https://redirect.github.com/andrew) for proposing and implementing this improvement! - New audit: [unsound-ternary](https://docs.zizmor.sh/audits/#unsound-ternary) detects pseudo-ternary expressions that don't evaluate as expected ([#​2085](https://redirect.github.com/zizmorcore/zizmor/issues/2085)) Many thanks to [@​terror](https://redirect.github.com/terror) for proposing and implementing this improvement! - New audit: [adhoc-packages](https://docs.zizmor.sh/audits/#adhoc-packages) detects run: steps that install packages in an ad-hoc manner ([#​2061](https://redirect.github.com/zizmorcore/zizmor/issues/2061)) Many thanks to [@​connorshea](https://redirect.github.com/connorshea) for proposing and implementing this improvement! #### Enhancements 🌱[🔗](https://docs.zizmor.sh/release-notes/#enhancements) - The [cache-poisoning](https://docs.zizmor.sh/audits/#cache-poisoning) audit now detects additional cache disablement heuristics ([#​2053](https://redirect.github.com/zizmorcore/zizmor/issues/2053)) - The [known-vulnerable-actions](https://docs.zizmor.sh/audits/#known-vulnerable-actions) audit is now configurable. See [the configuration documentation](https://docs.zizmor.sh/audits/#known-vulnerable-actions-configuration) for details ([#​2084](https://redirect.github.com/zizmorcore/zizmor/issues/2084)) - The [excessive-permissions](https://docs.zizmor.sh/audits/#excessive-permissions) audit is now aware of the code-quality permission ([#​2088](https://redirect.github.com/zizmorcore/zizmor/issues/2088)) - The [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) audit's auto-fix now uses the fully qualified version tag (e.g. # v6.0.2) when fixing a major-version ref (e.g. [@​v6](https://redirect.github.com/v6)) ([#​2127](https://redirect.github.com/zizmorcore/zizmor/issues/2127)) #### Performance Improvements 🚄[🔗](https://docs.zizmor.sh/release-notes/#performance-improvements) - Most online audits are significantly faster, thanks to more precise retry handling ([#​2036](https://redirect.github.com/zizmorcore/zizmor/issues/2036)) Bug Fixes 🐛[🔗](https://docs.zizmor.sh/release-notes/#bug-fixes) - Fixed a bug where zizmor's LSP would not recognize dependabot.yaml files in its default configuration ([#​2026](https://redirect.github.com/zizmorcore/zizmor/issues/2026)) Many thanks to [@​fionn](https://redirect.github.com/fionn) for implementing this fix! - Fixed a bug where [ref-version-mismatch](https://docs.zizmor.sh/audits/#ref-version-mismatch) would fail to fully match some version comments ([#​2040](https://redirect.github.com/zizmorcore/zizmor/issues/2040)) - Fixed a bug where [dependabot-cooldown](https://docs.zizmor.sh/audits/#dependabot-cooldown) would fail to honor the user's configured days when performing autofixes ([#​2055](https://redirect.github.com/zizmorcore/zizmor/issues/2055)) - Steps and jobs gated by statically-false if: conditions (e.g. if: false, if: ${{ false }}) are now skipped during auditing, since they cannot execute ([#​2059](https://redirect.github.com/zizmorcore/zizmor/issues/2059), [#​2069](https://redirect.github.com/zizmorcore/zizmor/issues/2069)) - Fixed a bug where [ref-version-mismatch](https://docs.zizmor.sh/audits/#ref-version-mismatch) would fail to identify some valid version comments ([#​2073](https://redirect.github.com/zizmorcore/zizmor/issues/2073)) - Fixed a bug where [unpinned-images](https://docs.zizmor.sh/audits/#unpinned-images) would incorrectly flag empty matrix expansions as unpinned container image references ([#​2102](https://redirect.github.com/zizmorcore/zizmor/issues/2102)) - Fixed a bug where [unpinned-images](https://docs.zizmor.sh/audits/#unpinned-images) would incorrectly flag some matrix expansions as unpinned ([#​2098](https://redirect.github.com/zizmorcore/zizmor/issues/2098)) - The SARIF (--format=sarif) and GitHub Annotations (--format=github) output formats now provide more correct/useful paths, particularly when the user provides a relative path as input to zizmor rather than zizmor . ([#​1748](https://redirect.github.com/zizmorcore/zizmor/issues/1748), [#​2095](https://redirect.github.com/zizmorcore/zizmor/issues/2095)) #### Changes ⚠️[🔗](https://docs.zizmor.sh/release-notes/#changes) - The [impostor-commit](https://docs.zizmor.sh/audits/#impostor-commit) audit no longer suggests auto-fixes, to avoid incorrectly minimizing the amount of manual remediation work needed ([#​2054](https://redirect.github.com/zizmorcore/zizmor/issues/2054)) - The JSON and SARIF outputs no longer contain a misleading prefix key ([#​2095](https://redirect.github.com/zizmorcore/zizmor/issues/2095))
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- pyproject.toml | 4 +- uv.lock | 156 ++++++++++++++++++++++--------------------------- 2 files changed, 73 insertions(+), 87 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c5ec3f2d5..061d17236a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,9 +5,9 @@ requires-python = ">=3.10" [dependency-groups] dev = [ - "djlint==1.39.2", + "djlint==1.39.4", "yamllint==1.38.0", - "zizmor==1.25.2", + "zizmor==1.26.1", ] [tool.djlint] diff --git a/uv.lock b/uv.lock index 302ba1c873..1dc994d287 100644 --- a/uv.lock +++ b/uv.lock @@ -39,11 +39,10 @@ wheels = [ [[package]] name = "djlint" -version = "1.39.2" +version = "1.39.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama" }, { name = "cssbeautifier" }, { name = "jsbeautifier" }, { name = "json5" }, @@ -51,66 +50,65 @@ dependencies = [ { name = "pyyaml" }, { name = "regex" }, { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tqdm" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/d8/119f35832801129dc6a4bdf82c163bb80d0095891eea9fc3543eaf8c9792/djlint-1.39.2.tar.gz", hash = "sha256:dd27ef03bd26d1e0a167da26ec2a6fcb511dddd032b2553abf71a5c31eaa8b34", size = 56083, upload-time = "2026-06-11T14:03:01.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/6b/db4829f972157ef45a04563f3360d1bd6425c040c4cb488646755bbc1387/djlint-1.39.4.tar.gz", hash = "sha256:e9ea9b4558785a1193268ad06ac5766647613b3733c00fda5c20a479e031bdbd", size = 55914, upload-time = "2026-06-24T00:26:59.124Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/a5/ede7c8e1f0a49fc5baea9c358617f11ce5fcb34f53594fcec447fd440356/djlint-1.39.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d21abcd252e3f28f13f5090f2c214f49e91a53b71c0e949460fb8e3065634c6", size = 539969, upload-time = "2026-06-11T14:02:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/52/87/8d81b169062626d8b1782404816d22bfdc2454e9aabf61be4be79acd9ea6/djlint-1.39.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f3d1a797c348115ee3fbea39751223721247501f74728cdb6be6776f2e663cd5", size = 514397, upload-time = "2026-06-11T14:02:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/0f72462cad08d40803a5132a8a95b42f7bfa692b806d52beaf4206ccdb13/djlint-1.39.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69830fb1d0e0f5ccd09d248bb9bc4adf6f9937ea76037e68cc3dda7ec33c4701", size = 544179, upload-time = "2026-06-11T14:02:04.932Z" }, - { url = "https://files.pythonhosted.org/packages/37/df/f433649196eb123192cbb619c3cc5b58b09fd629f5442d589c16b2adaca3/djlint-1.39.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17979198e7084b5e24bc22b89be89d12977b37304d6a24e6d382f6699d96ecc2", size = 564445, upload-time = "2026-06-11T14:02:07.572Z" }, - { url = "https://files.pythonhosted.org/packages/01/b6/0f105e9d8128574edfd2db201caf89ab0a3496c706a4b6f8a8417ffc4210/djlint-1.39.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ea0863fd53bd21e75ea0d04711898855bd579d6bd9783dae797deea82b428500", size = 550594, upload-time = "2026-06-11T14:03:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d2/728cd56ae7525f3ff2dc93aa996d01edce164c7b6709e54cdd7c3a4a6c61/djlint-1.39.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:820ce7467d6200a7435155bbd0a080316d7c16a5c4730712c2e3b8c905657430", size = 574855, upload-time = "2026-06-11T14:01:52.72Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/3f03d55710398cfc9dc4ee38537b8d709d75c218a1312e77dce11919e3b1/djlint-1.39.2-cp310-cp310-win32.whl", hash = "sha256:3343ae644cf3e16aaf4b3027833255c93bde9798521d5f8481a7099adce2dba5", size = 421933, upload-time = "2026-06-11T14:02:06.242Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c7/982a098e6eecfe530206d4c40356e71f6b69c7610d38341369f54eb25565/djlint-1.39.2-cp310-cp310-win_amd64.whl", hash = "sha256:0877d6a6db2f88dc8ae16752605f8852d8503e5939f4dc48ba6520f6ba2dfade", size = 470431, upload-time = "2026-06-11T14:02:51.036Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/a4e953506bbdf9fb344aa9f0cd1eaff56cce41bbc6916ef4005b1748536c/djlint-1.39.2-cp310-cp310-win_arm64.whl", hash = "sha256:9ab8822909d084148506450b49a9abfd13e62423cb7b8d89cd9153ada53ac1e0", size = 405286, upload-time = "2026-06-11T14:02:28.513Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3c/4bac0dfe37cab284b00fb4981d3c24e7338c4750ab72a1a1cabee3641097/djlint-1.39.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a30c4b46914ec15e8260b1f935f2a9d786111852fb069e3fb9c9c2f46fcae74", size = 530075, upload-time = "2026-06-11T14:01:54.174Z" }, - { url = "https://files.pythonhosted.org/packages/96/be/f4ead398868229985a0dd38e513b5c2c37d565136ff2615ee842ac3ef80e/djlint-1.39.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7483e8b46eb6c92f2275024452d762b2bff09278238d0bbc6ad181f969b9a4dc", size = 505501, upload-time = "2026-06-11T14:01:57.088Z" }, - { url = "https://files.pythonhosted.org/packages/d7/00/c791f9a111cd2aa904549558621f7e00ce19e7b309c11f836ae1e91a673f/djlint-1.39.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81314434617ac888d1f53f4cf8ffb718bad79f454bc3a6af2874d347e0ab8a8f", size = 534141, upload-time = "2026-06-11T14:02:52.482Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/8dc966b6b1993f64dbd162106d158459e52cc5aa18edb5a29fffe646e5eb/djlint-1.39.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:473803d2a6bff1bc61c0bfac3f3c36ebe58348342a3c234275969c57eeba8814", size = 552778, upload-time = "2026-06-11T14:02:47.051Z" }, - { url = "https://files.pythonhosted.org/packages/fc/38/64cbb0467cd38d129a2f245a73be84b297133e92f2f049a33f7f68623321/djlint-1.39.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33aa6da970c7ed3e62573e00d1aaf7f3f234361b571e55d0a916d300a69bffc6", size = 541041, upload-time = "2026-06-11T14:02:34.716Z" }, - { url = "https://files.pythonhosted.org/packages/23/97/735cfd6dc20056dcbed1b4f8709974493098a9bd54ffae26d3cfe376a8e7/djlint-1.39.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c261e83fca81ed7dfc992f5bae9013e33cb5a6df5a71f96476962fd13c4e734c", size = 564589, upload-time = "2026-06-11T14:02:14.117Z" }, - { url = "https://files.pythonhosted.org/packages/be/e0/6c023418f8d70beace93debb69efcbca41475c083d381d08e0022e403867/djlint-1.39.2-cp311-cp311-win32.whl", hash = "sha256:73e5277c5c553935448d209643a7adeb9fe185550ec9007470c132ccaa5ff339", size = 421581, upload-time = "2026-06-11T14:02:30.021Z" }, - { url = "https://files.pythonhosted.org/packages/8e/cd/3d973b5b86cbd3058116015c0f858fee439a25fc76a4b05646328155ee25/djlint-1.39.2-cp311-cp311-win_amd64.whl", hash = "sha256:117af0b57f71c8531c3fbb95162c20e74ace0d957f65ac93539ff42e84b778c8", size = 470411, upload-time = "2026-06-11T14:02:58.765Z" }, - { url = "https://files.pythonhosted.org/packages/df/11/40cdefcae1fc49e77c285bcee31b83710875fbb994c4c789155341851a89/djlint-1.39.2-cp311-cp311-win_arm64.whl", hash = "sha256:af01dd3555f65dcb3d735a7254ead88ddb50e6dad76a12af22c85ff3ea66a062", size = 403952, upload-time = "2026-06-11T14:02:48.331Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/8d0d8134e59f2e94144ffed957bf8de1e57b3be2545605172306a0dcf8ff/djlint-1.39.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4f26ec23a39c1ef45f2357e95395aa566010d513282ed34a06f0f54690888b0", size = 540630, upload-time = "2026-06-11T14:02:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d8/862798ab0bac48974dae8f54ad3a0024f8797c3c59c06cf92f8a474c1c17/djlint-1.39.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f907116317fa429d9e29aac529001fdeec48a862131154e5792ebcd9d5415012", size = 511270, upload-time = "2026-06-11T14:02:16.559Z" }, - { url = "https://files.pythonhosted.org/packages/8e/c5/ee8cacf7953843ff0605ed205ab2d0289411a118be3453c8686977c763b3/djlint-1.39.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eacde1123ace11f43029e565850861e73f4d7f44aaeb07c2af1ce40dc242d84", size = 537483, upload-time = "2026-06-11T14:02:24.475Z" }, - { url = "https://files.pythonhosted.org/packages/ac/9b/15d9632db5b187594acb50fb199b26bb0b512e66535ced8345894c0e3d01/djlint-1.39.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525b0cf09ff13b84a17c704fb7e34144d94fd49fa0a29e23e70394487b3f382b", size = 560049, upload-time = "2026-06-11T14:01:58.369Z" }, - { url = "https://files.pythonhosted.org/packages/0f/eb/b3d07b4e1df39194c4342d52c7224ed84267458eb67989c6405079251968/djlint-1.39.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a54dac1106ce607b134faa59acca01bfb3f0b36d6ccc562558d90fb0ddce55f2", size = 543416, upload-time = "2026-06-11T14:02:41.653Z" }, - { url = "https://files.pythonhosted.org/packages/58/d2/d8697eb492efa1c83f546efe151c559cf392a1fba145b3fab9ab870a9606/djlint-1.39.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6767bc0b0a04a022308d94be004e1889801cdac1f858563f6c397094580d78e1", size = 570385, upload-time = "2026-06-11T14:02:08.898Z" }, - { url = "https://files.pythonhosted.org/packages/1a/fc/ba2531e2469ed4d0bb5cfda11e46d48a709999a4de61d3d0d5bad4f6eaef/djlint-1.39.2-cp312-cp312-win32.whl", hash = "sha256:059dbea42758394d6fae582e3b158e098ebdc9cb4eb68634bcb3334f95c55799", size = 423531, upload-time = "2026-06-11T14:02:19.123Z" }, - { url = "https://files.pythonhosted.org/packages/d4/70/2471ae8f78bb9639ccc2cd96ca17996a50d95806638a221415d8235e518d/djlint-1.39.2-cp312-cp312-win_amd64.whl", hash = "sha256:38129305d27f5e635404b131f21a2a4d8ffcc1f58446c0f3b4a25b38140ec710", size = 472749, upload-time = "2026-06-11T14:02:31.454Z" }, - { url = "https://files.pythonhosted.org/packages/d8/16/f03cd22c81652445d4f61c7658ca25e4fe048fd188ad58198c1cca36d9d1/djlint-1.39.2-cp312-cp312-win_arm64.whl", hash = "sha256:2382bfc52d2bf9ca9565696e4ee89ce811783090f36b4b03ce30888672da9506", size = 405100, upload-time = "2026-06-11T14:02:03.425Z" }, - { url = "https://files.pythonhosted.org/packages/05/9b/d1a630b2495f8b6a60555012dd57c661a9fe683c1f8483287ecddae749dc/djlint-1.39.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c243a1f18211cb9a4fec5b95f0b43c61941414f71fdb3d77dcc9798f9644b623", size = 538489, upload-time = "2026-06-11T14:02:54.322Z" }, - { url = "https://files.pythonhosted.org/packages/87/39/7e15d99f40cf6ec1d624df48ab465725914cf6c789fdf6f6dc28db264666/djlint-1.39.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:74d65499c5b29e5269eb6a4bff9dbf426c22cba54f4810d30fbfbcb482c9035f", size = 510153, upload-time = "2026-06-11T14:02:10.287Z" }, - { url = "https://files.pythonhosted.org/packages/db/a6/9ab6b79728a3a7f8235eac0f0d21e01e7457adfe3f368a3a1132eaa52897/djlint-1.39.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25b955e23c09bdfdb04de6b40b848cf93b39952178aa37d940af921a9715da4b", size = 535815, upload-time = "2026-06-11T14:02:01.034Z" }, - { url = "https://files.pythonhosted.org/packages/9e/9b/3eceecb4bead34e9087102078b58c084fa53f995827f03ea7cbf42fd5c48/djlint-1.39.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a2c0191f93181557e8dcbca2a6766ab972f769bb9c32d488cefa1b5195a74d", size = 557939, upload-time = "2026-06-11T14:02:43.072Z" }, - { url = "https://files.pythonhosted.org/packages/c9/cf/95bdb7d40699ae60d4dfda0ed7278e0a09f224c505bf0cd396f1e6ab4c23/djlint-1.39.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e0aa5e3087dd346ae3197781fa425aebccce6beed593f4dd17cbb00179ef8444", size = 541687, upload-time = "2026-06-11T14:02:11.683Z" }, - { url = "https://files.pythonhosted.org/packages/73/a7/6b8ba672e1c7c8216306ecfacd4053b645ef583f573c69a3f2087c654922/djlint-1.39.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30094c7533b0e919f4674baeaf0acd061b1eb6c8ffbf4d6d3dc856c686747411", size = 568429, upload-time = "2026-06-11T14:01:55.72Z" }, - { url = "https://files.pythonhosted.org/packages/d2/50/03b189c613fb3f0286e8ddfbc3a31856c6a2d3c7a01eb89cf8441ca25f21/djlint-1.39.2-cp313-cp313-win32.whl", hash = "sha256:3eb8942afbf2e5d0ffc208d88db33dc38abb585ce03fdd17d9a0f03e4b7e761a", size = 423325, upload-time = "2026-06-11T14:01:48.24Z" }, - { url = "https://files.pythonhosted.org/packages/5e/00/a0a34a80b732ae227b395bdd2718836c696336524bf6f97210cc6ff871e6/djlint-1.39.2-cp313-cp313-win_amd64.whl", hash = "sha256:9040e8ff1e7e141cd67402110f0edb67835955003c7a076c97398dcee5f814d1", size = 472106, upload-time = "2026-06-11T14:02:12.8Z" }, - { url = "https://files.pythonhosted.org/packages/bf/65/c28711b4e15c932e2be15eee9b0d37465394bab65acb0f2f5f51ee42decd/djlint-1.39.2-cp313-cp313-win_arm64.whl", hash = "sha256:a54edd4326adc48577d244425e8d3c175ec3cc6abd3732418efeccc0b92b9d1a", size = 404747, upload-time = "2026-06-11T14:02:33.238Z" }, - { url = "https://files.pythonhosted.org/packages/46/3e/16cfba85037a7dcd2333263594c8c94f6443b0f3726f55cd2426fc7fac5b/djlint-1.39.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e2fefc27180dad754e76c9ece6f5dbe99a808c7a0dd4ff4213dd39d82f1878cd", size = 537519, upload-time = "2026-06-11T14:03:03.663Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9b/dd5dffc2eadb46a3167c13151b6f7dbcef39996da56794d45f43b1826a61/djlint-1.39.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c62372a9d037d612f9b957829c0dbdef96e017dc2d9cf4970e094de6fee4ee55", size = 509380, upload-time = "2026-06-11T14:02:35.978Z" }, - { url = "https://files.pythonhosted.org/packages/0a/38/2e89eea336e5990999112fddd7b238407a3d9183a06acb38e00bd79d1dfd/djlint-1.39.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d27a88e84fd9e75cc49247b9d200f51c7bd037364efd58256d3b0e70c10775db", size = 538317, upload-time = "2026-06-11T14:02:49.693Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fe/2546eecaaed67b2f92c2bd571b88b84692cd9b6883bdbfe6370391d86219/djlint-1.39.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b77ce23aabab2426f5130948c0d605bd7414550bbca8a3892b7d255644c2da", size = 557757, upload-time = "2026-06-11T14:02:17.769Z" }, - { url = "https://files.pythonhosted.org/packages/3e/fa/36bc8176a4a1702ed8446b606ddecc9502d398a55c3803e69469ba8304f9/djlint-1.39.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4547b35fd6f5030ffe4555e66916f8ba8107e0e9c2c6a4e721fb980ba79e3fce", size = 544474, upload-time = "2026-06-11T14:02:45.64Z" }, - { url = "https://files.pythonhosted.org/packages/62/69/5c0733745e5a2a275f3dad4977745890a777c5a74141b6c0e13a998992c2/djlint-1.39.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1257a2db80184ba70a195d8fe6026b54dab4dbc5156a90a61adbc916e500a6b1", size = 567721, upload-time = "2026-06-11T14:01:49.913Z" }, - { url = "https://files.pythonhosted.org/packages/5c/66/7c2a820f2d9948ff0b423d6395664a13979ff702edeb6efd51bf404b5c66/djlint-1.39.2-cp314-cp314-win32.whl", hash = "sha256:766d0266fbb0064c7579d66ed06487d9198d99880c5bbab03f24386edfa766f5", size = 429369, upload-time = "2026-06-11T14:02:20.447Z" }, - { url = "https://files.pythonhosted.org/packages/2e/32/006d9e22e2184c87cd556a7333d12c5fba2ec8b78bc6333621c4b067b81d/djlint-1.39.2-cp314-cp314-win_amd64.whl", hash = "sha256:345608f6eefe627d39f2491a673bc80688a86af3977113fc91b01f4b827bec2a", size = 481219, upload-time = "2026-06-11T14:01:59.669Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/5488c01bc730b86af531b72066c913d0ebe234ddb3101f9898eb044ffc44/djlint-1.39.2-cp314-cp314-win_arm64.whl", hash = "sha256:ed0d24f5af98f7ef8c50061b64ab4cdd61abfd133df9f99810d3efc82e25a3c1", size = 412865, upload-time = "2026-06-11T14:02:56.015Z" }, - { url = "https://files.pythonhosted.org/packages/de/89/66b5260dc2f677264e0580565eb1a7b30bd842bebaba7940e11dc9a348b0/djlint-1.39.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:16cc5df9891a80090600f32111448e175148670cb7ca7789c04c4cdf9f9334d8", size = 584161, upload-time = "2026-06-11T14:02:57.393Z" }, - { url = "https://files.pythonhosted.org/packages/7a/38/e1a31e0fdd2cf090f4f862f282fe1fb0b363c0e248a105ed7f3aa16a5508/djlint-1.39.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20df1b4a79bc05329c207110478ad26513deaef3a7409b4a3cab55216172749", size = 556646, upload-time = "2026-06-11T14:02:37.574Z" }, - { url = "https://files.pythonhosted.org/packages/57/58/95733fc6f42a0bf31aa1186e0323fe224327bb840666d083261913cac90b/djlint-1.39.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c2f34d9ef9aa73536bf485648ce4fa381bb3d8d843fb97ec6725d5b3457b84d", size = 573320, upload-time = "2026-06-11T14:02:21.965Z" }, - { url = "https://files.pythonhosted.org/packages/7b/7c/9a03f9e859bc3dd05992dd8bf40298bcc18693dbbcba25a3eec58dd25fa5/djlint-1.39.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:851a9c78674120c99fe6842bcd3377bce698583823ee7861ae4992e84fff3e9b", size = 593052, upload-time = "2026-06-11T14:02:39.044Z" }, - { url = "https://files.pythonhosted.org/packages/b7/73/d0fa38536f1652584ddb6db9487bf8ef95322b92ce88ff4fe16719715526/djlint-1.39.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c9df4a39d57476bcd2a02ae8d873796b92517517d4512f497b40be3e7398e84", size = 579381, upload-time = "2026-06-11T14:02:40.393Z" }, - { url = "https://files.pythonhosted.org/packages/ee/35/689726a28c99af26b925f97f66b7046eee3b3881ca3161a04695e8e8112b/djlint-1.39.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e0356e3a66357d72ca5d69f2fb78d47fed26efb31d104cba44f5e6fbf503a4e2", size = 602713, upload-time = "2026-06-11T14:02:44.356Z" }, - { url = "https://files.pythonhosted.org/packages/91/03/5e59d32de6d3b4fc6113450e6198755d169b23fc7d8daaa5bbcc2ebc5c57/djlint-1.39.2-cp314-cp314t-win32.whl", hash = "sha256:ad5213ce1bbab6c18e1461498f219ca66382c40766d499e233ff2174a23026c6", size = 475348, upload-time = "2026-06-11T14:01:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/bd/58/bd0ca13d2cfb31b666efc3b10af5dd37a72269b0282ec6490993e9655704/djlint-1.39.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d5711a2945844446fa19c35f151e077c2119411e49db30a3f97f32b9325b89bf", size = 535320, upload-time = "2026-06-11T14:03:00.083Z" }, - { url = "https://files.pythonhosted.org/packages/a4/c5/b2b8d8918f770a0dac8b2ad7f4a5caa3bc63c60f4cd178418b72f19f0a5a/djlint-1.39.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f894464e6946e761003844ac5bfe4f51f362258a51d4e65a9641a4bd74da4ea2", size = 429535, upload-time = "2026-06-11T14:02:02.145Z" }, - { url = "https://files.pythonhosted.org/packages/62/c1/ffb80fed94a3bf746d37134b504b794de893cb71e229ea2e1c91f14d1864/djlint-1.39.2-py3-none-any.whl", hash = "sha256:7c49bfda97816afd36273f12d67aa432ed44cd9f62d5c31f9ea9c316ebe808b2", size = 62347, upload-time = "2026-06-11T14:02:23.136Z" }, + { url = "https://files.pythonhosted.org/packages/32/63/e9a4e01cf1cbad8b3d138ca51cd04a8d625a147f1bbe63384938e40b8c46/djlint-1.39.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd48f9c3e5b5390667f37e7315f250dbb1d2a445d2e2e75c063ceeef9dadc89b", size = 530221, upload-time = "2026-06-24T00:25:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2d/315fa58c63820ac242d4f1dbb491734d2ec1292d052f16d2a03946f4ab69/djlint-1.39.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6177dbd2b046547e0486d4eddf523041cb53be50d974bb4a0221ee76feb17ab4", size = 508010, upload-time = "2026-06-24T00:25:57.211Z" }, + { url = "https://files.pythonhosted.org/packages/e0/66/c7fa0b6040cda17740db61faf83831d5486cf2499652d88e47cf1303fe99/djlint-1.39.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c13ae91c61936ab310e1c6b6e865a116609274ceac69164e2efcec1214748dc", size = 541485, upload-time = "2026-06-24T00:25:58.318Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6a/1df34be16857beb235634312213e5418608d625c4b4603b15714a36d670f/djlint-1.39.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d848a03d57b220e6908c4cea000b9ada1f02b13b24689888c9d3db81249e89f5", size = 560160, upload-time = "2026-06-24T00:25:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/40/c4/48d395aab18d6d6d7601b0a9b393ba46b10e6ec965df83f63cfe3c7c9a8c/djlint-1.39.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1c72550f720bd8e57658f0aaa840a17e14bd036b6eca765807b4f2aeac37689", size = 547477, upload-time = "2026-06-24T00:26:00.805Z" }, + { url = "https://files.pythonhosted.org/packages/56/8b/fb2651dab6b72f6e40db8c59e05c5e2ff7eceb73c6cbf12fdb276926b29a/djlint-1.39.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0875ef6e7a90e06ad5f1b0f5afb819c2cd05669bb3ac7dc19260bdbf569096ed", size = 570514, upload-time = "2026-06-24T00:26:01.999Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2a/df3bdaac0597a9970813eef8051946c7f8b7c1f49371a564b1f55ea7e7d7/djlint-1.39.4-cp310-cp310-win32.whl", hash = "sha256:edb9011558e9d08a3550de76775509e95db10f68890fccfcc4267ee875e7a352", size = 407042, upload-time = "2026-06-24T00:26:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/2a/33/7c1a899fa389441797fa41193dbe8a3e70ae2d53b37f91666ea823c77962/djlint-1.39.4-cp310-cp310-win_amd64.whl", hash = "sha256:5bface691be013f42758298ff1d1bc69f8a9e375ca69f414845edb595259d23d", size = 451797, upload-time = "2026-06-24T00:26:04.091Z" }, + { url = "https://files.pythonhosted.org/packages/05/84/0d5f7eab655753a8890cc4e0c8c666461a5b9897215e215812a288520c64/djlint-1.39.4-cp310-cp310-win_arm64.whl", hash = "sha256:d00a468834afa12618bbef3d57fda531e1dfa57584c2180236aa93c0a3b71af0", size = 401425, upload-time = "2026-06-24T00:26:05.221Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bb/fbe6481004947e317bd52f68f676ac100f5ad1517f1e0dac4cd09d78921c/djlint-1.39.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d649492a27342ad33dcf460c7ad6cc9d87fcdc262df5cd17b1608e7def947bb", size = 518604, upload-time = "2026-06-24T00:26:06.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/59/f6e4495ec46849728586771d06ce5d40d9f485ec9be2d78a7ab254a02eb2/djlint-1.39.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d3dc4f5839dc797d64e709f68d06e6bdd6b35ba36d6977ef8ca939be732c9fe", size = 495872, upload-time = "2026-06-24T00:26:07.352Z" }, + { url = "https://files.pythonhosted.org/packages/53/ca/abb05c22c1229cdc1933b065b83297c1295224ec96e0a65b13a17992d1ee/djlint-1.39.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:edd037e491fd0397e7bb483d8e2e5767b6e9c2943c73b5bee0810d32b48dabdc", size = 529659, upload-time = "2026-06-24T00:26:08.489Z" }, + { url = "https://files.pythonhosted.org/packages/8e/68/52416d9dc9cf4689046fff4f04115c2bcfc101aebad4f1590ca74695432a/djlint-1.39.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e13804a1b1f100e3e9f86a4c252de92cc893dc291cb47522f83f007e71ba2cb", size = 546764, upload-time = "2026-06-24T00:26:09.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/ad/272c0dea8731d02b600d10ae98abed7dded9db6b1f420f4114e75a0a4b78/djlint-1.39.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:de6d0d4c5b847c917cb7abbcc97462eec5259ec97900c339b0290bba9603f160", size = 536341, upload-time = "2026-06-24T00:26:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f5/f6f0a444289c288a1004d6d1deca3205d44f778b2dda30e5d7bb6ab72933/djlint-1.39.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c2e40a8fc55d4df93468fb3357ae7598c17b0e2090fb7fac6242db267ad9973e", size = 556137, upload-time = "2026-06-24T00:26:11.926Z" }, + { url = "https://files.pythonhosted.org/packages/12/85/0f755baf0a2fde1527b827ae71daa44a2ef66c045394f838d5c48329c342/djlint-1.39.4-cp311-cp311-win32.whl", hash = "sha256:69f415ed583e167159ae0bd5ffdd49a12c3e60cb062c91efee3980c39f7f5716", size = 405984, upload-time = "2026-06-24T00:26:12.889Z" }, + { url = "https://files.pythonhosted.org/packages/ff/dd/a1fa28f3d1cc23cb521bbdff9e008bcc31dd7122fc5fdf75b67abffc991b/djlint-1.39.4-cp311-cp311-win_amd64.whl", hash = "sha256:1046d1e1899c651683b3dd54a9ca9693ab48022b0ae830b9fd0484c89b76210b", size = 451850, upload-time = "2026-06-24T00:26:13.961Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/330d77b9891f29077ebb3746268f6ff6b0dbce78ebaa7a2feaa76909a7ce/djlint-1.39.4-cp311-cp311-win_arm64.whl", hash = "sha256:5c6de1203c555db6ca842399481e56e0fe426633b7c0c0198f0291bb061e2e66", size = 399866, upload-time = "2026-06-24T00:26:15.242Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/347da8de52d383294c00ecffee17a8c903e6fba90e26a2736204aa88e03c/djlint-1.39.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2465f7d7de298a3c8cba54c08861fa8ee7f00ef0b4644b682a7eaed447b2e9e2", size = 530860, upload-time = "2026-06-24T00:26:16.187Z" }, + { url = "https://files.pythonhosted.org/packages/cf/31/a585bc5e339bd664f121a58bb2cafee365545543dba26e6a0e026e68a520/djlint-1.39.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:455fe7aacdf56f7e5f9e3d9957159e5808172534f1630a98a690aa60b5ca309f", size = 503640, upload-time = "2026-06-24T00:26:17.249Z" }, + { url = "https://files.pythonhosted.org/packages/de/c8/7c0c10155aca75e4ad956b7b59a38a5e3768b63d318204f2e4428ac19707/djlint-1.39.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b3cb2fc53ea9ab2a674c02063e60d60bc8ca9e56898d9f28ee5e77217bcfb3", size = 533287, upload-time = "2026-06-24T00:26:18.314Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/615cf1ab14e85e5162d908d65e21984e3e3707653d554031645e5db4fade/djlint-1.39.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e8c07b37661601d4c87a13d3627792f83e082ac96c89d6652128d191a32c8c5", size = 554201, upload-time = "2026-06-24T00:26:19.503Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cd/6f2ab08c9016fb5f78ac1d85d830dfaa3a30698b571a6a258534f94b3f01/djlint-1.39.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07983ccc7f11a5bbf1e91bb55330d3d0034ab73f84ace164d0e8d473ef2dc4e7", size = 539586, upload-time = "2026-06-24T00:26:20.58Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e1/d08f7a600939ed3fcf69f55656f478840de746d8ad9e24bdb4364c173aef/djlint-1.39.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3a9aa10c7d2910945e12f00bc6a1cacd458d4454c00e3ca33df6795c874575ff", size = 564939, upload-time = "2026-06-24T00:26:21.923Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/1e2be01fee523a3d0f7fd28db60c84a81ec72726a1af9a9de922c14c981a/djlint-1.39.4-cp312-cp312-win32.whl", hash = "sha256:e4098ea7ee3c8feccc8cdcfb172b01888251ce669461275429d55805d7f0ec40", size = 409458, upload-time = "2026-06-24T00:26:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6f/a29825bb2509a8d6189e09e25b9c1112a7ee8552126c05cb0da2d6802b86/djlint-1.39.4-cp312-cp312-win_amd64.whl", hash = "sha256:917681d3997f8828a2ed8d15337fb5500488515aa3f6dee401758255f831be0e", size = 454230, upload-time = "2026-06-24T00:26:24.157Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/6fdf42810a907bc45d14e25a02265670791e72ffe4771e7a4118b0d9363c/djlint-1.39.4-cp312-cp312-win_arm64.whl", hash = "sha256:c52fef4fac5337ceabc36f509f42c3e079e58f51676afc63d5b712a3f47bb45a", size = 401261, upload-time = "2026-06-24T00:26:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/d7/81/5b311443e45f0aac4d44832ad32732b01bcd03e256a5101b28ce94b66700/djlint-1.39.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b89adc02971a22826688b6b06f7c6eb6a7c716b7574cdc4e40242ae3fdd043be", size = 528675, upload-time = "2026-06-24T00:26:26.23Z" }, + { url = "https://files.pythonhosted.org/packages/58/e8/1dedb80782a1dd9fbcb5c9c771ec76e94b0f1ff63f0157efac41993b7bfd/djlint-1.39.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8c085199d27c396cf28d56e9d02c9384d094c4e62a088ad74a8480ddf2af3eb", size = 502312, upload-time = "2026-06-24T00:26:27.489Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/dde76a8f11b19a4050e61598136501f04f2a1a4f0be473c9c2d80b3b86fa/djlint-1.39.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c78ffe71b1c483f22e759c80b81c0f1bd1ffd426d8057bfaf770b1a203bab12", size = 531544, upload-time = "2026-06-24T00:26:28.961Z" }, + { url = "https://files.pythonhosted.org/packages/4a/5e/295ed4e88a5b82fcb89908ba91c601b39291c23d6bad402d8494a8b456fd/djlint-1.39.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04b9d61084e053e84d2bcdefe597343b0c57bbf1973b8c1db557582c005424f2", size = 552093, upload-time = "2026-06-24T00:26:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/00/0f/c149305d6545eb413ba4a659f30fb18ae63185d3da5ce5419a849c9af6a6/djlint-1.39.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73deb7bec6e51bbd9098dcdc50dbf352633d923eeaf45497815c319041687360", size = 537933, upload-time = "2026-06-24T00:26:31.49Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/6b41ad9dc3eb6f96420b5e3fb1fc7290da70d9cca80dae0dc21967ce9fd4/djlint-1.39.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dba8270ee08d30e4e923a8a73deeb49da807aae158be698cc1ad7cdcfdd52a1a", size = 562639, upload-time = "2026-06-24T00:26:32.663Z" }, + { url = "https://files.pythonhosted.org/packages/4e/4b/2aefe87763920aee12fccea06e9fec9261dfd47158e312126fd12e49f5f0/djlint-1.39.4-cp313-cp313-win32.whl", hash = "sha256:e15fd7a42fad7fdb8147ce0585f492f91ee26e0e9e06b88d22ddf1ae43e091a8", size = 409158, upload-time = "2026-06-24T00:26:33.966Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3f/cd4d2367082832b965ee06d5e51fe1371cae5a8d8f5cae7b0b4cc149dd03/djlint-1.39.4-cp313-cp313-win_amd64.whl", hash = "sha256:bcf7e2d911f395ea04fcfffd28088b10d0e4bfbbdad13ee6bfd5f38703f7f3e7", size = 453774, upload-time = "2026-06-24T00:26:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/29/23/65c1c03f3a474170c69a07c5c80a90ffd519732de3f85f8bd892c9463572/djlint-1.39.4-cp313-cp313-win_arm64.whl", hash = "sha256:c455bee480ba77ee2da1df3182eb879233c94a1e8481f0915e6f2aec2404207d", size = 400862, upload-time = "2026-06-24T00:26:36.337Z" }, + { url = "https://files.pythonhosted.org/packages/e2/62/b4a47ad08c2489ee251e3103dee4bd68b532237d369c2131f0636ef4f454/djlint-1.39.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:dd8248b29c7ec9d4111de80bc6f3edb946360833e8fce9eb7cc1bcd1c5b8108c", size = 527418, upload-time = "2026-06-24T00:26:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/8a/75/4e0eb4d35dff8c0bbd1ffc881e9c97d09350066018bb1a1c21ff5acd5e8c/djlint-1.39.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5107590b0f7ebe39e6853d2a83af55fb2efed340ef16abc56e7da43c35ab41d3", size = 501291, upload-time = "2026-06-24T00:26:38.609Z" }, + { url = "https://files.pythonhosted.org/packages/e4/10/5a1f9eded107f27183ab78a8e059bf829453cf9ffd649eb6c47d41dbd87b/djlint-1.39.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f9b2c58be13a34ddfe6ce69055e959df749aaf528a0cf3e2cb1d960fef55c4", size = 534510, upload-time = "2026-06-24T00:26:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bc/1d5b0dfb151e9fd36c464b11501179a5454327382c0ec25ce86b968833b2/djlint-1.39.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0259312cf4a991ba1c6627bf25cf9621a66945ae7d15654a0e1bbaae7a9ab27b", size = 551580, upload-time = "2026-06-24T00:26:41.147Z" }, + { url = "https://files.pythonhosted.org/packages/33/46/2ba7e9479cec40a221aab923985fe789d1e0a465f75bbff7cfde21f4702a/djlint-1.39.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4015a76d3fb197a01ca80f7bfd50ba650fe1175741b6ba2355660485e3fc570", size = 540336, upload-time = "2026-06-24T00:26:42.361Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0f/d9b0a570d2f160a90ad539183e70918d24b7e19d140bfef3a85c8c133323/djlint-1.39.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:58bb7608e5f26b4a5205d7e1ddd713ed3bc8ac95c8a6cdf607b2976e9376af71", size = 562440, upload-time = "2026-06-24T00:26:43.547Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d3/339831f8111208df00be411b0e91e5096747d758567a66e35a1faec92348/djlint-1.39.4-cp314-cp314-win32.whl", hash = "sha256:391cb297b493e60690067f2362950f293831ca734e9f4095ead36e29cb257d13", size = 414246, upload-time = "2026-06-24T00:26:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0a/b52cd70eebaa8665e6873649d0637ff7c70e9fc1cc45797309e877775a03/djlint-1.39.4-cp314-cp314-win_amd64.whl", hash = "sha256:66708007bd906f1ba292f31892b6f815da12df15dafcda14bb4bdf5d14d052f0", size = 461587, upload-time = "2026-06-24T00:26:45.707Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/af62da1daa68261c0fdeca2c17ebbf6f105276493cb18d2cf2790ef2a842/djlint-1.39.4-cp314-cp314-win_arm64.whl", hash = "sha256:4109a4034014f83474b61b1670cf5e96d427c116e7363e612b18201e991360f0", size = 408432, upload-time = "2026-06-24T00:26:46.749Z" }, + { url = "https://files.pythonhosted.org/packages/dd/cb/a8ca8a3a940f56d660134c29e61e216f4aa25f9712a4075b5e90be5ddf9c/djlint-1.39.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9fb25d9b04415c61102a6a81ad774671cfc1bf7ef96dbd49e813e2d51abd1e2c", size = 572123, upload-time = "2026-06-24T00:26:47.813Z" }, + { url = "https://files.pythonhosted.org/packages/2a/de/5de99d54d2cdd678b348686dfbe096ca9e0f1ecc2d1a79c3f518d5dae7fe/djlint-1.39.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:65f92a6de7b7ba4b2b12893521b1c9ffa86903e60010b4c04b8e69e5f78aa7aa", size = 545163, upload-time = "2026-06-24T00:26:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/94/74/592885a1baa731f66c3ebb9e1e0bc3db83b1a29e366d730805291eac0fcc/djlint-1.39.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e9fa8902f1ebcd043e7dd7eec950addb7853ce4e78a8e906bc15adab684d1f8", size = 569301, upload-time = "2026-06-24T00:26:49.915Z" }, + { url = "https://files.pythonhosted.org/packages/e6/30/92ded800e019669592648139b32c2e92d60852c6bfb7381878f34f5df798/djlint-1.39.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcf5228b85ced6f84fa7de958c8477b69970c0514ea71ef4ef6e29ba4063733d", size = 586073, upload-time = "2026-06-24T00:26:51.103Z" }, + { url = "https://files.pythonhosted.org/packages/e1/45/c3e9e74c495b869c79d084f5ab4f4aad31ed3814353c45cfd6bb64ac4ee0/djlint-1.39.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71cce0a2081c8bdcae24938c0b56869ca7af74e4e82d6d3c08819a0de10c9031", size = 573594, upload-time = "2026-06-24T00:26:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/88/5c/252007eab3f29b60dc7bd20c6c60cdce2505e4cf386abb372156acae1a4a/djlint-1.39.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:de46ff3130b6294db23eca57c1e59bd6a5eff5808fba72128441a964bbafc654", size = 595901, upload-time = "2026-06-24T00:26:53.24Z" }, + { url = "https://files.pythonhosted.org/packages/9b/24/4ce2d5def17c4bfac39f87bb8745e637550919dedfe05496d162576af849/djlint-1.39.4-cp314-cp314t-win32.whl", hash = "sha256:71a970735936ee8bfca83026d070446d171c582f719ba97cde15981be9e6a337", size = 435412, upload-time = "2026-06-24T00:26:54.38Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/6ab27179e43367112f313246a0dee725ca5768b4a855cd6640680385bf19/djlint-1.39.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5b16ab547959b2b324aefa7d08588aa4d100a95b656b80d45df37078c42602d1", size = 479103, upload-time = "2026-06-24T00:26:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2d/11b6c544f0f067c65b301a28b324967f3d49ea7e2d960d4594e8955f0a85/djlint-1.39.4-cp314-cp314t-win_arm64.whl", hash = "sha256:0978b32a3650b5de9d20ecba8a91e15934385b31e7e604f9c09ae524cb5a83de", size = 426173, upload-time = "2026-06-24T00:26:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b6/2f7aede00bfccc633bd2b2cd809e500d3e0cda9511b2ab46b622510ccdda/djlint-1.39.4-py3-none-any.whl", hash = "sha256:a4b981881ae83ff6f29a3f576184a79fcfe4a817e6daedfffe101a0557f8f86e", size = 62204, upload-time = "2026-06-24T00:26:58.25Z" }, ] [[package]] @@ -138,9 +136,9 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "djlint", specifier = "==1.39.2" }, + { name = "djlint", specifier = "==1.39.4" }, { name = "yamllint", specifier = "==1.38.0" }, - { name = "zizmor", specifier = "==1.25.2" }, + { name = "zizmor", specifier = "==1.26.1" }, ] [[package]] @@ -422,18 +420,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -458,18 +444,18 @@ wheels = [ [[package]] name = "zizmor" -version = "1.25.2" +version = "1.26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/41/8987d546e3101cc76748b2f1b0ccda58e244773ef5124d39e7e749e3d6e4/zizmor-1.25.2.tar.gz", hash = "sha256:f26ffeb16659c8922c7b08203ca5a4f8bf5e1a7e8d190734961c40877cf778ea", size = 517794, upload-time = "2026-05-16T06:28:43.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/a0/a29b38e24981b4bb41db4f292b2c9fb9ddf8b05d6b724abddd7bd108b621/zizmor-1.26.1.tar.gz", hash = "sha256:0c2cc575007a4db99d89d5acc6120cfa7b61504bc2394c3b50af348c73f1916e", size = 535275, upload-time = "2026-06-21T02:47:21.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/bd/84108a92ccbfda0d28efc11f382997c7a767b58863bf4a550634b8cf0211/zizmor-1.25.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17cc8cfd9d472e8b11945a869c198d25cfdf4a33f36fa7a1f9674099f5fb509d", size = 9115548, upload-time = "2026-05-16T06:28:33.591Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c0/66453a2553a66286a96ca32d75e3e6bcc94ce7f907cd5f8c2c3fce55315e/zizmor-1.25.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3e301eb4465e2da77857cf01ab4ef0184cf3818e826800b270ab01ae7338977", size = 8665071, upload-time = "2026-05-16T06:28:30.861Z" }, - { url = "https://files.pythonhosted.org/packages/52/3e/d60939d1cc4907c0d021a7c46362aab5e8045550bb09157d56c070e43568/zizmor-1.25.2-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:cf64374149b567c9373228b76c8e77a389b4071899f84b82c36ee50fab894e79", size = 8842884, upload-time = "2026-05-16T06:28:26.041Z" }, - { url = "https://files.pythonhosted.org/packages/46/82/f3e8d9b6d941194f2558591b449c106d46a16ea566b95eccff3a83bf6acc/zizmor-1.25.2-py3-none-manylinux_2_28_armv7l.whl", hash = "sha256:0beba1601be08bd00c9277e6ed4b026e125b26b379d86d6d98eb708409b3050d", size = 8449741, upload-time = "2026-05-16T06:28:45.424Z" }, - { url = "https://files.pythonhosted.org/packages/4b/13/445bc98acc2c976d6b8f8ca59b9c09f055adb5ffb3445d99af8ff7efcb4f/zizmor-1.25.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:c4246f1344d8dbeffc044d7bb11b131773a7db7eb57d9073c45942dfd3543a1f", size = 9285184, upload-time = "2026-05-16T06:28:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/cf/78/fc7717c706bde7531b2fde12003994fbc04c47ab4f91aa6ca9b3b24b30fd/zizmor-1.25.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dbb1b5c85b8de8eaa0227c6620f06c8e4fbd0a4da2086e218bc225c0bef0923d", size = 8886579, upload-time = "2026-05-16T06:28:51.384Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bc/a46f11377cdc145c625d62d88c30fead56f9d29bc31652069a1a0eaed6c2/zizmor-1.25.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d670a1e2f00b3cd56febd145bc1a0b2c4caf1cbe5dad8128721843fa877e2d2e", size = 8413576, upload-time = "2026-05-16T06:28:36.376Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3b/0fd93b77171c8f229e8e1304eecc9931bf3009f722c57967d545d9f151b6/zizmor-1.25.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b75c84d7387389f95edadbe859fb2aaf0a360c5b080932cc53e92ae1db6f09ef", size = 9378162, upload-time = "2026-05-16T06:28:41.999Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3f/dcb85fb9a0d87794847f9043f9db9bb4d274cf4b8077604bc13850c8fdb4/zizmor-1.25.2-py3-none-win32.whl", hash = "sha256:aa9f4c43b499c55339c3ef2e885133c5017cd9a18d76d9335541203cfa5ae1e7", size = 7548509, upload-time = "2026-05-16T06:28:28.828Z" }, - { url = "https://files.pythonhosted.org/packages/d2/81/1cb088098bd53f9b910098b0c19d06dc587acf328a170ef8afd1cd93b482/zizmor-1.25.2-py3-none-win_amd64.whl", hash = "sha256:af55bd9bd119ea8cbce2a7addc3922503019de32c1fe31106d70b3dc77d77908", size = 8609822, upload-time = "2026-05-16T06:28:48.078Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/2f47f7db8db9491025e00a7f1a0f25d32b642c0285b2fe070ac63e679b47/zizmor-1.26.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7ea21ca959c8e888de238fee81d73a1fdf89a82067eac75b8f1acdbd23e2eeaf", size = 9086061, upload-time = "2026-06-21T02:46:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/58/92/cf6801f01e1d65cbda89a2e2926ea42caf1daad9ffa3f1fc88e4c68f48a9/zizmor-1.26.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:78083b495593f8b0b9dec14036a0836a5afcddda8a40738336ff4e399476b741", size = 8626865, upload-time = "2026-06-21T02:47:00.323Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b1/ff38fc2921f1fb13244bb3a3642c4b45ecf3946c279942aafcb5dbf55a57/zizmor-1.26.1-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:bb7ebbe565a3742eb49a590352127ad549bb122b9b4ff9424ebab7525fa3b6b6", size = 8843965, upload-time = "2026-06-21T02:47:03.318Z" }, + { url = "https://files.pythonhosted.org/packages/3d/06/c07fd0eeef0427d93e99d552d5386526fbcd0bf05fc95cd37bdc6229fccb/zizmor-1.26.1-py3-none-manylinux_2_28_armv7l.whl", hash = "sha256:d3049010b6bd6f849413b6d20c28e0c677b90e0a5b2bc73cbee7f7bd86dc5828", size = 8386985, upload-time = "2026-06-21T02:47:05.741Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2b/61ab13d45d6ce57ef5a08bb3246981f62e30bb4938098b17bb7b88110b79/zizmor-1.26.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:6a958d8a0941d7e1d0de8436670b5cb7fc64c8028b4d16e3f519ccc77f953cef", size = 9257232, upload-time = "2026-06-21T02:47:07.941Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ad/bd74a96cb02045414ec5b573cd97ff3b82a97fd0bd6658f93c36a011c439/zizmor-1.26.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d2744cdf944436ca7a009ae8b626a017a40381ec990216abd6cf6b8beb23323a", size = 8873798, upload-time = "2026-06-21T02:47:10.472Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7e/fb3d608ee11e2f619d43ad93bab46eb7b32769fa82b1d86fd23f27c2585b/zizmor-1.26.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:44099f426af9da750ff9f548a0084e11d7d83e0158fe1a2778672398d728efdd", size = 8350857, upload-time = "2026-06-21T02:47:12.748Z" }, + { url = "https://files.pythonhosted.org/packages/27/cc/82d7a838c2d490071555c364f90eb851044b3eeefc1d68612179a2cd1ae5/zizmor-1.26.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8313cc264dec792f00a7328eb7c8e89e7d62d54f950fc897d1e6a5a6e5762203", size = 9351148, upload-time = "2026-06-21T02:47:14.777Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ee/d2a2301f30b9e1bf0d721bfd31739acd71e048757f9ba79279583eb30ac0/zizmor-1.26.1-py3-none-win32.whl", hash = "sha256:c96d7787d69fb298eae939e00dfdf7f534d7dfbd9cc17ab442c0650a56851415", size = 7531021, upload-time = "2026-06-21T02:47:17.247Z" }, + { url = "https://files.pythonhosted.org/packages/91/58/ad561f3a5057d3c0f152002e180a3a5745e72ea9d69bf66450ef9f5d3fe5/zizmor-1.26.1-py3-none-win_amd64.whl", hash = "sha256:0a05acf6068609fb6df3b137276cf18a686226a1e0e207941cb34a85929f16cf", size = 8616584, upload-time = "2026-06-21T02:47:19.094Z" }, ] -- 2.54.0 From 0c67849e68160d4cd121ecb4134ab43159710d0c Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Mon, 29 Jun 2026 12:20:22 +0530 Subject: [PATCH 049/169] fix(packages): validate debian distribution and component names (#38116) **Newline injection into the Debian Release and Packages indices** The `distribution` and `component` come straight from the request path and are written line by line into the generated `Release` and `Packages` files (the `Suite`/`Codename`/`Components` lines and the `Filename: pool///...` line), but `UploadPackageFile` only checked they were non-empty. `ctx.PathParam` url-decodes the segment, so an encoded newline such as `main%0AInjected-Field: x` is accepted, stored and then re-emitted for that distribution, which lets an authenticated uploader forge extra fields in the index apt consumes. Restricted both values to a conservative name pattern in the handler, since that is the layer that accepts them; this should also keep the pool paths well formed. --------- Co-authored-by: wxiaoguang --- modules/packages/debian/metadata.go | 55 +++++++++++++++++------- modules/packages/debian/metadata_test.go | 42 +++++++++++++++--- routers/api/packages/debian/debian.go | 6 +-- 3 files changed, 79 insertions(+), 24 deletions(-) diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go index 8d8b03147f..c3bb86ddd1 100644 --- a/modules/packages/debian/metadata.go +++ b/modules/packages/debian/metadata.go @@ -11,6 +11,7 @@ import ( "net/mail" "regexp" "strings" + "sync" "gitea.dev/modules/util" "gitea.dev/modules/validation" @@ -36,18 +37,36 @@ const ( controlTar = "control.tar" ) -var ( - ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing") - ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm") - ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") - ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") - ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid") +var GlobalVars = sync.OnceValue(func() (ret struct { + ErrMissingControlFile error + ErrUnsupportedCompression error + ErrInvalidName error + ErrInvalidVersion error + ErrInvalidArchitecture error + + namePattern *regexp.Regexp + versionPattern *regexp.Regexp + symbolPattern *regexp.Regexp +}, +) { + ret.ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing") + ret.ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm") + ret.ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") + ret.ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") + ret.ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid") // https://www.debian.org/doc/debian-policy/ch-controlfields.html#source - namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`) + ret.namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`) // https://www.debian.org/doc/debian-policy/ch-controlfields.html#version - versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`) -) + ret.versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`) + + // distribution and component are taken from the request path and written + // verbatim into the generated line-based Release and Packages indices (and + // into the pool// paths referenced from them), so + // they must be restricted to a character set that cannot break that format. + ret.symbolPattern = regexp.MustCompile(`\A[a-zA-Z0-9][a-zA-Z0-9.~+_-]*\z`) + return ret +}) type Package struct { Name string @@ -64,6 +83,10 @@ type Metadata struct { Dependencies []string `json:"dependencies,omitempty"` } +func IsValidDistributionOrComponent(s string) bool { + return GlobalVars().symbolPattern.MatchString(s) +} + // ParsePackage parses the Debian package file // https://manpages.debian.org/bullseye/dpkg-dev/deb.5.en.html func ParsePackage(r io.Reader) (*Package, error) { @@ -109,7 +132,7 @@ func ParsePackage(r io.Reader) (*Package, error) { inner = zr default: - return nil, ErrUnsupportedCompression + return nil, GlobalVars().ErrUnsupportedCompression } tr := tar.NewReader(inner) @@ -133,7 +156,7 @@ func ParsePackage(r io.Reader) (*Package, error) { } } - return nil, ErrMissingControlFile + return nil, GlobalVars().ErrMissingControlFile } // ParseControlFile parses a Debian control file to retrieve the metadata @@ -210,14 +233,14 @@ func ParseControlFile(r io.Reader) (*Package, error) { return nil, err } - if !namePattern.MatchString(p.Name) { - return nil, ErrInvalidName + if !GlobalVars().namePattern.MatchString(p.Name) { + return nil, GlobalVars().ErrInvalidName } - if !versionPattern.MatchString(p.Version) { - return nil, ErrInvalidVersion + if !GlobalVars().versionPattern.MatchString(p.Version) { + return nil, GlobalVars().ErrInvalidVersion } if p.Architecture == "" { - return nil, ErrInvalidArchitecture + return nil, GlobalVars().ErrInvalidArchitecture } dependencies := strings.Split(depends.String(), ",") diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go index 6ff10a7f21..25784ac08a 100644 --- a/modules/packages/debian/metadata_test.go +++ b/modules/packages/debian/metadata_test.go @@ -49,7 +49,7 @@ func TestParsePackage(t *testing.T) { p, err := ParsePackage(data) assert.Nil(t, p) - assert.ErrorIs(t, err, ErrMissingControlFile) + assert.ErrorIs(t, err, GlobalVars().ErrMissingControlFile) }) t.Run("Compression", func(t *testing.T) { @@ -58,7 +58,7 @@ func TestParsePackage(t *testing.T) { p, err := ParsePackage(data) assert.Nil(t, p) - assert.ErrorIs(t, err, ErrUnsupportedCompression) + assert.ErrorIs(t, err, GlobalVars().ErrUnsupportedCompression) }) var buf bytes.Buffer @@ -141,7 +141,7 @@ func TestParseControlFile(t *testing.T) { for _, name := range []string{"", "-cd"} { p, err := ParseControlFile(buildContent(name, packageVersion, packageArchitecture)) assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidName) + assert.ErrorIs(t, err, GlobalVars().ErrInvalidName) } }) @@ -149,14 +149,14 @@ func TestParseControlFile(t *testing.T) { for _, version := range []string{"", "1-", ":1.0", "1_0"} { p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture)) assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidVersion) + assert.ErrorIs(t, err, GlobalVars().ErrInvalidVersion) } }) t.Run("InvalidArchitecture", func(t *testing.T) { p, err := ParseControlFile(buildContent(packageName, packageVersion, "")) assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidArchitecture) + assert.ErrorIs(t, err, GlobalVars().ErrInvalidArchitecture) }) t.Run("Valid", func(t *testing.T) { @@ -200,3 +200,35 @@ func TestParseControlFile(t *testing.T) { assert.NotContains(t, p.Control, "evil.deb") }) } + +func TestValidateDistributionOrComponent(t *testing.T) { + bad := []string{ + "", + ".", + "..", + "-stable", + ".hidden", + "a/b", + "a b", + "bookworm\nSigned-By: evil", + "main\nFilename: pool/x", + "a\tb", + } + for _, name := range bad { + assert.False(t, IsValidDistributionOrComponent(name), "bad=%q", name) + } + + good := []string{ + "stable", + "bookworm", + "bookworm-backports", + "stable-updates", + "main", + "non-free-firmware", + "a", + "1", + } + for _, name := range good { + assert.True(t, IsValidDistributionOrComponent(name), "good=%q", name) + } +} diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index f9bf2960f8..ee9d4d554a 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -120,9 +120,9 @@ func GetRepositoryFileByHash(ctx *context.Context) { } func UploadPackageFile(ctx *context.Context) { - distribution := strings.TrimSpace(ctx.PathParam("distribution")) - component := strings.TrimSpace(ctx.PathParam("component")) - if distribution == "" || component == "" { + distribution := ctx.PathParam("distribution") + component := ctx.PathParam("component") + if !debian_module.IsValidDistributionOrComponent(distribution) || !debian_module.IsValidDistributionOrComponent(component) { apiError(ctx, http.StatusBadRequest, "invalid distribution or component") return } -- 2.54.0 From e68ee61879a6e6dacbf70cab5219ca0527c35657 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 29 Jun 2026 01:30:14 -0700 Subject: [PATCH 050/169] chore(deps): update action dependencies (#38258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | Pending | |---|---|---|---|---| | [actions/setup-go](https://redirect.github.com/actions/setup-go) | action | minor | `v6.4.0` → `v6.5.0` | | | [go-gitea/giteabot](https://redirect.github.com/go-gitea/giteabot) | action | patch | `v1.0.3` → `v1.0.4` | | | redis | service | digest | `a505f8b` → `c904002` | | | [renovatebot/github-action](https://redirect.github.com/renovatebot/github-action) | action | patch | `v46.1.15` → `v46.1.16` | `v46.1.17` | --- ### Release Notes
actions/setup-go (actions/setup-go) ### [`v6.5.0`](https://redirect.github.com/actions/setup-go/releases/tag/v6.5.0) [Compare Source](https://redirect.github.com/actions/setup-go/compare/v6.4.0...v6.5.0) ##### What's Changed ##### Dependency update - Upgrade actions dependencies by [@​priyagupta108](https://redirect.github.com/priyagupta108) with [@​Copilot](https://redirect.github.com/Copilot) in [#​744](https://redirect.github.com/actions/setup-go/pull/744) - Upgrade [@​types/node](https://redirect.github.com/types/node) and typescript-eslint dependencies to resolve npm audit findings by [@​HarithaVattikuti](https://redirect.github.com/HarithaVattikuti) in [#​755](https://redirect.github.com/actions/setup-go/pull/755) - Upgrade [@​actions/cache](https://redirect.github.com/actions/cache) to 5.1.0, log cache write denied by [@​jasongin](https://redirect.github.com/jasongin) in [#​758](https://redirect.github.com/actions/setup-go/pull/758) - Upgrade version to 6.5.0 in package.json and package-lock.json by [@​HarithaVattikuti](https://redirect.github.com/HarithaVattikuti) in [#​762](https://redirect.github.com/actions/setup-go/pull/762) ##### New Contributors - [@​priyagupta108](https://redirect.github.com/priyagupta108) with [@​Copilot](https://redirect.github.com/Copilot) made their first contribution in [#​744](https://redirect.github.com/actions/setup-go/pull/744) - [@​jasongin](https://redirect.github.com/jasongin) made their first contribution in [#​758](https://redirect.github.com/actions/setup-go/pull/758) **Full Changelog**:
go-gitea/giteabot (go-gitea/giteabot) ### [`v1.0.4`](https://redirect.github.com/go-gitea/giteabot/releases/tag/v1.0.4) [Compare Source](https://redirect.github.com/go-gitea/giteabot/compare/v1.0.3...v1.0.4) ##### What's Changed - Keep lgtm status up to date on fork and backport PRs by [@​silverwind](https://redirect.github.com/silverwind) in [#​9](https://redirect.github.com/go-gitea/giteabot/pull/9) **Full Changelog**:
renovatebot/github-action (renovatebot/github-action) ### [`v46.1.16`](https://redirect.github.com/renovatebot/github-action/releases/tag/v46.1.16) [Compare Source](https://redirect.github.com/renovatebot/github-action/compare/v46.1.15...v46.1.16) ##### Documentation - update references to renovatebot/github-action to v46.1.15 ([0013591](https://redirect.github.com/renovatebot/github-action/commit/00135917fdbb8f382071ce3f27c8432ad0f75c2a)) ##### Miscellaneous Chores - **deps:** update dependency [@​types/node](https://redirect.github.com/types/node) to v24.13.0 ([358d0a4](https://redirect.github.com/renovatebot/github-action/commit/358d0a480c37ecd2b23ab66dcd5170452917642c)) - **deps:** update dependency [@​types/node](https://redirect.github.com/types/node) to v24.13.1 ([783fe90](https://redirect.github.com/renovatebot/github-action/commit/783fe90b5a88b8a02d5d6c9bb65c01e36b110545)) - **deps:** update dependency [@​types/node](https://redirect.github.com/types/node) to v24.13.2 ([74b1acf](https://redirect.github.com/renovatebot/github-action/commit/74b1acf27101775dfaf2793c89c214898cd33520)) - **deps:** update dependency [@​types/node](https://redirect.github.com/types/node) to v24.13.2 ([#​1049](https://redirect.github.com/renovatebot/github-action/issues/1049)) ([23dcba0](https://redirect.github.com/renovatebot/github-action/commit/23dcba0a91738a6e394d2e991c307937406b6587)) - **deps:** update dependency esbuild to v0.28.1 \[security] ([#​1041](https://redirect.github.com/renovatebot/github-action/issues/1041)) ([54012bd](https://redirect.github.com/renovatebot/github-action/commit/54012bd29e2a1a395bbffede3e46a836aebc3e47)) - **deps:** update dependency lint-staged to v17 ([#​1051](https://redirect.github.com/renovatebot/github-action/issues/1051)) ([6a9f6dc](https://redirect.github.com/renovatebot/github-action/commit/6a9f6dc5beb03977800c57cd92b5ca15bfc67439)) - **deps:** update dependency npm-run-all2 to v9 ([#​1052](https://redirect.github.com/renovatebot/github-action/issues/1052)) ([8757a4e](https://redirect.github.com/renovatebot/github-action/commit/8757a4e574f6d5c0ba3fd6a6706c420c5c96e1c9)) - **deps:** update dependency npm-run-all2 to v9.0.2 ([2c2c4e5](https://redirect.github.com/renovatebot/github-action/commit/2c2c4e5c89b4c5bfdd31288b8f30d8acf0a3c4a3)) - **deps:** update linters to v8.60.1 ([d40e1b7](https://redirect.github.com/renovatebot/github-action/commit/d40e1b7d86f5ce052321dee875f215d0c0db3443)) - **deps:** update linters to v8.61.0 ([#​1043](https://redirect.github.com/renovatebot/github-action/issues/1043)) ([1e06192](https://redirect.github.com/renovatebot/github-action/commit/1e061929c42cf0828b24480be6f542ba6ccf88a3)) - **deps:** update node.js to v24.17.0 ([#​1050](https://redirect.github.com/renovatebot/github-action/issues/1050)) ([2cf33bc](https://redirect.github.com/renovatebot/github-action/commit/2cf33bc523576895fa380cf8af2e05336c943486)) - **deps:** update pnpm to v10.34.2 ([#​1048](https://redirect.github.com/renovatebot/github-action/issues/1048)) ([63ebb9d](https://redirect.github.com/renovatebot/github-action/commit/63ebb9d84b858604f205265f37e642256bf5f295)) - **deps:** update pnpm to v10.34.3 ([#​1054](https://redirect.github.com/renovatebot/github-action/issues/1054)) ([cd3436d](https://redirect.github.com/renovatebot/github-action/commit/cd3436d028bc86910e9cbf348b1c975a58d90135)) - **deps:** update pnpm/action-setup action to v6 ([#​1053](https://redirect.github.com/renovatebot/github-action/issues/1053)) ([77e5805](https://redirect.github.com/renovatebot/github-action/commit/77e58054f1f031a8b4f80dcbbd00f65e45643d09)) - **deps:** update prettier packages to v3.8.4 ([#​1045](https://redirect.github.com/renovatebot/github-action/issues/1045)) ([d688888](https://redirect.github.com/renovatebot/github-action/commit/d688888385cd5bd04a70a7889c8112b7d960512b)) - **deps:** update semantic-release monorepo to v25.0.4 ([#​1046](https://redirect.github.com/renovatebot/github-action/issues/1046)) ([d2dacc8](https://redirect.github.com/renovatebot/github-action/commit/d2dacc89959d7963d77f06ffadf0abcf1265fdc7)) - **deps:** update semantic-release monorepo to v25.0.5 ([#​1047](https://redirect.github.com/renovatebot/github-action/issues/1047)) ([d91f80c](https://redirect.github.com/renovatebot/github-action/commit/d91f80c864d00b0ddc134c949cf464395ef1afaa)) ##### Build System - **deps:** lock file maintenance ([26f827f](https://redirect.github.com/renovatebot/github-action/commit/26f827fdc5121b9d4fbe1cf8dcb76d6efe58b78b)) ##### Continuous Integration - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.214.6 ([f3fd163](https://redirect.github.com/renovatebot/github-action/commit/f3fd1634318528e2c0bf9402ad11ced1e2583cc4)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.216.1 ([8cf15ee](https://redirect.github.com/renovatebot/github-action/commit/8cf15ee083f561ff061f991a7ae7daeef11b0243)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.216.2 ([29c9f31](https://redirect.github.com/renovatebot/github-action/commit/29c9f31e4a3ad4f169bbfaf78a3fcbba9569e275)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.216.4 ([400f75c](https://redirect.github.com/renovatebot/github-action/commit/400f75cbdb0a8ec5364c9f3f22932c79c91ec56f)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.217.0 ([2aea29e](https://redirect.github.com/renovatebot/github-action/commit/2aea29ebc0bebd74676a36c246d9dffc8635aa2a)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.217.1 ([268f254](https://redirect.github.com/renovatebot/github-action/commit/268f25430113d2f91e6ab0244171726aff45791d)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.218.0 ([ebcc800](https://redirect.github.com/renovatebot/github-action/commit/ebcc800ccdcbca03d3939d07cd5170dcbe3fddf1)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.219.0 ([a61593e](https://redirect.github.com/renovatebot/github-action/commit/a61593e15cdcaa203cdf199b01785cbde1b3393b)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.220.0 ([#​1037](https://redirect.github.com/renovatebot/github-action/issues/1037)) ([0d198c1](https://redirect.github.com/renovatebot/github-action/commit/0d198c1f3cedd5d962195b498b53d23de67b3c7f)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.222.0 ([46f2bd6](https://redirect.github.com/renovatebot/github-action/commit/46f2bd6ed2f60e6e68085b7ccb0d408e498ad646)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.222.1 ([90deabf](https://redirect.github.com/renovatebot/github-action/commit/90deabf8530cd43db786bef00bb6a5bbbf66e372)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.224.0 ([22d7b5c](https://redirect.github.com/renovatebot/github-action/commit/22d7b5c57aa60ca4659f5f24d368812e789f1141)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.224.1 ([39a2ba1](https://redirect.github.com/renovatebot/github-action/commit/39a2ba1236cbe85d17c776505eda386398144ed2)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.225.0 ([c2f08ab](https://redirect.github.com/renovatebot/github-action/commit/c2f08ab1a1834784134b79fa3a30b83132ff5c51)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.226.1 ([75a5340](https://redirect.github.com/renovatebot/github-action/commit/75a5340ae6e848edb49abbeb7343434c84fd144c)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.227.0 ([da1079a](https://redirect.github.com/renovatebot/github-action/commit/da1079ac41ddf8ac9d473f3110b9bc2d20e8c823)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.227.1 ([26a0ce7](https://redirect.github.com/renovatebot/github-action/commit/26a0ce7c73154e11c6677da35bd23ddf1c77a704)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.228.0 ([9dd450f](https://redirect.github.com/renovatebot/github-action/commit/9dd450fe094737324c6740847d34d21d0a12670e)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.228.1 ([066bf0a](https://redirect.github.com/renovatebot/github-action/commit/066bf0aa9449c5bd4e2315df33f2986b10299ee7)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.229.0 ([edd7e4f](https://redirect.github.com/renovatebot/github-action/commit/edd7e4f83edd2652f40ed3d13236296a75cb9d9c)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.229.1 ([64e44a4](https://redirect.github.com/renovatebot/github-action/commit/64e44a4239e1eade784cf0abd54ffc4e82cb40e1)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.229.2 ([dce4d1b](https://redirect.github.com/renovatebot/github-action/commit/dce4d1b6ba43914d91ab2a2a713fd384298a42fe)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.230.0 ([30fd043](https://redirect.github.com/renovatebot/github-action/commit/30fd04394d4dfe018df48314de0036334d88d119)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.230.1 ([425d313](https://redirect.github.com/renovatebot/github-action/commit/425d313d98c58c11780a712fbee72a7e35d25b92)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.231.0 ([ae939aa](https://redirect.github.com/renovatebot/github-action/commit/ae939aab83afa3d641843d1d04300091752e09a8)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.231.1 ([cac502d](https://redirect.github.com/renovatebot/github-action/commit/cac502de33fd8931d15a849d8eff67d8b75d253c)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.231.2 ([242a56f](https://redirect.github.com/renovatebot/github-action/commit/242a56f27d85117d4b0703ff1e381aaafdc0488e)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.231.3 ([3b66329](https://redirect.github.com/renovatebot/github-action/commit/3b6632905280f6bc2656245b3916333c3c224c99)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.232.0 ([c0502ab](https://redirect.github.com/renovatebot/github-action/commit/c0502aba634bf921fa2af4eb4a052318991265bd)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.232.1 ([d46a7eb](https://redirect.github.com/renovatebot/github-action/commit/d46a7ebfc5129ac009353beed92152c3ddc4ce3b)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.233.1 ([b476f30](https://redirect.github.com/renovatebot/github-action/commit/b476f3002f23f603eb89450785a8ce68037ebf20)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.233.2 ([bc50ad1](https://redirect.github.com/renovatebot/github-action/commit/bc50ad1e38f4e32bed5288aec6e9c303a5e81117)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.233.3 ([908f92d](https://redirect.github.com/renovatebot/github-action/commit/908f92dbc49eaefff1e0c8771aa41a957268baa5)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.233.4 ([a48bc32](https://redirect.github.com/renovatebot/github-action/commit/a48bc32b6b570fe1fa55975fda7205bbbb98afa3)) - **deps:** update ghcr.io/renovatebot/renovate docker tag to v43.234.0 ([c929092](https://redirect.github.com/renovatebot/github-action/commit/c929092dcc2e71fcddf23dc5c9d2cdf70ed17ed4)) - **deps:** update ghcr.io/zizmorcore/zizmor docker tag to v1.26.1 ([#​1055](https://redirect.github.com/renovatebot/github-action/issues/1055)) ([c878bfb](https://redirect.github.com/renovatebot/github-action/commit/c878bfb5430ce52efc451671e7887a2b5d755ffc)) - **deps:** update zizmorcore/zizmor-action action to v0.5.7 ([996e7bc](https://redirect.github.com/renovatebot/github-action/commit/996e7bc84761f298cb8bc5c765895b6db953876b))
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Only on Monday (`* * * * 1`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- .github/actions/go-setup/action.yml | 2 +- .github/workflows/cron-licenses.yml | 2 +- .github/workflows/cron-renovate.yml | 2 +- .github/workflows/giteabot-backport.yml | 2 +- .github/workflows/giteabot.yml | 2 +- .github/workflows/pull-db-tests.yml | 2 +- .github/workflows/release-nightly.yml | 2 +- .github/workflows/release-tag-rc.yml | 2 +- .github/workflows/release-tag-version.yml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/actions/go-setup/action.yml b/.github/actions/go-setup/action.yml index a99ef0629a..8fd6e519a2 100644 --- a/.github/actions/go-setup/action.yml +++ b/.github/actions/go-setup/action.yml @@ -13,7 +13,7 @@ runs: using: composite steps: - uses: ./.github/actions/free-disk-space - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index 9ab7c4b926..06ea0e3031 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/cron-renovate.yml b/.github/workflows/cron-renovate.yml index 78fc20ca90..99d3a79ddb 100644 --- a/.github/workflows/cron-renovate.yml +++ b/.github/workflows/cron-renovate.yml @@ -21,7 +21,7 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15 + - uses: renovatebot/github-action@6d859fc95779be83a0335ca704879b47e5d79641 # v46.1.16 with: renovate-version: ${{ env.RENOVATE_VERSION }} configurationFile: renovate.json5 diff --git a/.github/workflows/giteabot-backport.yml b/.github/workflows/giteabot-backport.yml index b90c225b4b..f398d48126 100644 --- a/.github/workflows/giteabot-backport.yml +++ b/.github/workflows/giteabot-backport.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: go-gitea/giteabot@f8a6f4c14d46920b4b5448852be3de72d00066f0 # v1.0.3 + - uses: go-gitea/giteabot@912675d47455ac93be82d8bda4667a02b20a6fe4 # v1.0.4 with: github_token: ${{ secrets.GITEABOT_TOKEN }} gitea_fork: giteabot/gitea diff --git a/.github/workflows/giteabot.yml b/.github/workflows/giteabot.yml index 5ffde2e75e..c439850a20 100644 --- a/.github/workflows/giteabot.yml +++ b/.github/workflows/giteabot.yml @@ -58,7 +58,7 @@ jobs: steps: # pull_request_review runs without repository secrets on fork PRs, so fall # back to the workflow token for the non-backport checks handled here. - - uses: go-gitea/giteabot@f8a6f4c14d46920b4b5448852be3de72d00066f0 # v1.0.3 + - uses: go-gitea/giteabot@912675d47455ac93be82d8bda4667a02b20a6fe4 # v1.0.4 with: github_token: ${{ secrets.GITEABOT_TOKEN || github.token }} checks: ${{ github.event.inputs.checks || 'labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }} diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index f73bb7f95f..83026bcb59 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -131,7 +131,7 @@ jobs: ports: - "7700:7700" redis: - image: redis:latest@sha256:a505f8b9d8ac3ff7b0848055b4abf1901d6d77606774aa1e38bd37f1197ed2b5 + image: redis:latest@sha256:c904002d182255b6db3cbe3a1e8ce6c187d15390c39500b59fc07181aabff7bf options: >- # wait until redis has started --health-cmd "redis-cli ping" --health-interval 5s diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index e3997c8123..7adeb0323b 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -19,7 +19,7 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 03f17e8c23..c8186f2144 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -20,7 +20,7 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: go-version-file: go.mod check-latest: true diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 09f9a73971..8225dae257 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -23,7 +23,7 @@ jobs: # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: go-version-file: go.mod check-latest: true -- 2.54.0 From 07b18467c0af13d086c23c6a8174ef762230c7ac Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 29 Jun 2026 03:59:14 -0700 Subject: [PATCH 051/169] fix: update npm dependencies, fix misc issues (#38257) Update all npm dependencies and fix discovered issues. Co-authored-by: bircni Co-authored-by: silverwind Co-authored-by: wxiaoguang --- Makefile | 7 +- options/fileicon/material-icon-rules.json | 574 ++++--- options/fileicon/material-icon-svgs.json | 1120 +++++++------- package.json | 26 +- pnpm-lock.yaml | 1356 +++++------------ renovate.json5 | 2 +- vite.config.ts | 2 +- web_src/js/modules/i18n.test.ts | 15 +- web_src/js/modules/i18n.ts | 4 +- .../plugins/frontend-openapi-swagger.ts | 6 +- 10 files changed, 1306 insertions(+), 1806 deletions(-) diff --git a/Makefile b/Makefile index 0dba802b12..ea83477f98 100644 --- a/Makefile +++ b/Makefile @@ -127,6 +127,7 @@ BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* mo GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go SVG_DEST_DIR := public/assets/img/svg +SVG_DEST_DIRS := $(SVG_DEST_DIR) options/fileicon AIR_TMP_DIR := .air @@ -633,10 +634,10 @@ svg: node_modules ## build svg files .PHONY: svg-check svg-check: svg - @git add $(SVG_DEST_DIR) - @diff=$$(git diff --color=always --cached $(SVG_DEST_DIR)); \ + @git add $(SVG_DEST_DIRS) + @diff=$$(git diff --color=always --cached $(SVG_DEST_DIRS)); \ if [ -n "$$diff" ]; then \ - echo "Please run 'make svg' and 'git add $(SVG_DEST_DIR)' and commit the result:"; \ + echo "Please run 'make svg' and 'git add $(SVG_DEST_DIRS)' and commit the result:"; \ printf "%s" "$${diff}"; \ exit 1; \ fi diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index aa967b7100..c0c3a8d380 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -5,6 +5,11 @@ "_rust": "folder-rust", "-rust": "folder-rust", "__rust__": "folder-rust", + "cargo": "folder-rust", + ".cargo": "folder-rust", + "_cargo": "folder-rust", + "-cargo": "folder-rust", + "__cargo__": "folder-rust", "bot": "folder-robot", ".bot": "folder-robot", "_bot": "folder-robot", @@ -350,6 +355,11 @@ "_scripting": "folder-scripts", "-scripting": "folder-scripts", "__scripting__": "folder-scripts", + "xtask": "folder-scripts", + ".xtask": "folder-scripts", + "_xtask": "folder-scripts", + "-xtask": "folder-scripts", + "__xtask__": "folder-scripts", "node": "folder-node", ".node": "folder-node", "_node": "folder-node", @@ -1020,6 +1030,11 @@ "_externals": "folder-lib", "-externals": "folder-lib", "__externals__": "folder-lib", + "crates": "folder-lib", + ".crates": "folder-lib", + "_crates": "folder-lib", + "-crates": "folder-lib", + "__crates__": "folder-lib", "themes": "folder-theme", ".themes": "folder-theme", "_themes": "folder-theme", @@ -4414,22 +4429,18 @@ "_organism": "folder-organism", "-organism": "folder-organism", "__organism__": "folder-organism", + "claude": "folder-claude", ".claude": "folder-claude", - "..claude": "folder-claude", - "_.claude": "folder-claude", - "-.claude": "folder-claude", - "__.claude__": "folder-claude", + "_claude": "folder-claude", + "-claude": "folder-claude", + "__claude__": "folder-claude", + "cursor": "folder-cursor", ".cursor": "folder-cursor", - "..cursor": "folder-cursor", - "_.cursor": "folder-cursor", - "-.cursor": "folder-cursor", - "__.cursor__": "folder-cursor", - ".gemini": "folder-gemini-ai", - "..gemini": "folder-gemini-ai", - "_.gemini": "folder-gemini-ai", - "-.gemini": "folder-gemini-ai", - "__.gemini__": "folder-gemini-ai", + "_cursor": "folder-cursor", + "-cursor": "folder-cursor", + "__cursor__": "folder-cursor", "gemini": "folder-gemini-ai", + ".gemini": "folder-gemini-ai", "_gemini": "folder-gemini-ai", "-gemini": "folder-gemini-ai", "__gemini__": "folder-gemini-ai", @@ -4443,6 +4454,11 @@ "_geminiai": "folder-gemini-ai", "-geminiai": "folder-gemini-ai", "__geminiai__": "folder-gemini-ai", + "opencode": "folder-opencode", + ".opencode": "folder-opencode", + "_opencode": "folder-opencode", + "-opencode": "folder-opencode", + "__opencode__": "folder-opencode", "input": "folder-input", ".input": "folder-input", "_input": "folder-input", @@ -4583,6 +4599,11 @@ "_instructions": "folder-instructions", "-instructions": "folder-instructions", "__instructions__": "folder-instructions", + "zed": "folder-zed", + ".zed": "folder-zed", + "_zed": "folder-zed", + "-zed": "folder-zed", + "__zed__": "folder-zed", "appwrite": "folder-appwrite", ".appwrite": "folder-appwrite", "_appwrite": "folder-appwrite", @@ -4617,7 +4638,22 @@ ".kotlin": "folder-kotlin", "_kotlin": "folder-kotlin", "-kotlin": "folder-kotlin", - "__kotlin__": "folder-kotlin" + "__kotlin__": "folder-kotlin", + "redis": "folder-redis", + ".redis": "folder-redis", + "_redis": "folder-redis", + "-redis": "folder-redis", + "__redis__": "folder-redis", + "redis-db": "folder-redis", + ".redis-db": "folder-redis", + "_redis-db": "folder-redis", + "-redis-db": "folder-redis", + "__redis-db__": "folder-redis", + "redislabs": "folder-redis", + ".redislabs": "folder-redis", + "_redislabs": "folder-redis", + "-redislabs": "folder-redis", + "__redislabs__": "folder-redis" }, "folderNamesExpanded": { "rust": "folder-rust-open", @@ -4625,6 +4661,11 @@ "_rust": "folder-rust-open", "-rust": "folder-rust-open", "__rust__": "folder-rust-open", + "cargo": "folder-rust-open", + ".cargo": "folder-rust-open", + "_cargo": "folder-rust-open", + "-cargo": "folder-rust-open", + "__cargo__": "folder-rust-open", "bot": "folder-robot-open", ".bot": "folder-robot-open", "_bot": "folder-robot-open", @@ -4970,6 +5011,11 @@ "_scripting": "folder-scripts-open", "-scripting": "folder-scripts-open", "__scripting__": "folder-scripts-open", + "xtask": "folder-scripts-open", + ".xtask": "folder-scripts-open", + "_xtask": "folder-scripts-open", + "-xtask": "folder-scripts-open", + "__xtask__": "folder-scripts-open", "node": "folder-node-open", ".node": "folder-node-open", "_node": "folder-node-open", @@ -5640,6 +5686,11 @@ "_externals": "folder-lib-open", "-externals": "folder-lib-open", "__externals__": "folder-lib-open", + "crates": "folder-lib-open", + ".crates": "folder-lib-open", + "_crates": "folder-lib-open", + "-crates": "folder-lib-open", + "__crates__": "folder-lib-open", "themes": "folder-theme-open", ".themes": "folder-theme-open", "_themes": "folder-theme-open", @@ -9034,22 +9085,18 @@ "_organism": "folder-organism-open", "-organism": "folder-organism-open", "__organism__": "folder-organism-open", + "claude": "folder-claude-open", ".claude": "folder-claude-open", - "..claude": "folder-claude-open", - "_.claude": "folder-claude-open", - "-.claude": "folder-claude-open", - "__.claude__": "folder-claude-open", + "_claude": "folder-claude-open", + "-claude": "folder-claude-open", + "__claude__": "folder-claude-open", + "cursor": "folder-cursor-open", ".cursor": "folder-cursor-open", - "..cursor": "folder-cursor-open", - "_.cursor": "folder-cursor-open", - "-.cursor": "folder-cursor-open", - "__.cursor__": "folder-cursor-open", - ".gemini": "folder-gemini-ai-open", - "..gemini": "folder-gemini-ai-open", - "_.gemini": "folder-gemini-ai-open", - "-.gemini": "folder-gemini-ai-open", - "__.gemini__": "folder-gemini-ai-open", + "_cursor": "folder-cursor-open", + "-cursor": "folder-cursor-open", + "__cursor__": "folder-cursor-open", "gemini": "folder-gemini-ai-open", + ".gemini": "folder-gemini-ai-open", "_gemini": "folder-gemini-ai-open", "-gemini": "folder-gemini-ai-open", "__gemini__": "folder-gemini-ai-open", @@ -9063,6 +9110,11 @@ "_geminiai": "folder-gemini-ai-open", "-geminiai": "folder-gemini-ai-open", "__geminiai__": "folder-gemini-ai-open", + "opencode": "folder-opencode-open", + ".opencode": "folder-opencode-open", + "_opencode": "folder-opencode-open", + "-opencode": "folder-opencode-open", + "__opencode__": "folder-opencode-open", "input": "folder-input-open", ".input": "folder-input-open", "_input": "folder-input-open", @@ -9203,6 +9255,11 @@ "_instructions": "folder-instructions-open", "-instructions": "folder-instructions-open", "__instructions__": "folder-instructions-open", + "zed": "folder-zed-open", + ".zed": "folder-zed-open", + "_zed": "folder-zed-open", + "-zed": "folder-zed-open", + "__zed__": "folder-zed-open", "appwrite": "folder-appwrite-open", ".appwrite": "folder-appwrite-open", "_appwrite": "folder-appwrite-open", @@ -9237,7 +9294,22 @@ ".kotlin": "folder-kotlin-open", "_kotlin": "folder-kotlin-open", "-kotlin": "folder-kotlin-open", - "__kotlin__": "folder-kotlin-open" + "__kotlin__": "folder-kotlin-open", + "redis": "folder-redis-open", + ".redis": "folder-redis-open", + "_redis": "folder-redis-open", + "-redis": "folder-redis-open", + "__redis__": "folder-redis-open", + "redis-db": "folder-redis-open", + ".redis-db": "folder-redis-open", + "_redis-db": "folder-redis-open", + "-redis-db": "folder-redis-open", + "__redis-db__": "folder-redis-open", + "redislabs": "folder-redis-open", + ".redislabs": "folder-redis-open", + "_redislabs": "folder-redis-open", + "-redislabs": "folder-redis-open", + "__redislabs__": "folder-redis-open" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {}, @@ -9247,11 +9319,29 @@ "xhtml": "html", "html_vm": "html", "asp": "html", + "html": "html", + "aspx": "html", + "jshtm": "html", + "rhtml": "html", + "shtml": "html", + "volt": "html", + "xht": "html", "jade": "pug", "pug": "pug", "md": "markdown", "markdown": "markdown", "rst": "markdown", + "copilotmd": "markdown", + "litcoffee": "markdown", + "markdn": "markdown", + "mdown": "markdown", + "mdtext": "markdown", + "mdtxt": "markdown", + "mdwn": "markdown", + "mkd": "markdown", + "mkdn": "markdown", + "ronn": "markdown", + "workbook": "markdown", "blink": "blink", "css": "css", "scss": "sass", @@ -9263,6 +9353,11 @@ "json5": "json", "jsonl": "json", "ndjson": "json", + "geojson": "json", + "har": "json", + "jsonld": "json", + "webmanifest": "json", + "ts.map": "json", "schema.json": "json_schema", "hjson": "hjson", "jinja": "jinja", @@ -9281,6 +9376,14 @@ "yml.dist": "yaml", "yaml.dist": "yaml", "yaml-tmlanguage": "yaml", + "yaml": "yaml", + "yml": "yaml", + "cff": "yaml", + "eyaml": "yaml", + "eyml": "yaml", + "winget": "yaml", + "yaml-tmpreferences": "yaml", + "yaml-tmtheme": "yaml", "xml": "xml", "plist": "xml", "xsd": "xml", @@ -9298,6 +9401,48 @@ "dmn": "xml", "jrxml": "xml", "xmp": "xml", + "ascx": "xml", + "atom": "xml", + "axaml": "xml", + "axml": "xml", + "bpmn": "xml", + "csl": "xml", + "csproj.user": "xml", + "dita": "xml", + "ditamap": "xml", + "dtml": "xml", + "ent": "xml", + "fxml": "xml", + "isml": "xml", + "jmx": "xml", + "launch": "xml", + "menu": "xml", + "opml": "xml", + "owl": "xml", + "proj": "xml", + "publishsettings": "xml", + "pubxml": "xml", + "pubxml.user": "xml", + "rdf": "xml", + "rng": "xml", + "rss": "xml", + "shproj": "xml", + "storyboard": "xml", + "targets": "xml", + "tld": "xml", + "tmx": "xml", + "vbproj": "xml", + "vbproj.user": "xml", + "wsdl": "xml", + "wxi": "xml", + "wxl": "xml", + "wxs": "xml", + "xbl": "xml", + "xib": "xml", + "xliff": "xml", + "xoml": "xml", + "xpdl": "xml", + "xul": "xml", "toml": "toml", "toon": "toon", "png": "image", @@ -9322,6 +9467,7 @@ "jbig2": "image", "jb2": "image", "jng": "image", + "jxl": "image", "jxr": "image", "pgf": "image", "pic": "image", @@ -9397,6 +9543,10 @@ "act": "palette", "esx": "javascript", "mjs": "javascript", + "js": "javascript", + "cjs": "javascript", + "es6": "javascript", + "pac": "javascript", "jsx": "react", "tsx": "react_ts", "routing.ts": "routing", @@ -9426,6 +9576,13 @@ "cfg": "settings", "cnf": "settings", "tool-versions": "settings", + "directory": "settings", + "mak": "settings", + "npmrc": "settings", + "repo": "settings", + "ts": "typescript", + "cts": "typescript", + "mts": "typescript", "d.ts": "typescript-def", "d.cts": "typescript-def", "d.mts": "typescript-def", @@ -9463,6 +9620,9 @@ "vcxproj": "visualstudio", "vcxproj.filters": "visualstudio", "wixproj": "visualstudio", + "bas": "visualstudio", + "brs": "visualstudio", + "vba": "visualstudio", "vcl": "varnish", "pdb": "database", "sql": "database", @@ -9502,6 +9662,7 @@ "ldf": "database", "frm": "database", "kdbx": "database", + "dsql": "database", "kql": "kusto", "cs": "csharp", "csx": "csharp", @@ -9565,6 +9726,7 @@ "hex": "hex", "java": "java", "jsp": "java", + "jav": "java", "jar": "jar", "class": "javaclass", "c3": "c3", @@ -9572,6 +9734,7 @@ "i": "c", "mi": "c", "h": "h", + "hip": "hip", "cc": "cpp", "cpp": "cpp", "cxx": "cpp", @@ -9580,6 +9743,15 @@ "mii": "cpp", "ii": "cpp", "cppm": "cpp", + "c++m": "cpp", + "ccm": "cpp", + "cxxm": "cpp", + "h.in": "cpp", + "hpp.in": "cpp", + "ipp": "cpp", + "ixx": "cpp", + "tpp": "cpp", + "txx": "cpp", "hh": "hpp", "hpp": "hpp", "hxx": "hpp", @@ -9587,9 +9759,19 @@ "hp": "hpp", "tcc": "hpp", "inl": "hpp", + "m": "objective-c", + "mm": "objective-cpp", "rc": "rc", "go": "go", "py": "python", + "cpy": "python", + "gyp": "python", + "gypi": "python", + "ipy": "python", + "pyi": "python", + "pyt": "python", + "pyw": "python", + "rpy": "python", "pyc": "python-misc", "whl": "python-misc", "egg": "python-misc", @@ -9607,12 +9789,34 @@ "exp": "console", "nu": "console", "xsh": "console", + "bash_aliases": "console", + "bash_login": "console", + "bash_logout": "console", + "bash_profile": "console", + "bashrc": "console", + "cshrc": "console", + "ebuild": "console", + "eclass": "console", + "profile": "console", + "tcshrc": "console", + "xprofile": "console", + "xsession": "console", + "xsessionrc": "console", + "yash_profile": "console", + "yashrc": "console", + "zlogin": "console", + "zlogout": "console", + "zprofile": "console", + "zsh-theme": "console", + "zshenv": "console", + "zshrc": "console", "ps1": "powershell", "psm1": "powershell", "psd1": "powershell", "ps1xml": "powershell", "psc1": "powershell", "pssc": "powershell", + "psrc": "powershell", "excalidraw": "excalidraw", "excalidraw.json": "excalidraw", "excalidraw.svg": "excalidraw", @@ -9654,6 +9858,9 @@ "lib": "lib", "a": "lib", "bib": "bibliography", + "bbl": "bibliography", + "bcf": "bibliography", + "blg": "bibliography", "bst": "bibtex-style", "dll": "dll", "ilk": "dll", @@ -9661,10 +9868,18 @@ "rb": "ruby", "erb": "ruby", "rbs": "ruby", + "gemspec": "ruby", + "podspec": "ruby", + "rake": "ruby", + "rbi": "ruby", + "rbx": "ruby", + "rjs": "ruby", + "ru": "ruby", "fs": "fsharp", "fsx": "fsharp", "fsi": "fsharp", "fsproj": "fsharp", + "fsscript": "fsharp", "swift": "swift", "xcplayground": "swift", "swiftdeps": "swift", @@ -9680,6 +9895,15 @@ "containerfile": "docker", "compose.yaml": "docker", "compose.yml": "docker", + "tex": "tex", + "ltx": "tex", + "cls": "tex", + "clo": "tex", + "latex": "tex", + "aux": "tex", + "tikz": "tex", + "synctex": "tex", + "synctex.gz": "tex", "sty": "sty", "ctx": "context", "dtx": "dtx", @@ -9820,13 +10044,23 @@ "mist.tsx": "mist", "otne": "otne", "patch": "git", + "diff": "diff", + "rej": "diff", "lua": "lua", "clj": "clojure", "cljs": "clojure", "cljc": "clojure", + "cljx": "clojure", + "clojure": "clojure", + "edn": "clojure", "groovy": "groovy", + "gvy": "groovy", + "nf": "groovy", "r": "r", "rmd": "r", + "rhistory": "r", + "rprofile": "r", + "rt": "r", "dart": "dart", "freezed.dart": "dart_generated", "g.dart": "dart_generated", @@ -9862,8 +10096,13 @@ "lock": "lock", "hbs": "handlebars", "mustache": "handlebars", + "handlebars": "handlebars", + "hjs": "handlebars", "pm": "perl", "raku": "perl", + "pod": "perl", + "psgi": "perl", + "t": "perl", "hx": "haxe", "spec.ts": "test-ts", "spec.cts": "test-ts", @@ -10074,6 +10313,7 @@ "prisma": "prisma", "cshtml": "razor", "vbhtml": "razor", + "razor": "razor", "abc": "abc", "ad": "asciidoc", "adoc": "asciidoc", @@ -10088,16 +10328,37 @@ "stl": "3d", "stp": "3d", "step": "3d", + "ste": "3d", "obj": "3d", "o": "3d", "ac": "3d", + "dwg": "3d", "dxf": "3d", "fbx": "3d", "mesh": "3d", + "3dm": "3d", + "3mf": "3d", + "catpart": "3d", + "catproduct": "3d", + "f3d": "3d", + "iam": "3d", + "ige": "3d", + "iges": "3d", + "igs": "3d", + "ipt": "3d", + "jt": "3d", "mqo": "3d", "pmd": "3d", "pmx": "3d", + "prt": "3d", + "sab": "3d", + "sat": "3d", "skp": "3d", + "sldasm": "3d", + "slddrw": "3d", + "sldprt": "3d", + "smb": "3d", + "smt": "3d", "vac": "3d", "vdp": "3d", "vox": "3d", @@ -10109,6 +10370,10 @@ "wrl": "3d", "usd": "3d", "usdz": "3d", + "wire": "3d", + "x_b": "3d", + "x_t": "3d", + "123dx": "3d", "svg": "svg", "ai": "adobe-illustrator", "ait": "adobe-illustrator", @@ -10230,6 +10495,11 @@ "fen": "chess", "gmi": "gemini", "gemini": "gemini", + "php": "php", + "php4": "php", + "php5": "php", + "phtml": "php", + "ctp": "php", "tsconfig.json": "tsconfig", "tauri": "tauri", "jsconfig.json": "jsconfig", @@ -10297,6 +10567,13 @@ "wgsl": "shader", "spv": "shader", "slang": "shader", + "cginc": "shader", + "compute": "shader", + "fx": "shader", + "fxh": "shader", + "hlsli": "shader", + "psh": "shader", + "vsh": "shader", "sy": "siyuan", "ndst.yml": "ndst", "ndst.yaml": "ndst", @@ -10409,65 +10686,22 @@ "lean": "lean", "sls": "salt", "m2": "macaulay2", + "ua": "uiua", "skill.md": "skill", "skills.md": "skill", "instructions.md": "instructions", "instruction.md": "instructions", - "cljx": "clojure", - "clojure": "clojure", - "edn": "clojure", - "ccm": "cpp", - "cxxm": "cpp", - "c++m": "cpp", - "ipp": "cpp", - "ixx": "cpp", - "tpp": "cpp", - "txx": "cpp", - "hpp.in": "cpp", - "h.in": "cpp", - "diff": "diff", - "rej": "diff", - "fsscript": "fsharp", + "mrpack": "mrpack", "gitignore_global": "ignore", "gitignore": "ignore", "git-blame-ignore-revs": "ignore", - "gvy": "groovy", - "nf": "groovy", - "handlebars": "handlebars", - "hjs": "handlebars", - "hlsli": "hlsl", - "fx": "hlsl", - "fxh": "hlsl", - "vsh": "hlsl", - "psh": "hlsl", - "cginc": "hlsl", - "compute": "hlsl", - "html": "html", - "shtml": "html", - "xht": "html", - "aspx": "html", - "jshtm": "html", - "volt": "html", - "rhtml": "html", - "directory": "properties", "gitattributes": "properties", "gitconfig": "properties", "gitmodules": "properties", "editorconfig": "properties", - "repo": "properties", - "jav": "java", - "js": "javascript", - "es6": "javascript", - "cjs": "javascript", - "pac": "javascript", "bowerrc": "json", "jscsrc": "json", - "webmanifest": "json", - "ts.map": "json", - "har": "json", "jslintrc": "json", - "jsonld": "json", - "geojson": "json", "vuerc": "json", "eslintrc": "jsonc", "eslintrc.json": "jsonc", @@ -10476,134 +10710,14 @@ "hintrc": "jsonc", "babelrc": "jsonc", "jmd": "juliamarkdown", - "cls": "tex", - "tex": "latex", - "ltx": "latex", - "mak": "makefile", - "mkd": "markdown", - "mdwn": "markdown", - "mdown": "markdown", - "markdn": "markdown", - "mdtxt": "markdown", - "mdtext": "markdown", - "workbook": "markdown", "npmignore": "ignore", - "npmrc": "properties", - "m": "objective-c", - "mm": "objective-cpp", - "pod": "perl", - "t": "perl", - "psgi": "perl", "rakumod": "raku", "rakutest": "raku", "rakudoc": "raku", "nqp": "raku", "p6": "raku", "pl6": "raku", - "pm6": "raku", - "php": "php", - "php4": "php", - "php5": "php", - "phtml": "php", - "ctp": "php", - "psrc": "powershell", - "rpy": "python", - "pyw": "python", - "cpy": "python", - "gyp": "python", - "gypi": "python", - "pyi": "python", - "ipy": "python", - "pyt": "python", - "rhistory": "r", - "rprofile": "r", - "rt": "r", - "razor": "razor", - "rbx": "ruby", - "rjs": "ruby", - "gemspec": "ruby", - "rake": "ruby", - "ru": "ruby", - "podspec": "ruby", - "rbi": "ruby", - "bashrc": "shellscript", - "bash_aliases": "shellscript", - "bash_profile": "shellscript", - "bash_login": "shellscript", - "ebuild": "shellscript", - "eclass": "shellscript", - "profile": "shellscript", - "bash_logout": "shellscript", - "xprofile": "shellscript", - "xsession": "shellscript", - "xsessionrc": "shellscript", - "zshrc": "shellscript", - "zprofile": "shellscript", - "zlogin": "shellscript", - "zlogout": "shellscript", - "zshenv": "shellscript", - "zsh-theme": "shellscript", - "cshrc": "shellscript", - "tcshrc": "shellscript", - "yashrc": "shellscript", - "yash_profile": "shellscript", - "dsql": "sql", - "ts": "typescript", - "cts": "typescript", - "mts": "typescript", - "brs": "vb", - "bas": "vb", - "vba": "vb", - "ascx": "xml", - "atom": "xml", - "axml": "xml", - "axaml": "xml", - "bpmn": "xml", - "csl": "xml", - "csproj.user": "xml", - "dita": "xml", - "ditamap": "xml", - "ent": "xml", - "dtml": "xml", - "fxml": "xml", - "isml": "xml", - "jmx": "xml", - "launch": "xml", - "menu": "xml", - "opml": "xml", - "owl": "xml", - "proj": "xml", - "publishsettings": "xml", - "pubxml": "xml", - "pubxml.user": "xml", - "rdf": "xml", - "rng": "xml", - "rss": "xml", - "shproj": "xml", - "storyboard": "xml", - "targets": "xml", - "tld": "xml", - "tmx": "xml", - "vbproj": "xml", - "vbproj.user": "xml", - "wsdl": "xml", - "wxi": "xml", - "wxl": "xml", - "wxs": "xml", - "xbl": "xml", - "xib": "xml", - "xliff": "xml", - "xpdl": "xml", - "xul": "xml", - "xoml": "xml", - "yaml": "yaml", - "yml": "yaml", - "eyaml": "yaml", - "eyml": "yaml", - "cff": "yaml", - "yaml-tmpreferences": "yaml", - "yaml-tmtheme": "yaml", - "winget": "yaml" + "pm6": "raku" }, "fileNames": { ".pug-lintrc": "pug", @@ -10637,6 +10751,7 @@ "playwright-ct.config.cts": "playwright", "playwright-ct.config.mts": "playwright", ".htaccess": "xml", + "jakefile": "javascript", ".release-it.json": "rocket", ".release-it.ts": "rocket", ".release-it.js": "rocket", @@ -10713,6 +10828,17 @@ "pre-commit": "console", "pre-push": "console", "post-merge": "console", + ".envrc": "console", + ".hushlogin": "console", + "apkbuild": "console", + "pkgbuild": "console", + "bashrc_apple_terminal": "console", + "zlogin": "console", + "zlogout": "console", + "zprofile": "console", + "zshenv": "console", + "zshrc": "console", + "zshrc_apple_terminal": "console", "excalidraw": "excalidraw", "excalidraw.json": "excalidraw", "excalidraw.svg": "excalidraw", @@ -10759,6 +10885,25 @@ "keystatic.config.jsx": "keystatic", "keystatic.config.js": "keystatic", ".ruby-version": "ruby", + "appraisals": "ruby", + "berksfile": "ruby", + "berksfile.lock": "ruby", + "brewfile": "ruby", + "capfile": "ruby", + "cheffile": "ruby", + "dangerfile": "ruby", + "deliverfile": "ruby", + "guardfile": "ruby", + "gymfile": "ruby", + "hobofile": "ruby", + "matchfile": "ruby", + "podfile": "ruby", + "puppetfile": "ruby", + "rakefile": "ruby", + "rantfile": "ruby", + "scanfile": "ruby", + "snapfile": "ruby", + "thorfile": "ruby", "gemfile": "gemfile", ".rubocop.yml": "rubocop", ".rubocop-todo.yml": "rubocop", @@ -10927,6 +11072,9 @@ ".git-blame-ignore-revs": "git", ".git-for-windows-updater": "git", "git-history": "git", + "commit_editmsg": "git", + "merge_msg": "git", + "git-rebase-todo": "git", ".luacheckrc": "lua", ".rhistory": "r", ".pubignore": "dart", @@ -10936,10 +11084,13 @@ "semgrep.yml": "semgrep", ".semgrepignore": "semgrep", "vue.config.js": "vue-config", + "vue.config.cjs": "vue-config", + "vue.config.mjs": "vue-config", "vue.config.ts": "vue-config", "vetur.config.js": "vue-config", "vetur.config.ts": "vue-config", "volar.config.js": "vue-config", + ".vuerc": "vue-config", "nuxt.config.js": "nuxt", "nuxt.config.ts": "nuxt", ".nuxtignore": "nuxt", @@ -10955,6 +11106,7 @@ "ng-package.json": "angular", ".mjmlconfig": "mjml", "vercel.json": "vercel", + "vercel.ts": "vercel", ".vercelignore": "vercel", "now.json": "vercel", ".nowignore": "vercel", @@ -11715,6 +11867,8 @@ ".browserslistrc": "browserlist", ".snyk": "snyk", ".drone.yml": "drone", + "opencode.json": "opencode", + "opencode.jsonc": "opencode", ".sequelizerc": "sequelize", "gatsby-config.js": "gatsby", "gatsby-config.mjs": "gatsby", @@ -12585,6 +12739,7 @@ "zeabur.yml": "zeabur", "zeabur.toml": "zeabur", "copilot-instructions.md": "copilot", + ".copilotignore": "copilot", ".pre-commit-config.yaml": "pre-commit", ".pre-commit-hooks.yaml": "pre-commit", ".lintstagedrc": "lintstaged", @@ -12666,6 +12821,9 @@ "metro.config.cjs": "metro", "metro.config.mjs": "metro", "metro.config.json": "metro", + "metro.config.ts": "metro", + "metro.config.cts": "metro", + "metro.config.mts": "metro", "src/bashly.yaml": "bashly", "src/bashly.yml": "bashly", "bashly-settings.yaml": "bashly-settings", @@ -12680,6 +12838,14 @@ "skill.md": "skill", "instructions.md": "instructions", "instruction.md": "instructions", + "tsdown.config.ts": "tsdown", + "tsdown.config.mts": "tsdown", + "tsdown.config.cts": "tsdown", + "tsdown.config.js": "tsdown", + "tsdown.config.mjs": "tsdown", + "tsdown.config.cjs": "tsdown", + "tsdown.config.json": "tsdown", + "tsdown.config": "tsdown", "appwrite.json": "appwrite", "appwrite.js": "appwrite", "appwrite.ts": "appwrite", @@ -12934,6 +13100,7 @@ ".rubocop-todo.yml": "rubocop_light", ".rubocop_todo.yml": "rubocop_light", "vercel.json": "vercel_light", + "vercel.ts": "vercel_light", ".vercelignore": "vercel_light", "now.json": "vercel_light", ".nowignore": "vercel_light", @@ -13001,6 +13168,8 @@ "browserslist": "browserlist_light", ".browserslistrc": "browserlist_light", ".drone.yml": "drone_light", + "opencode.json": "opencode_light", + "opencode.jsonc": "opencode_light", ".wakatime-project": "wakatime_light", "circle.yml": "circleci_light", ".releaserc": "semantic-release_light", @@ -13086,6 +13255,7 @@ "zeabur.yml": "zeabur_light", "zeabur.toml": "zeabur_light", "copilot-instructions.md": "copilot_light", + ".copilotignore": "copilot_light", "hosts": "hosts_light", ".cursorignore": "cursor_light", ".cursorindexingignore": "cursor_light", @@ -13129,11 +13299,11 @@ "_idea": "folder-intellij_light", "-idea": "folder-intellij_light", "__idea__": "folder-intellij_light", + "cursor": "folder-cursor_light", ".cursor": "folder-cursor_light", - "..cursor": "folder-cursor_light", - "_.cursor": "folder-cursor_light", - "-.cursor": "folder-cursor_light", - "__.cursor__": "folder-cursor_light" + "_cursor": "folder-cursor_light", + "-cursor": "folder-cursor_light", + "__cursor__": "folder-cursor_light" }, "folderNamesExpanded": { "jinja": "folder-jinja-open_light", @@ -13156,11 +13326,11 @@ "_idea": "folder-intellij-open_light", "-idea": "folder-intellij-open_light", "__idea__": "folder-intellij-open_light", + "cursor": "folder-cursor-open_light", ".cursor": "folder-cursor-open_light", - "..cursor": "folder-cursor-open_light", - "_.cursor": "folder-cursor-open_light", - "-.cursor": "folder-cursor-open_light", - "__.cursor__": "folder-cursor-open_light" + "_cursor": "folder-cursor-open_light", + "-cursor": "folder-cursor-open_light", + "__cursor__": "folder-cursor-open_light" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {} diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json index 5c673ad3d2..a4109d9fa6 100644 --- a/options/fileicon/material-icon-svgs.json +++ b/options/fileicon/material-icon-svgs.json @@ -196,577 +196,583 @@ "firebase": "", "flash": "", "flow": "", - "folder-admin-open": "", - "folder-admin": "", - "folder-android-open": "", - "folder-android": "", - "folder-angular-open": "", - "folder-angular": "", - "folder-animation-open": "", - "folder-animation": "", - "folder-ansible-open": "", - "folder-ansible": "", - "folder-api-open": "", - "folder-api": "", - "folder-apollo-open": "", - "folder-apollo": "", - "folder-app-open": "", - "folder-app": "", + "folder-admin-open": "", + "folder-admin": "", + "folder-android-open": "", + "folder-android": "", + "folder-angular-open": "", + "folder-angular": "", + "folder-animation-open": "", + "folder-animation": "", + "folder-ansible-open": "", + "folder-ansible": "", + "folder-api-open": "", + "folder-api": "", + "folder-apollo-open": "", + "folder-apollo": "", + "folder-app-open": "", + "folder-app": "", "folder-appwrite-open": "", "folder-appwrite": "", - "folder-archive-open": "", - "folder-archive": "", + "folder-archive-open": "", + "folder-archive": "", "folder-assembly-open": "", "folder-assembly": "", - "folder-astro-open": "", - "folder-astro": "", - "folder-atom-open": "", - "folder-atom": "", - "folder-attachment-open": "", - "folder-attachment": "", - "folder-audio-open": "", - "folder-audio": "", - "folder-aurelia-open": "", - "folder-aurelia": "", - "folder-aws-open": "", - "folder-aws": "", - "folder-azure-pipelines-open": "", - "folder-azure-pipelines": "", - "folder-backup-open": "", - "folder-backup": "", - "folder-base-open": "", - "folder-base": "", - "folder-batch-open": "", - "folder-batch": "", - "folder-benchmark-open": "", - "folder-benchmark": "", - "folder-bibliography-open": "", - "folder-bibliography": "", - "folder-bicep-open": "", - "folder-bicep": "", - "folder-blender-open": "", - "folder-blender": "", - "folder-bloc-open": "", - "folder-bloc": "", - "folder-bower-open": "", - "folder-bower": "", - "folder-buildkite-open": "", - "folder-buildkite": "", - "folder-cart-open": "", - "folder-cart": "", - "folder-changesets-open": "", - "folder-changesets": "", - "folder-ci-open": "", - "folder-ci": "", - "folder-circleci-open": "", - "folder-circleci": "", - "folder-class-open": "", - "folder-class": "", - "folder-claude-open": "", - "folder-claude": "", - "folder-client-open": "", - "folder-client": "", - "folder-cline-open": "", - "folder-cline": "", - "folder-cloud-functions-open": "", - "folder-cloud-functions": "", - "folder-cloudflare-open": "", - "folder-cloudflare": "", - "folder-cluster-open": "", - "folder-cluster": "", - "folder-cobol-open": "", - "folder-cobol": "", - "folder-command-open": "", - "folder-command": "", - "folder-components-open": "", - "folder-components": "", - "folder-config-open": "", - "folder-config": "", - "folder-connection-open": "", - "folder-connection": "", - "folder-console-open": "", - "folder-console": "", - "folder-constant-open": "", - "folder-constant": "", - "folder-container-open": "", - "folder-container": "", - "folder-content-open": "", - "folder-content": "", - "folder-context-open": "", - "folder-context": "", - "folder-contract-open": "", - "folder-contract": "", - "folder-controller-open": "", - "folder-controller": "", - "folder-core-open": "", - "folder-core": "", - "folder-coverage-open": "", - "folder-coverage": "", - "folder-css-open": "", - "folder-css": "", - "folder-cue-open": "", - "folder-cue": "", - "folder-cursor-open": "", - "folder-cursor-open_light": "", - "folder-cursor": "", - "folder-cursor_light": "", - "folder-custom-open": "", - "folder-custom": "", - "folder-cypress-open": "", - "folder-cypress": "", - "folder-dal-open": "", - "folder-dal": "", - "folder-dart-open": "", - "folder-dart": "", - "folder-database-open": "", - "folder-database": "", - "folder-debug-open": "", - "folder-debug": "", - "folder-decorators-open": "", - "folder-decorators": "", - "folder-delta-open": "", - "folder-delta": "", - "folder-deprecated-open.clone": "", - "folder-deprecated.clone": "", - "folder-desktop-open": "", - "folder-desktop": "", - "folder-development-open.clone": "", - "folder-development.clone": "", - "folder-directive-open": "", - "folder-directive": "", - "folder-dist-open": "", - "folder-dist": "", - "folder-docker-open": "", - "folder-docker": "", - "folder-docs-open": "", - "folder-docs": "", - "folder-download-open": "", - "folder-download": "", - "folder-drizzle-open": "", - "folder-drizzle": "", - "folder-dump-open": "", - "folder-dump": "", - "folder-eas-open.clone": "", - "folder-eas.clone": "", - "folder-element-open": "", - "folder-element": "", - "folder-enum-open": "", - "folder-enum": "", - "folder-environment-open": "", - "folder-environment": "", - "folder-error-open": "", - "folder-error": "", - "folder-eslint-open": "", - "folder-eslint": "", - "folder-event-open": "", - "folder-event": "", - "folder-examples-open": "", - "folder-examples": "", - "folder-expo-open": "", - "folder-expo": "", - "folder-export-open": "", - "folder-export": "", - "folder-fastlane-open": "", - "folder-fastlane": "", - "folder-favicon-open": "", - "folder-favicon": "", - "folder-features-open": "", - "folder-features": "", + "folder-astro-open": "", + "folder-astro": "", + "folder-atom-open": "", + "folder-atom": "", + "folder-attachment-open": "", + "folder-attachment": "", + "folder-audio-open": "", + "folder-audio": "", + "folder-aurelia-open": "", + "folder-aurelia": "", + "folder-aws-open": "", + "folder-aws": "", + "folder-azure-pipelines-open": "", + "folder-azure-pipelines": "", + "folder-backup-open": "", + "folder-backup": "", + "folder-base-open": "", + "folder-base": "", + "folder-batch-open": "", + "folder-batch": "", + "folder-benchmark-open": "", + "folder-benchmark": "", + "folder-bibliography-open": "", + "folder-bibliography": "", + "folder-bicep-open": "", + "folder-bicep": "", + "folder-blender-open": "", + "folder-blender": "", + "folder-bloc-open": "", + "folder-bloc": "", + "folder-bower-open": "", + "folder-bower": "", + "folder-buildkite-open": "", + "folder-buildkite": "", + "folder-cart-open": "", + "folder-cart": "", + "folder-changesets-open": "", + "folder-changesets": "", + "folder-ci-open": "", + "folder-ci": "", + "folder-circleci-open": "", + "folder-circleci": "", + "folder-class-open": "", + "folder-class": "", + "folder-claude-open": "", + "folder-claude": "", + "folder-client-open": "", + "folder-client": "", + "folder-cline-open": "", + "folder-cline": "", + "folder-cloud-functions-open": "", + "folder-cloud-functions": "", + "folder-cloudflare-open": "", + "folder-cloudflare": "", + "folder-cluster-open": "", + "folder-cluster": "", + "folder-cobol-open": "", + "folder-cobol": "", + "folder-command-open": "", + "folder-command": "", + "folder-components-open": "", + "folder-components": "", + "folder-config-open": "", + "folder-config": "", + "folder-connection-open": "", + "folder-connection": "", + "folder-console-open": "", + "folder-console": "", + "folder-constant-open": "", + "folder-constant": "", + "folder-container-open": "", + "folder-container": "", + "folder-content-open": "", + "folder-content": "", + "folder-context-open": "", + "folder-context": "", + "folder-contract-open": "", + "folder-contract": "", + "folder-controller-open": "", + "folder-controller": "", + "folder-core-open": "", + "folder-core": "", + "folder-coverage-open": "", + "folder-coverage": "", + "folder-css-open": "", + "folder-css": "", + "folder-cue-open": "", + "folder-cue": "", + "folder-cursor-open": "", + "folder-cursor-open_light": "", + "folder-cursor": "", + "folder-cursor_light": "", + "folder-custom-open": "", + "folder-custom": "", + "folder-cypress-open": "", + "folder-cypress": "", + "folder-dal-open": "", + "folder-dal": "", + "folder-dart-open": "", + "folder-dart": "", + "folder-database-open": "", + "folder-database": "", + "folder-debug-open": "", + "folder-debug": "", + "folder-decorators-open": "", + "folder-decorators": "", + "folder-delta-open": "", + "folder-delta": "", + "folder-deprecated-open.clone": "", + "folder-deprecated.clone": "", + "folder-desktop-open": "", + "folder-desktop": "", + "folder-development-open.clone": "", + "folder-development.clone": "", + "folder-directive-open": "", + "folder-directive": "", + "folder-dist-open": "", + "folder-dist": "", + "folder-docker-open": "", + "folder-docker": "", + "folder-docs-open": "", + "folder-docs": "", + "folder-download-open": "", + "folder-download": "", + "folder-drizzle-open": "", + "folder-drizzle": "", + "folder-dump-open": "", + "folder-dump": "", + "folder-eas-open.clone": "", + "folder-eas.clone": "", + "folder-element-open": "", + "folder-element": "", + "folder-enum-open": "", + "folder-enum": "", + "folder-environment-open": "", + "folder-environment": "", + "folder-error-open": "", + "folder-error": "", + "folder-eslint-open": "", + "folder-eslint": "", + "folder-event-open": "", + "folder-event": "", + "folder-examples-open": "", + "folder-examples": "", + "folder-expo-open": "", + "folder-expo": "", + "folder-export-open": "", + "folder-export": "", + "folder-fastlane-open": "", + "folder-fastlane": "", + "folder-favicon-open": "", + "folder-favicon": "", + "folder-features-open": "", + "folder-features": "", "folder-filter-open": "", "folder-filter": "", - "folder-firebase-open": "", - "folder-firebase": "", - "folder-firestore-open": "", - "folder-firestore": "", - "folder-flow-open": "", - "folder-flow": "", - "folder-flutter-open": "", - "folder-flutter": "", - "folder-font-open": "", - "folder-font": "", - "folder-forgejo-open": "", - "folder-forgejo": "", + "folder-firebase-open": "", + "folder-firebase": "", + "folder-firestore-open": "", + "folder-firestore": "", + "folder-flow-open": "", + "folder-flow": "", + "folder-flutter-open": "", + "folder-flutter": "", + "folder-font-open": "", + "folder-font": "", + "folder-forgejo-open": "", + "folder-forgejo": "", "folder-form-open": "", "folder-form": "", - "folder-functions-open": "", - "folder-functions": "", - "folder-gamemaker-open": "", - "folder-gamemaker": "", - "folder-gemini-ai-open": "", - "folder-gemini-ai": "", - "folder-generator-open": "", - "folder-generator": "", - "folder-gh-workflows-open": "", - "folder-gh-workflows": "", - "folder-git-open": "", - "folder-git": "", - "folder-gitea-open": "", - "folder-gitea-workflows-open.clone": "", - "folder-gitea-workflows.clone": "", - "folder-gitea": "", - "folder-github-open": "", - "folder-github": "", - "folder-gitlab-open": "", - "folder-gitlab": "", - "folder-global-open": "", - "folder-global": "", + "folder-functions-open": "", + "folder-functions": "", + "folder-gamemaker-open": "", + "folder-gamemaker": "", + "folder-gemini-ai-open": "", + "folder-gemini-ai": "", + "folder-generator-open": "", + "folder-generator": "", + "folder-gh-workflows-open": "", + "folder-gh-workflows": "", + "folder-git-open": "", + "folder-git": "", + "folder-gitea-open": "", + "folder-gitea-workflows-open.clone": "", + "folder-gitea-workflows.clone": "", + "folder-gitea": "", + "folder-github-open": "", + "folder-github": "", + "folder-gitlab-open": "", + "folder-gitlab": "", + "folder-global-open": "", + "folder-global": "", "folder-go-open": "", "folder-go": "", - "folder-godot-open": "", - "folder-godot": "", - "folder-gradle-open": "", - "folder-gradle": "", - "folder-graphql-open": "", - "folder-graphql": "", - "folder-guard-open": "", - "folder-guard": "", - "folder-gulp-open": "", - "folder-gulp": "", - "folder-helm-open": "", - "folder-helm": "", - "folder-helper-open": "", - "folder-helper": "", - "folder-home-open": "", - "folder-home": "", - "folder-hook-open": "", - "folder-hook": "", - "folder-husky-open": "", - "folder-husky": "", - "folder-i18n-open": "", - "folder-i18n": "", - "folder-images-open": "", - "folder-images": "", - "folder-import-open": "", - "folder-import": "", - "folder-include-open": "", - "folder-include": "", - "folder-input-open": "", - "folder-input": "", - "folder-instructions-open.clone": "", - "folder-instructions.clone": "", - "folder-intellij-open": "", - "folder-intellij-open_light": "", - "folder-intellij": "", - "folder-intellij_light": "", - "folder-interceptor-open": "", - "folder-interceptor": "", - "folder-interface-open": "", - "folder-interface": "", - "folder-ios-open": "", - "folder-ios": "", - "folder-java-open": "", - "folder-java": "", - "folder-javascript-open": "", - "folder-javascript": "", - "folder-jinja-open": "", - "folder-jinja-open_light": "", - "folder-jinja": "", - "folder-jinja_light": "", - "folder-job-open": "", - "folder-job": "", - "folder-json-open": "", - "folder-json": "", - "folder-jupyter-open": "", - "folder-jupyter": "", - "folder-keys-open": "", - "folder-keys": "", + "folder-godot-open": "", + "folder-godot": "", + "folder-gradle-open": "", + "folder-gradle": "", + "folder-graphql-open": "", + "folder-graphql": "", + "folder-guard-open": "", + "folder-guard": "", + "folder-gulp-open": "", + "folder-gulp": "", + "folder-helm-open": "", + "folder-helm": "", + "folder-helper-open": "", + "folder-helper": "", + "folder-home-open": "", + "folder-home": "", + "folder-hook-open": "", + "folder-hook": "", + "folder-husky-open": "", + "folder-husky": "", + "folder-i18n-open": "", + "folder-i18n": "", + "folder-images-open": "", + "folder-images": "", + "folder-import-open": "", + "folder-import": "", + "folder-include-open": "", + "folder-include": "", + "folder-input-open": "", + "folder-input": "", + "folder-instructions-open.clone": "", + "folder-instructions.clone": "", + "folder-intellij-open": "", + "folder-intellij-open_light": "", + "folder-intellij": "", + "folder-intellij_light": "", + "folder-interceptor-open": "", + "folder-interceptor": "", + "folder-interface-open": "", + "folder-interface": "", + "folder-ios-open": "", + "folder-ios": "", + "folder-java-open": "", + "folder-java": "", + "folder-javascript-open": "", + "folder-javascript": "", + "folder-jinja-open": "", + "folder-jinja-open_light": "", + "folder-jinja": "", + "folder-jinja_light": "", + "folder-job-open": "", + "folder-job": "", + "folder-json-open": "", + "folder-json": "", + "folder-jupyter-open": "", + "folder-jupyter": "", + "folder-keys-open": "", + "folder-keys": "", "folder-kotlin-open": "", "folder-kotlin": "", - "folder-kubernetes-open": "", - "folder-kubernetes": "", - "folder-kusto-open": "", - "folder-kusto": "", - "folder-layout-open": "", - "folder-layout": "", - "folder-lefthook-open": "", - "folder-lefthook": "", - "folder-less-open": "", - "folder-less": "", - "folder-lib-open": "", - "folder-lib": "", - "folder-license-open": "", - "folder-license": "", - "folder-link-open": "", - "folder-link": "", - "folder-linux-open": "", - "folder-linux": "", - "folder-liquibase-open": "", - "folder-liquibase": "", - "folder-log-open": "", - "folder-log": "", - "folder-lottie-open": "", - "folder-lottie": "", - "folder-lua-open": "", - "folder-lua": "", - "folder-luau-open": "", - "folder-luau": "", - "folder-macos-open": "", - "folder-macos": "", - "folder-mail-open": "", - "folder-mail": "", - "folder-mappings-open": "", - "folder-mappings": "", - "folder-markdown-open": "", - "folder-markdown": "", - "folder-mercurial-open": "", - "folder-mercurial": "", - "folder-messages-open": "", - "folder-messages": "", - "folder-meta-open": "", - "folder-meta": "", - "folder-metro-open": "", - "folder-metro": "", - "folder-middleware-open": "", - "folder-middleware": "", - "folder-migrations-open": "", - "folder-migrations": "", - "folder-mjml-open": "", - "folder-mjml": "", - "folder-mobile-open": "", - "folder-mobile": "", - "folder-mock-open": "", - "folder-mock": "", - "folder-mojo-open": "", - "folder-mojo": "", - "folder-molecule-open": "", - "folder-molecule": "", - "folder-moon-open": "", - "folder-moon": "", - "folder-netlify-open": "", - "folder-netlify": "", - "folder-next-open": "", - "folder-next": "", - "folder-nginx-open": "", - "folder-nginx": "", - "folder-ngrx-actions-open.clone": "", - "folder-ngrx-actions.clone": "", - "folder-ngrx-effects-open.clone": "", - "folder-ngrx-effects.clone": "", - "folder-ngrx-entities-open.clone": "", - "folder-ngrx-entities.clone": "", - "folder-ngrx-reducer-open.clone": "", - "folder-ngrx-reducer.clone": "", - "folder-ngrx-selectors-open.clone": "", - "folder-ngrx-selectors.clone": "", - "folder-ngrx-state-open.clone": "", - "folder-ngrx-state.clone": "", - "folder-ngrx-store-open": "", - "folder-ngrx-store": "", - "folder-node-open": "", - "folder-node": "", - "folder-nuxt-open": "", - "folder-nuxt": "", - "folder-obsidian-open": "", - "folder-obsidian": "", + "folder-kubernetes-open": "", + "folder-kubernetes": "", + "folder-kusto-open": "", + "folder-kusto": "", + "folder-layout-open": "", + "folder-layout": "", + "folder-lefthook-open": "", + "folder-lefthook": "", + "folder-less-open": "", + "folder-less": "", + "folder-lib-open": "", + "folder-lib": "", + "folder-license-open": "", + "folder-license": "", + "folder-link-open": "", + "folder-link": "", + "folder-linux-open": "", + "folder-linux": "", + "folder-liquibase-open": "", + "folder-liquibase": "", + "folder-log-open": "", + "folder-log": "", + "folder-lottie-open": "", + "folder-lottie": "", + "folder-lua-open": "", + "folder-lua": "", + "folder-luau-open": "", + "folder-luau": "", + "folder-macos-open": "", + "folder-macos": "", + "folder-mail-open": "", + "folder-mail": "", + "folder-mappings-open": "", + "folder-mappings": "", + "folder-markdown-open": "", + "folder-markdown": "", + "folder-mercurial-open": "", + "folder-mercurial": "", + "folder-messages-open": "", + "folder-messages": "", + "folder-meta-open": "", + "folder-meta": "", + "folder-metro-open": "", + "folder-metro": "", + "folder-middleware-open": "", + "folder-middleware": "", + "folder-migrations-open": "", + "folder-migrations": "", + "folder-mjml-open": "", + "folder-mjml": "", + "folder-mobile-open": "", + "folder-mobile": "", + "folder-mock-open": "", + "folder-mock": "", + "folder-mojo-open": "", + "folder-mojo": "", + "folder-molecule-open": "", + "folder-molecule": "", + "folder-moon-open": "", + "folder-moon": "", + "folder-netlify-open": "", + "folder-netlify": "", + "folder-next-open": "", + "folder-next": "", + "folder-nginx-open": "", + "folder-nginx": "", + "folder-ngrx-actions-open.clone": "", + "folder-ngrx-actions.clone": "", + "folder-ngrx-effects-open.clone": "", + "folder-ngrx-effects.clone": "", + "folder-ngrx-entities-open.clone": "", + "folder-ngrx-entities.clone": "", + "folder-ngrx-reducer-open.clone": "", + "folder-ngrx-reducer.clone": "", + "folder-ngrx-selectors-open.clone": "", + "folder-ngrx-selectors.clone": "", + "folder-ngrx-state-open.clone": "", + "folder-ngrx-state.clone": "", + "folder-ngrx-store-open": "", + "folder-ngrx-store": "", + "folder-node-open": "", + "folder-node": "", + "folder-nuxt-open": "", + "folder-nuxt": "", + "folder-obsidian-open": "", + "folder-obsidian": "", "folder-open": "", - "folder-organism-open": "", - "folder-organism": "", - "folder-other-open": "", - "folder-other": "", - "folder-packages-open": "", - "folder-packages": "", - "folder-pdf-open": "", - "folder-pdf": "", - "folder-pdm-open": "", - "folder-pdm": "", - "folder-php-open": "", - "folder-php": "", - "folder-phpmailer-open": "", - "folder-phpmailer": "", - "folder-pipe-open": "", - "folder-pipe": "", - "folder-plastic-open": "", - "folder-plastic": "", - "folder-plugin-open": "", - "folder-plugin": "", - "folder-policy-open": "", - "folder-policy": "", - "folder-postman-open": "", - "folder-postman": "", - "folder-powershell-open": "", - "folder-powershell": "", - "folder-prisma-open": "", - "folder-prisma": "", - "folder-private-open": "", - "folder-private": "", - "folder-project-open": "", - "folder-project": "", - "folder-prompts-open": "", - "folder-prompts": "", - "folder-proto-open": "", - "folder-proto": "", - "folder-public-open": "", - "folder-public": "", - "folder-python-open": "", - "folder-python": "", - "folder-pytorch-open": "", - "folder-pytorch": "", - "folder-quasar-open": "", - "folder-quasar": "", - "folder-queue-open": "", - "folder-queue": "", - "folder-r-open": "", - "folder-r": "", - "folder-react-components-open": "", + "folder-opencode-open": "", + "folder-opencode": "", + "folder-organism-open": "", + "folder-organism": "", + "folder-other-open": "", + "folder-other": "", + "folder-packages-open": "", + "folder-packages": "", + "folder-pdf-open": "", + "folder-pdf": "", + "folder-pdm-open": "", + "folder-pdm": "", + "folder-php-open": "", + "folder-php": "", + "folder-phpmailer-open": "", + "folder-phpmailer": "", + "folder-pipe-open": "", + "folder-pipe": "", + "folder-plastic-open": "", + "folder-plastic": "", + "folder-plugin-open": "", + "folder-plugin": "", + "folder-policy-open": "", + "folder-policy": "", + "folder-postman-open": "", + "folder-postman": "", + "folder-powershell-open": "", + "folder-powershell": "", + "folder-prisma-open": "", + "folder-prisma": "", + "folder-private-open": "", + "folder-private": "", + "folder-project-open": "", + "folder-project": "", + "folder-prompts-open": "", + "folder-prompts": "", + "folder-proto-open": "", + "folder-proto": "", + "folder-public-open": "", + "folder-public": "", + "folder-python-open": "", + "folder-python": "", + "folder-pytorch-open": "", + "folder-pytorch": "", + "folder-quasar-open": "", + "folder-quasar": "", + "folder-queue-open": "", + "folder-queue": "", + "folder-r-open": "", + "folder-r": "", + "folder-react-components-open": "", "folder-react-components": "", - "folder-redux-actions-open.clone": "", - "folder-redux-actions.clone": "", - "folder-redux-reducer-open": "", - "folder-redux-reducer": "", - "folder-redux-selector-open.clone": "", - "folder-redux-selector.clone": "", - "folder-redux-store-open.clone": "", - "folder-redux-store.clone": "", - "folder-redux-toolkit-open.clone": "", - "folder-redux-toolkit.clone": "", - "folder-repository-open": "", - "folder-repository": "", - "folder-resolver-open": "", - "folder-resolver": "", - "folder-resource-open": "", - "folder-resource": "", - "folder-review-open": "", - "folder-review": "", - "folder-robot-open": "", - "folder-robot": "", + "folder-redis-open.clone": "", + "folder-redis.clone": "", + "folder-redux-actions-open.clone": "", + "folder-redux-actions.clone": "", + "folder-redux-reducer-open": "", + "folder-redux-reducer": "", + "folder-redux-selector-open.clone": "", + "folder-redux-selector.clone": "", + "folder-redux-store-open.clone": "", + "folder-redux-store.clone": "", + "folder-redux-toolkit-open.clone": "", + "folder-redux-toolkit.clone": "", + "folder-repository-open": "", + "folder-repository": "", + "folder-resolver-open": "", + "folder-resolver": "", + "folder-resource-open": "", + "folder-resource": "", + "folder-review-open": "", + "folder-review": "", + "folder-robot-open": "", + "folder-robot": "", "folder-root-open": "", "folder-root": "", - "folder-routes-open": "", - "folder-routes": "", - "folder-rules-open": "", - "folder-rules": "", - "folder-rust-open": "", - "folder-rust": "", - "folder-salt-open": "", - "folder-salt": "", - "folder-sandbox-open": "", - "folder-sandbox": "", - "folder-sass-open": "", - "folder-sass": "", - "folder-scala-open": "", - "folder-scala": "", - "folder-scons-open": "", - "folder-scons": "", - "folder-scrap-open.clone": "", - "folder-scrap.clone": "", - "folder-scripts-open": "", - "folder-scripts": "", - "folder-secure-open": "", - "folder-secure": "", - "folder-seeders-open": "", - "folder-seeders": "", - "folder-server-open": "", - "folder-server": "", - "folder-serverless-open": "", - "folder-serverless": "", - "folder-shader-open": "", - "folder-shader": "", - "folder-shared-open": "", - "folder-shared": "", - "folder-simulations-open": "", - "folder-simulations": "", - "folder-skills-open": "", - "folder-skills": "", - "folder-snapcraft-open": "", + "folder-routes-open": "", + "folder-routes": "", + "folder-rules-open": "", + "folder-rules": "", + "folder-rust-open": "", + "folder-rust": "", + "folder-salt-open": "", + "folder-salt": "", + "folder-sandbox-open": "", + "folder-sandbox": "", + "folder-sass-open": "", + "folder-sass": "", + "folder-scala-open": "", + "folder-scala": "", + "folder-scons-open": "", + "folder-scons": "", + "folder-scrap-open.clone": "", + "folder-scrap.clone": "", + "folder-scripts-open": "", + "folder-scripts": "", + "folder-secure-open": "", + "folder-secure": "", + "folder-seeders-open": "", + "folder-seeders": "", + "folder-server-open": "", + "folder-server": "", + "folder-serverless-open": "", + "folder-serverless": "", + "folder-shader-open": "", + "folder-shader": "", + "folder-shared-open": "", + "folder-shared": "", + "folder-simulations-open": "", + "folder-simulations": "", + "folder-skills-open": "", + "folder-skills": "", + "folder-snapcraft-open": "", "folder-snapcraft": "", - "folder-snippet-open": "", - "folder-snippet": "", - "folder-src-open": "", - "folder-src-tauri-open": "", - "folder-src-tauri": "", - "folder-src": "", - "folder-stack-open": "", - "folder-stack": "", - "folder-stencil-open": "", - "folder-stencil": "", - "folder-store-open": "", - "folder-store": "", - "folder-storybook-open": "", - "folder-storybook": "", - "folder-stylus-open": "", - "folder-stylus": "", - "folder-sublime-open": "", - "folder-sublime": "", - "folder-supabase-open": "", - "folder-supabase": "", - "folder-svelte-open": "", - "folder-svelte": "", - "folder-svg-open": "", - "folder-svg": "", - "folder-syntax-open": "", - "folder-syntax": "", - "folder-target-open": "", - "folder-target": "", - "folder-taskfile-open": "", - "folder-taskfile": "", - "folder-tasks-open": "", - "folder-tasks": "", - "folder-television-open": "", - "folder-television": "", - "folder-temp-open": "", - "folder-temp": "", - "folder-template-open": "", - "folder-template": "", - "folder-terraform-open": "", - "folder-terraform": "", - "folder-test-open": "", - "folder-test": "", - "folder-theme-open": "", - "folder-theme": "", - "folder-toc-open": "", - "folder-toc": "", - "folder-tools-open": "", - "folder-tools": "", - "folder-trash-open": "", - "folder-trash": "", - "folder-trigger-open": "", - "folder-trigger": "", - "folder-turborepo-open": "", - "folder-turborepo": "", - "folder-typescript-open": "", - "folder-typescript": "", - "folder-ui-open": "", - "folder-ui": "", - "folder-unity-open": "", - "folder-unity": "", - "folder-update-open": "", - "folder-update": "", - "folder-upload-open": "", - "folder-upload": "", - "folder-utils-open": "", - "folder-utils": "", - "folder-vercel-open": "", - "folder-vercel": "", - "folder-verdaccio-open": "", - "folder-verdaccio": "", - "folder-video-open": "", - "folder-video": "", - "folder-views-open": "", - "folder-views": "", - "folder-vm-open": "", - "folder-vm": "", - "folder-vscode-open": "", - "folder-vscode": "", - "folder-vue-directives-open": "", - "folder-vue-directives": "", - "folder-vue-open": "", - "folder-vue": "", - "folder-vuepress-open": "", - "folder-vuepress": "", - "folder-vuex-store-open": "", - "folder-vuex-store": "", - "folder-wakatime-open": "", - "folder-wakatime": "", - "folder-webpack-open": "", - "folder-webpack": "", - "folder-windows-open": "", - "folder-windows": "", - "folder-wordpress-open": "", - "folder-wordpress": "", - "folder-yarn-open": "", - "folder-yarn": "", - "folder-zeabur-open": "", - "folder-zeabur": "", + "folder-snippet-open": "", + "folder-snippet": "", + "folder-src-open": "", + "folder-src-tauri-open": "", + "folder-src-tauri": "", + "folder-src": "", + "folder-stack-open": "", + "folder-stack": "", + "folder-stencil-open": "", + "folder-stencil": "", + "folder-store-open": "", + "folder-store": "", + "folder-storybook-open": "", + "folder-storybook": "", + "folder-stylus-open": "", + "folder-stylus": "", + "folder-sublime-open": "", + "folder-sublime": "", + "folder-supabase-open": "", + "folder-supabase": "", + "folder-svelte-open": "", + "folder-svelte": "", + "folder-svg-open": "", + "folder-svg": "", + "folder-syntax-open": "", + "folder-syntax": "", + "folder-target-open": "", + "folder-target": "", + "folder-taskfile-open": "", + "folder-taskfile": "", + "folder-tasks-open": "", + "folder-tasks": "", + "folder-television-open": "", + "folder-television": "", + "folder-temp-open": "", + "folder-temp": "", + "folder-template-open": "", + "folder-template": "", + "folder-terraform-open": "", + "folder-terraform": "", + "folder-test-open": "", + "folder-test": "", + "folder-theme-open": "", + "folder-theme": "", + "folder-toc-open": "", + "folder-toc": "", + "folder-tools-open": "", + "folder-tools": "", + "folder-trash-open": "", + "folder-trash": "", + "folder-trigger-open": "", + "folder-trigger": "", + "folder-turborepo-open": "", + "folder-turborepo": "", + "folder-typescript-open": "", + "folder-typescript": "", + "folder-ui-open": "", + "folder-ui": "", + "folder-unity-open": "", + "folder-unity": "", + "folder-update-open": "", + "folder-update": "", + "folder-upload-open": "", + "folder-upload": "", + "folder-utils-open": "", + "folder-utils": "", + "folder-vercel-open": "", + "folder-vercel": "", + "folder-verdaccio-open": "", + "folder-verdaccio": "", + "folder-video-open": "", + "folder-video": "", + "folder-views-open": "", + "folder-views": "", + "folder-vm-open": "", + "folder-vm": "", + "folder-vscode-open": "", + "folder-vscode": "", + "folder-vue-directives-open": "", + "folder-vue-directives": "", + "folder-vue-open": "", + "folder-vue": "", + "folder-vuepress-open": "", + "folder-vuepress": "", + "folder-vuex-store-open": "", + "folder-vuex-store": "", + "folder-wakatime-open": "", + "folder-wakatime": "", + "folder-webpack-open": "", + "folder-webpack": "", + "folder-windows-open": "", + "folder-windows": "", + "folder-wordpress-open": "", + "folder-wordpress": "", + "folder-yarn-open": "", + "folder-yarn": "", + "folder-zeabur-open": "", + "folder-zeabur": "", + "folder-zed-open": "", + "folder-zed": "", "folder": "", "font": "", "forth": "", @@ -819,6 +825,7 @@ "helm": "", "heroku": "", "hex": "", + "hip": "", "histoire": "", "hjson": "", "horusec": "", @@ -922,6 +929,7 @@ "mojo": "", "moon": "", "moonscript": "", + "mrpack": "", "mxml": "", "nano-staged": "", "nano-staged_light": "", @@ -968,6 +976,8 @@ "opam": "", "openapi": "", "openapi_light": "", + "opencode": "", + "opencode_light": "", "opentofu": "", "opentofu_light": "", "otne": "", @@ -1166,6 +1176,7 @@ "trigger": "", "tsconfig": "", "tsdoc": "", + "tsdown": "", "tsil": "", "tune": "", "turborepo": "", @@ -1176,6 +1187,7 @@ "typescript-def": "", "typescript": "", "typst": "", + "uiua": "", "umi": "", "uml": "", "uml_light": "", diff --git a/package.json b/package.json index 31e946ba2e..d0ae45bbf8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "type": "module", - "packageManager": "pnpm@11.8.0", + "packageManager": "pnpm@11.9.0", "engines": { "node": ">= 22.18.0", "pnpm": ">= 11.0.0" @@ -11,7 +11,7 @@ "@citation-js/plugin-csl": "0.7.22", "@citation-js/plugin-software-formats": "0.6.2", "@codemirror/autocomplete": "6.20.3", - "@codemirror/commands": "6.10.3", + "@codemirror/commands": "6.10.4", "@codemirror/lang-json": "6.0.2", "@codemirror/lang-markdown": "6.5.0", "@codemirror/language": "6.12.3", @@ -19,8 +19,8 @@ "@codemirror/legacy-modes": "6.5.3", "@codemirror/lint": "6.9.7", "@codemirror/search": "6.7.1", - "@codemirror/state": "6.6.0", - "@codemirror/view": "6.43.1", + "@codemirror/state": "6.7.0", + "@codemirror/view": "6.43.2", "@deltablot/dropzone": "7.4.3", "@github/markdown-toolbar-element": "2.2.3", "@github/paste-markdown": "1.5.3", @@ -59,7 +59,7 @@ "postcss": "8.5.15", "rolldown-license-plugin": "3.0.9", "sortablejs": "1.15.7", - "swagger-ui-dist": "5.32.6", + "swagger-ui-dist": "5.32.8", "tailwindcss": "3.4.19", "throttle-debounce": "5.0.2", "tippy.js": "6.3.7", @@ -67,7 +67,7 @@ "tributejs": "5.1.3", "uint8-to-base64": "0.2.1", "vanilla-colorful": "0.7.2", - "vite": "8.0.16", + "vite": "8.1.0", "vite-string-plugin": "2.0.4", "vue": "3.5.38", "vue-bar-graph": "2.2.0", @@ -76,27 +76,27 @@ "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.7.2", "@eslint/json": "2.0.0", - "@playwright/test": "1.61.0", + "@playwright/test": "1.61.1", "@stylistic/eslint-plugin": "5.10.0", "@stylistic/stylelint-plugin": "5.2.0", "@types/codemirror": "5.60.17", "@types/jquery": "4.0.1", "@types/js-yaml": "4.0.9", "@types/katex": "0.16.8", - "@types/node": "25.9.3", + "@types/node": "25.9.4", "@types/pdfobject": "2.2.5", "@types/sortablejs": "1.15.9", "@types/swagger-ui-dist": "3.30.6", "@types/throttle-debounce": "5.0.2", "@types/toastify-js": "1.12.4", - "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/parser": "8.62.0", "@vitejs/plugin-vue": "6.0.7", "@vitest/eslint-plugin": "1.6.20", "eslint": "10.5.0", "eslint-import-resolver-typescript": "4.4.5", "eslint-plugin-array-func": "5.1.1", "eslint-plugin-de-morgan": "2.1.2", - "eslint-plugin-import-x": "4.16.2", + "eslint-plugin-import-x": "4.17.0", "eslint-plugin-playwright": "2.10.4", "eslint-plugin-regexp": "3.1.0", "eslint-plugin-sonarjs": "4.1.0", @@ -104,11 +104,11 @@ "eslint-plugin-vue": "10.9.2", "eslint-plugin-vue-scoped-css": "3.1.1", "eslint-plugin-wc": "3.1.0", - "globals": "17.6.0", + "globals": "17.7.0", "happy-dom": "20.10.6", "jiti": "2.7.0", "markdownlint-cli": "0.49.0", - "material-icon-theme": "5.35.0", + "material-icon-theme": "5.36.1", "postcss-html": "1.8.1", "spectral-cli-bundle": "1.0.8", "stylelint": "17.13.0", @@ -118,7 +118,7 @@ "stylelint-value-no-unknown-custom-properties": "6.1.1", "svgo": "4.0.1", "typescript": "6.0.3", - "typescript-eslint": "8.61.1", + "typescript-eslint": "8.62.0", "updates": "17.18.0", "vitest": "4.1.9", "vue-tsc": "3.3.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4d5aa8972..0e9fef4893 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: 6.20.3 version: 6.20.3 '@codemirror/commands': - specifier: 6.10.3 - version: 6.10.3 + specifier: 6.10.4 + version: 6.10.4 '@codemirror/lang-json': specifier: 6.0.2 version: 6.0.2 @@ -48,11 +48,11 @@ importers: specifier: 6.7.1 version: 6.7.1 '@codemirror/state': - specifier: 6.6.0 - version: 6.6.0 + specifier: 6.7.0 + version: 6.7.0 '@codemirror/view': - specifier: 6.43.1 - version: 6.43.1 + specifier: 6.43.2 + version: 6.43.2 '@deltablot/dropzone': specifier: 7.4.3 version: 7.4.3 @@ -79,22 +79,22 @@ importers: version: 19.28.1 '@replit/codemirror-indentation-markers': specifier: 6.5.3 - version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1) + version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2) '@replit/codemirror-lang-nix': specifier: 6.0.1 - version: 6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10) + version: 6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10) '@replit/codemirror-lang-svelte': specifier: 6.0.0 - version: 6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10) + version: 6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10) '@replit/codemirror-vscode-keymap': specifier: 6.0.2 - version: 6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1) + version: 6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2) '@resvg/resvg-wasm': specifier: 2.6.2 version: 2.6.2 '@vitejs/plugin-vue': specifier: 6.0.7 - version: 6.0.7(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + version: 6.0.7(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) ansi_up: specifier: 6.0.6 version: 6.0.6 @@ -163,13 +163,13 @@ importers: version: 8.5.15 rolldown-license-plugin: specifier: 3.0.9 - version: 3.0.9(rolldown@1.0.3) + version: 3.0.9(rolldown@1.1.3) sortablejs: specifier: 1.15.7 version: 1.15.7 swagger-ui-dist: - specifier: 5.32.6 - version: 5.32.6 + specifier: 5.32.8 + version: 5.32.8 tailwindcss: specifier: 3.4.19 version: 3.4.19(yaml@2.9.0) @@ -192,11 +192,11 @@ importers: specifier: 0.7.2 version: 0.7.2 vite: - specifier: 8.0.16 - version: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) + specifier: 8.1.0 + version: 8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) vite-string-plugin: specifier: 2.0.4 - version: 2.0.4(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 2.0.4(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) vue: specifier: 3.5.38 version: 3.5.38(typescript@6.0.3) @@ -214,8 +214,8 @@ importers: specifier: 2.0.0 version: 2.0.0 '@playwright/test': - specifier: 1.61.0 - version: 1.61.0 + specifier: 1.61.1 + version: 1.61.1 '@stylistic/eslint-plugin': specifier: 5.10.0 version: 5.10.0(eslint@10.5.0(jiti@2.7.0)) @@ -235,8 +235,8 @@ importers: specifier: 0.16.8 version: 0.16.8 '@types/node': - specifier: 25.9.3 - version: 25.9.3 + specifier: 25.9.4 + version: 25.9.4 '@types/pdfobject': specifier: 2.2.5 version: 2.2.5 @@ -253,17 +253,17 @@ importers: specifier: 1.12.4 version: 1.12.4 '@typescript-eslint/parser': - specifier: 8.61.1 - version: 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.62.0 + version: 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) '@vitest/eslint-plugin': specifier: 1.6.20 - version: 1.6.20(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.9.3)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))) + version: 1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.9.4)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))) eslint: specifier: 10.5.0 version: 10.5.0(jiti@2.7.0) eslint-import-resolver-typescript: specifier: 4.4.5 - version: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) + version: 4.4.5(eslint-plugin-import-x@4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-array-func: specifier: 5.1.1 version: 5.1.1(eslint@10.5.0(jiti@2.7.0)) @@ -271,8 +271,8 @@ importers: specifier: 2.1.2 version: 2.1.2(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-import-x: - specifier: 4.16.2 - version: 4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) + specifier: 4.17.0 + version: 4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-playwright: specifier: 2.10.4 version: 2.10.4(eslint@10.5.0(jiti@2.7.0)) @@ -287,7 +287,7 @@ importers: version: 68.0.0(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-vue: specifier: 10.9.2 - version: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))) + version: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))) eslint-plugin-vue-scoped-css: specifier: 3.1.1 version: 3.1.1(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))) @@ -295,8 +295,8 @@ importers: specifier: 3.1.0 version: 3.1.0(eslint@10.5.0(jiti@2.7.0)) globals: - specifier: 17.6.0 - version: 17.6.0 + specifier: 17.7.0 + version: 17.7.0 happy-dom: specifier: 20.10.6 version: 20.10.6 @@ -307,8 +307,8 @@ importers: specifier: 0.49.0 version: 0.49.0 material-icon-theme: - specifier: 5.35.0 - version: 5.35.0 + specifier: 5.36.1 + version: 5.36.1 postcss-html: specifier: 1.8.1 version: 1.8.1 @@ -337,14 +337,14 @@ importers: specifier: 6.0.3 version: 6.0.3 typescript-eslint: - specifier: 8.61.1 - version: 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + specifier: 8.62.0 + version: 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) updates: specifier: 17.18.0 version: 17.18.0 vitest: specifier: 4.1.9 - version: 4.1.9(@types/node@25.9.3)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.9(@types/node@25.9.4)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) vue-tsc: specifier: 3.3.5 version: 3.3.5(typescript@6.0.3) @@ -446,8 +446,8 @@ packages: '@codemirror/autocomplete@6.20.3': resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} - '@codemirror/commands@6.10.3': - resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==} + '@codemirror/commands@6.10.4': + resolution: {integrity: sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==} '@codemirror/lang-angular@0.1.4': resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==} @@ -527,11 +527,11 @@ packages: '@codemirror/search@6.7.1': resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==} - '@codemirror/state@6.6.0': - resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==} + '@codemirror/state@6.7.0': + resolution: {integrity: sha512-Zbl9NyscLMZkfXPQnNAIIAFftidrA1UbcJEIMp24C0Bukc2I5T8wJS0wsXYsnDOqCFJUeJ1BITGNs5CqPDSmSg==} - '@codemirror/view@6.43.1': - resolution: {integrity: sha512-+BIjw/AG3tDQ4pJgTLPYdAW25eDE66YsvM4LKyVPgGzVgZ4a9Wj1SRX8kPVKgBDdPt8oHtZ15F0qx7p0oOHdHw==} + '@codemirror/view@6.43.2': + resolution: {integrity: sha512-8kU6WNRYBKV9Sw3cxNz+uSvUvx3tt+1qgupGFPubnbLFDHOgh5qQdIGmXcD7bkA/PROK6LDKVhKMpcY7H++Amg==} '@csstools/css-calc@3.2.1': resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} @@ -583,12 +583,21 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} @@ -936,8 +945,8 @@ packages: '@mermaid-js/parser@1.1.1': resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 @@ -954,14 +963,11 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} - '@package-json/types@0.0.12': - resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} - - '@playwright/test@1.61.0': - resolution: {integrity: sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==} + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} engines: {node: '>=18'} hasBin: true @@ -1019,97 +1025,97 @@ packages: resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + '@rolldown/binding-android-arm64@1.1.3': + resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + '@rolldown/binding-darwin-arm64@1.1.3': + resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + '@rolldown/binding-darwin-x64@1.1.3': + resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + '@rolldown/binding-freebsd-x64@1.1.3': + resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': + resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + '@rolldown/binding-linux-arm64-gnu@1.1.3': + resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + '@rolldown/binding-linux-arm64-musl@1.1.3': + resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + '@rolldown/binding-linux-ppc64-gnu@1.1.3': + resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + '@rolldown/binding-linux-s390x-gnu@1.1.3': + resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + '@rolldown/binding-linux-x64-gnu@1.1.3': + resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + '@rolldown/binding-linux-x64-musl@1.1.3': + resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + '@rolldown/binding-openharmony-arm64@1.1.3': + resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + '@rolldown/binding-wasm32-wasi@1.1.3': + resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + '@rolldown/binding-win32-arm64-msvc@1.1.3': + resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + '@rolldown/binding-win32-x64-msvc@1.1.3': + resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1175,8 +1181,8 @@ packages: resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} engines: {node: '>= 10'} - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1328,8 +1334,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@25.9.4': + resolution: {integrity: sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==} '@types/pdfobject@2.2.5': resolution: {integrity: sha512-7gD5tqc/RUDq0PyoLemL0vEHxBYi+zY0WVaFAx/Y0jBsXFgot1vB9No1GhDZGwRGJMCIZbgAb74QG9MTyTNU/g==} @@ -1373,63 +1379,63 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.61.1': - resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==} + '@typescript-eslint/eslint-plugin@8.62.0': + resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.61.1 + '@typescript-eslint/parser': ^8.62.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.61.1': - resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==} + '@typescript-eslint/parser@8.62.0': + resolution: {integrity: sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.61.1': - resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==} + '@typescript-eslint/project-service@8.62.0': + resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.61.1': - resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==} + '@typescript-eslint/scope-manager@8.62.0': + resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.61.1': - resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==} + '@typescript-eslint/tsconfig-utils@8.62.0': + resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.61.1': - resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==} + '@typescript-eslint/type-utils@8.62.0': + resolution: {integrity: sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.61.1': - resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==} + '@typescript-eslint/types@8.62.0': + resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.61.1': - resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==} + '@typescript-eslint/typescript-estree@8.62.0': + resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.61.1': - resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==} + '@typescript-eslint/utils@8.62.0': + resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.61.1': - resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==} + '@typescript-eslint/visitor-keys@8.62.0': + resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.12.2': @@ -1682,14 +1688,6 @@ packages: alien-signals@3.2.1: resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} - ansi-escapes@1.4.0: - resolution: {integrity: sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==} - engines: {node: '>=0.10.0'} - - ansi-regex@2.1.1: - resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} - engines: {node: '>=0.10.0'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1698,10 +1696,6 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@2.2.1: - resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} - engines: {node: '>=0.10.0'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1753,13 +1747,6 @@ packages: asciinema-player@3.16.0: resolution: {integrity: sha512-bl6U6QD6/2as3F8AaNnAQ1dAZMq9Q/mvlSE5XxQrththGcDQS+jqAUCEr3jrUl2xatIQwblNIyJuFUpX8qjK+g==} - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -1779,12 +1766,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.13.2: - resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1800,20 +1781,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - biome@0.3.3: - resolution: {integrity: sha512-4LXjrQYbn9iTXu9Y4SKT7ABzTV0WnLDHCVSd2fPUOKsy1gQ+E4xPFmlY1zcWexoi0j7fGHItlL6OWA2CZ/yYAQ==} - hasBin: true - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1878,17 +1849,10 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk@1.1.3: - resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} - engines: {node: '>=0.10.0'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1939,20 +1903,9 @@ packages: citeproc@2.4.63: resolution: {integrity: sha512-68F95Bp4UbgZU/DBUGQn0qV3HDZLCdI9+Bb2ByrTaNJDL5VEm9LqaiNaxljsvoaExSLEXe1/r6n2Z06SCzW3/Q==} - cli-cursor@1.0.2: - resolution: {integrity: sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==} - engines: {node: '>=0.10.0'} - - cli-width@1.1.1: - resolution: {integrity: sha512-eMU2akIeEIkCxGXUNmDnJq1KzOIiPnJ+rKqRe6hcxE3vIOPvpMrBYOn/Bl7zNlYJj/zQxXquAnozHUCf9Whnsg==} - clippie@4.2.0: resolution: {integrity: sha512-NcWaVzqChJ69+foFhwJXta3KXWNDJlpicxcfZG5udobyszOSBDhmFubKv1b/1nIZiVAsPoKqME2iV1SITZqFoQ==} - code-point-at@1.1.0: - resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} - engines: {node: '>=0.10.0'} - codemirror-lang-elixir@4.0.1: resolution: {integrity: sha512-z6W/XB4b7TZrp9EZYBGVq93vQfvKbff+1iM8YZaVErL0dguBAeLmVRlEv1NuDZHOP1qjJ3NwyibkUkNWn7q9VQ==} @@ -1984,9 +1937,6 @@ packages: resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} engines: {node: '>=22.12.0'} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2015,16 +1965,9 @@ packages: core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - core-js@2.6.12: - resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==} - deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. - core-js@3.32.2: resolution: {integrity: sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==} - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -2247,10 +2190,6 @@ packages: dagre-d3-es@7.0.14: resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -2300,10 +2239,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deep-rename-keys@0.2.1: - resolution: {integrity: sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==} - engines: {node: '>=0.10.0'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2372,18 +2307,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - earlgrey-runtime@0.1.2: - resolution: {integrity: sha512-T4qoScXi5TwALDv8nlGTvOuCT8jXcKcxtO8qVdqv46IA2GHJfQzwoBPbkOmORnyhu3A98cVVuhWLsM2CzPljJg==} - easymde@2.21.0: resolution: {integrity: sha512-5uE7I/DEN8gvGRwxaqAv7h1PMEK2ykNXVX5zL0dK3nCYROGja3AMbdQz8eCEELnfvCfy7tRkTmLuvyJG8uSWjQ==} - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - - editor@1.0.0: - resolution: {integrity: sha512-SoRmbGStwNYHgKfjOrX2L0mUvp9bUVv0uPppZSOMAntEbcFtoC3MKF5b3T6HQPXKIV+QGY3xPO3JK5it5lVkuw==} - electron-to-chromium@1.5.364: resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} @@ -2455,10 +2381,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -2530,8 +2452,8 @@ packages: peerDependencies: eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 - eslint-plugin-import-x@4.16.2: - resolution: {integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==} + eslint-plugin-import-x@4.17.0: + resolution: {integrity: sha512-aM7V25Bg6YuYxtEhwjafzfS0NTMds1D2PMQI0K4KqJxQJRtkP4CO+MQTWRdBq2qAnmPxTxLevhXUBtByxJqS1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/utils': ^8.56.0 @@ -2670,28 +2592,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@2.0.3: - resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - exit-hook@1.1.1: - resolution: {integrity: sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==} - engines: {node: '>=0.10.0'} - expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2730,10 +2634,6 @@ packages: fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - figures@1.7.0: - resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} - engines: {node: '>=0.10.0'} - file-entry-cache@11.1.3: resolution: {integrity: sha512-oMbq0PD6VIiIwMF6LIa7MEwd/l9huKwmqRKXqmrkqIZv8CvRbfowL+L0ryAl8h//HfAS0zS+4SbYoRyAoA6BJA==} @@ -2767,27 +2667,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - fs-extra@0.26.7: - resolution: {integrity: sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==} - - fs-promise@0.5.0: - resolution: {integrity: sha512-Y+4F4ujhEcayCJt6JmzcOun9MYGQwz+bVUiuBmTkJImhBHKpBvmVPZR9wtfiF7k3ffwAOAuurygQe+cPLSFQhw==} - deprecated: Use mz or fs-extra^3.0 with Promise Support - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2834,9 +2717,6 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2845,10 +2725,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} engines: {node: '>=6'} @@ -2857,8 +2733,8 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globals@17.6.0: - resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} engines: {node: '>=18'} globalthis@1.0.4: @@ -2890,19 +2766,6 @@ packages: resolution: {integrity: sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==} engines: {node: '>=20.0.0'} - har-schema@2.0.0: - resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} - engines: {node: '>=4'} - - har-validator@5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - - has-ansi@2.0.0: - resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} - engines: {node: '>=0.10.0'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -2959,10 +2822,6 @@ packages: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} - http-signature@1.2.0: - resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} - engines: {node: '>=0.8', npm: '>=1.3.7'} - https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -3000,13 +2859,6 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} @@ -3014,12 +2866,6 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inquirer-promise@0.0.3: - resolution: {integrity: sha512-82CQX586JAV9GAgU9yXZsMDs+NorjA0nLhkfFx9+PReyOnuoHRbHrC1Z90sS95bFJI1Tm1gzMObuE0HabzkJpg==} - - inquirer@0.11.4: - resolution: {integrity: sha512-QR+2TW90jnKk9LUUtbcA3yQXKt2rDEKMh6+BAZQIeumtzHexnwVLdPakSslGijXYLJCzFv7GMXbFCn0pA00EUw==} - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -3060,9 +2906,6 @@ packages: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} - is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - is-builtin-module@5.0.0: resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} engines: {node: '>=18.20'} @@ -3097,10 +2940,6 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-fullwidth-code-point@1.0.0: - resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==} - engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -3163,9 +3002,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-valid-element-name@1.0.0: resolution: {integrity: sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==} @@ -3187,9 +3023,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - jest-environment-jsdom@29.7.0: resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3235,9 +3068,6 @@ packages: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - jsdoc-type-pratt-parser@7.2.0: resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==} engines: {node: '>=20.0.0'} @@ -3268,15 +3098,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3284,24 +3108,14 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonfile@2.4.0: - resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==} - jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} - jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - jsx-ast-utils-x@0.1.0: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - kaiser@0.0.4: - resolution: {integrity: sha512-m8ju+rmBqvclZmyrOXgGGhOYSjKJK6RN1NhqEltemY87UqZOxEkizg9TOy1vQSyJ01Wx6SAPuuN0iO2Mgislvw==} - katex@0.16.47: resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true @@ -3319,17 +3133,10 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - kind-of@3.2.2: - resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} - engines: {node: '>=0.10.0'} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - klaw@1.3.1: - resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} - layout-base@1.0.2: resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} @@ -3440,12 +3247,6 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash@3.10.1: - resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3472,8 +3273,8 @@ packages: engines: {node: '>= 12'} hasBin: true - material-icon-theme@5.35.0: - resolution: {integrity: sha512-ptU3rjmZjPCeLRog0HcJ1HtnoVgOslc350WHk1oIvX7fVFE4NBAF7GL3QvCCEjtd1TSSDoxmS4dWsv6bTB1x9g==} + material-icon-theme@5.36.1: + resolution: {integrity: sha512-m61BOQlbzno5KrDMuSb2Z+SxPYXWrVSRZRo6YtmoboZjFdP+HMMAzl6fGlKZUkX7dDBV++eDXYOx5hAsr7derA==} engines: {vscode: ^1.55.0} math-intrinsics@1.1.0: @@ -3609,9 +3410,6 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - mute-stream@0.0.5: - resolution: {integrity: sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg==} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3664,16 +3462,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - number-is-nan@1.0.1: - resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} - engines: {node: '>=0.10.0'} - nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -3713,13 +3504,6 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@1.1.0: - resolution: {integrity: sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==} - engines: {node: '>=0.10.0'} - online-3d-viewer@0.18.0: resolution: {integrity: sha512-y7ZlV/zkakNUyjqcXz6XecA7vXgLEUnaAey9tyx8o6/wcdV64RfjXAQOjGXGY2JOZoDi4Cg1ic9icSWMWAvRQA==} @@ -3727,10 +3511,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - os-homedir@1.0.2: - resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} - engines: {node: '>=0.10.0'} - own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -3770,10 +3550,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -3790,9 +3566,6 @@ packages: perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3812,13 +3585,13 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - playwright-core@1.61.0: - resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} engines: {node: '>=18'} hasBin: true - playwright@1.61.0: - resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} engines: {node: '>=18'} hasBin: true @@ -3926,10 +3699,6 @@ packages: resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} - qs@6.5.5: - resolution: {integrity: sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==} - engines: {node: '>=0.6'} - querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -3946,9 +3715,6 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readline2@1.0.1: - resolution: {integrity: sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==} - refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -3957,9 +3723,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regenerator-runtime@0.9.6: - resolution: {integrity: sha512-D0Y/JJ4VhusyMOd/o25a3jdUqN/bC85EFsaoL9Oqmy/O4efCh+xhp7yj2EEOsj974qvMkcW8AwUzJ1jB/MbxCw==} - regexp-ast-analysis@0.7.1: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -3972,20 +3735,6 @@ packages: resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} hasBin: true - rename-keys@1.2.0: - resolution: {integrity: sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==} - engines: {node: '>= 0.8.0'} - - request-promise@3.0.0: - resolution: {integrity: sha512-wVGUX+BoKxYsavTA72i6qHcyLbjzM4LR4y/AmDCqlbuMAursZdDWO7PmgbGAUvD2SeEJ5iB99VSq/U51i/DNbw==} - engines: {node: '>=0.10.0'} - deprecated: request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 - - request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -4010,19 +3759,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true - restore-cursor@1.0.1: - resolution: {integrity: sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==} - engines: {node: '>=0.10.0'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} @@ -4031,17 +3771,14 @@ packages: peerDependencies: rolldown: '*' - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + rolldown@1.1.3: + resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - run-async@0.1.0: - resolution: {integrity: sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw==} - run-con@1.3.2: resolution: {integrity: sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==} hasBin: true @@ -4052,16 +3789,10 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - rx-lite@3.1.2: - resolution: {integrity: sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ==} - safe-array-concat@1.1.4: resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -4188,11 +3919,6 @@ packages: engines: {node: '>=20'} hasBin: true - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -4211,10 +3937,6 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - string-width@1.0.2: - resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==} - engines: {node: '>=0.10.0'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4235,10 +3957,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - strip-ansi@3.0.1: - resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} - engines: {node: '>=0.10.0'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4306,10 +4024,6 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} - supports-color@2.0.0: - resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} - engines: {node: '>=0.8.0'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4330,11 +4044,8 @@ packages: engines: {node: '>=16'} hasBin: true - svgson@5.3.1: - resolution: {integrity: sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA==} - - swagger-ui-dist@5.32.6: - resolution: {integrity: sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==} + swagger-ui-dist@5.32.8: + resolution: {integrity: sha512-dgMdWXIgnI4zX4OPhKEdWnlDODbgm8W3AX0Ivn/BBqcUh6xZsBxhZMnvk6DJyRz1BTrj8dPxtarmEGgkz30oyA==} symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -4366,9 +4077,6 @@ packages: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -4394,10 +4102,6 @@ packages: toastify-js@1.12.0: resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==} - tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -4431,12 +4135,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4461,8 +4159,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.61.1: - resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==} + typescript-eslint@8.62.0: + resolution: {integrity: sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4500,10 +4198,6 @@ packages: unrs-resolver@1.12.2: resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - untildify@3.0.3: - resolution: {integrity: sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==} - engines: {node: '>=4'} - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -4521,10 +4215,6 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - user-home@2.0.0: - resolution: {integrity: sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==} - engines: {node: '>=0.10.0'} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4532,30 +4222,21 @@ packages: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true - uuid@3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - vanilla-colorful@0.7.2: resolution: {integrity: sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==} - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - vite-string-plugin@2.0.4: resolution: {integrity: sha512-uahQl15I6hsHGzbjCmllVoKZBmZavXAp8GXBmq5omNQM52wgBv7e//98A6cONbq0Of6F/5uJ30OjF5ggNY6reQ==} peerDependencies: vite: '*' - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + vite@8.1.0: + resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -4729,9 +4410,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@7.0.1: resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -4748,16 +4426,10 @@ packages: utf-8-validate: optional: true - xml-lexer@0.2.2: - resolution: {integrity: sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==} - xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} - xml-reader@2.4.3: - resolution: {integrity: sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==} - xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -4877,15 +4549,15 @@ snapshots: '@codemirror/autocomplete@6.20.3': dependencies: '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 - '@codemirror/commands@6.10.3': + '@codemirror/commands@6.10.4': dependencies: '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@codemirror/lang-angular@0.1.4': @@ -4906,7 +4578,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/css': 1.3.3 @@ -4914,7 +4586,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/go': 1.0.1 @@ -4924,8 +4596,8 @@ snapshots: '@codemirror/lang-css': 6.3.1 '@codemirror/lang-javascript': 6.2.5 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/css': 1.3.3 '@lezer/html': 1.3.13 @@ -4940,8 +4612,8 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 '@codemirror/lint': 6.9.7 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/javascript': 1.5.4 @@ -4950,8 +4622,8 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -4974,8 +4646,8 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -4985,8 +4657,8 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/markdown': 1.6.4 @@ -4994,7 +4666,7 @@ snapshots: dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/php': 1.0.5 @@ -5002,7 +4674,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/python': 1.1.19 @@ -5015,7 +4687,7 @@ snapshots: dependencies: '@codemirror/lang-css': 6.3.1 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/sass': 1.1.0 @@ -5023,7 +4695,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -5048,8 +4720,8 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/xml': 1.0.6 @@ -5057,7 +4729,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -5091,8 +4763,8 @@ snapshots: '@codemirror/language@6.12.3': dependencies: - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -5104,23 +4776,23 @@ snapshots: '@codemirror/lint@6.9.7': dependencies: - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 crelt: 1.0.6 '@codemirror/search@6.7.1': dependencies: - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 crelt: 1.0.6 - '@codemirror/state@6.6.0': + '@codemirror/state@6.7.0': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.43.1': + '@codemirror/view@6.43.2': dependencies: - '@codemirror/state': 6.6.0 + '@codemirror/state': 6.7.0 crelt: 1.0.6 style-mod: 4.1.3 w3c-keyname: 2.2.8 @@ -5163,16 +4835,32 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.28.1': optional: true @@ -5335,14 +5023,14 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 25.9.4 jest-mock: 29.7.0 '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 25.9.3 + '@types/node': 25.9.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -5356,7 +5044,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.9.3 + '@types/node': 25.9.4 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -5501,11 +5189,18 @@ snapshots: dependencies: '@chevrotain/types': 11.1.2 - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true '@nodelib/fs.scandir@2.1.5': @@ -5520,13 +5215,11 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@oxc-project/types@0.133.0': {} + '@oxc-project/types@0.137.0': {} - '@package-json/types@0.0.12': {} - - '@playwright/test@1.61.0': + '@playwright/test@1.61.1': dependencies: - playwright: 1.61.0 + playwright: 1.61.1 '@popperjs/core@2.11.8': {} @@ -5534,95 +5227,95 @@ snapshots: dependencies: object-assign: 4.1.1 - '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)': + '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)': dependencies: '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 - '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10)': + '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.10)': dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 - '@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10)': + '@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.3)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)(@lezer/common@1.5.2)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.10)': dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/lang-css': 6.3.1 '@codemirror/lang-html': 6.4.11 '@codemirror/lang-javascript': 6.2.5 '@codemirror/language': 6.12.3 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/javascript': 1.5.4 '@lezer/lr': 1.4.10 - '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.6.0)(@codemirror/view@6.43.1)': + '@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.0)(@codemirror/view@6.43.2)': dependencies: '@codemirror/autocomplete': 6.20.3 - '@codemirror/commands': 6.10.3 + '@codemirror/commands': 6.10.4 '@codemirror/language': 6.12.3 '@codemirror/lint': 6.9.7 '@codemirror/search': 6.7.1 - '@codemirror/state': 6.6.0 - '@codemirror/view': 6.43.1 + '@codemirror/state': 6.7.0 + '@codemirror/view': 6.43.2 '@resvg/resvg-wasm@2.6.2': {} - '@rolldown/binding-android-arm64@1.0.3': + '@rolldown/binding-android-arm64@1.1.3': optional: true - '@rolldown/binding-darwin-arm64@1.0.3': + '@rolldown/binding-darwin-arm64@1.1.3': optional: true - '@rolldown/binding-darwin-x64@1.0.3': + '@rolldown/binding-darwin-x64@1.1.3': optional: true - '@rolldown/binding-freebsd-x64@1.0.3': + '@rolldown/binding-freebsd-x64@1.1.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + '@rolldown/binding-linux-arm-gnueabihf@1.1.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.3': + '@rolldown/binding-linux-arm64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.3': + '@rolldown/binding-linux-arm64-musl@1.1.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.3': + '@rolldown/binding-linux-ppc64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.3': + '@rolldown/binding-linux-s390x-gnu@1.1.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.3': + '@rolldown/binding-linux-x64-gnu@1.1.3': optional: true - '@rolldown/binding-linux-x64-musl@1.0.3': + '@rolldown/binding-linux-x64-musl@1.1.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': + '@rolldown/binding-openharmony-arm64@1.1.3': optional: true - '@rolldown/binding-wasm32-wasi@1.0.3': + '@rolldown/binding-wasm32-wasi@1.1.3': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.3': + '@rolldown/binding-win32-arm64-msvc@1.1.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.3': + '@rolldown/binding-win32-x64-msvc@1.1.3': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -5667,7 +5360,7 @@ snapshots: '@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/types': 8.62.0 eslint: 10.5.0(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -5691,7 +5384,7 @@ snapshots: '@tootallnate/once@2.0.1': {} - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -5852,7 +5545,7 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -5867,7 +5560,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@25.9.3': + '@types/node@25.9.4': dependencies: undici-types: 7.24.6 @@ -5898,7 +5591,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 '@types/yargs-parser@21.0.3': {} @@ -5906,14 +5599,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/type-utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.62.0 eslint: 10.5.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 @@ -5922,41 +5615,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.62.0 debug: 4.4.3 eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.61.1(typescript@6.0.3)': + '@typescript-eslint/project-service@8.62.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.61.1': + '@typescript-eslint/scope-manager@8.62.0': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 - '@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.62.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 eslint: 10.5.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -5964,14 +5657,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.61.1': {} + '@typescript-eslint/types@8.62.0': {} - '@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.62.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.61.1(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/project-service': 8.62.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3) + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.4 @@ -5981,20 +5674,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.61.1': + '@typescript-eslint/visitor-keys@8.62.0': dependencies: - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/types': 8.62.0 eslint-visitor-keys: 5.0.1 '@unrs/resolver-binding-android-arm-eabi@1.12.2': @@ -6055,7 +5748,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': @@ -6072,21 +5765,21 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.7(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) vue: 3.5.38(typescript@6.0.3) - '@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.9.3)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)))': + '@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.9.4)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)))': dependencies: - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.5.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 - vitest: 4.1.9(@types/node@25.9.3)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.9(@types/node@25.9.4)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -6099,13 +5792,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.9(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.9': dependencies: @@ -6246,16 +5939,10 @@ snapshots: alien-signals@3.2.1: {} - ansi-escapes@1.4.0: {} - - ansi-regex@2.1.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} - ansi-styles@2.2.1: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -6337,12 +6024,6 @@ snapshots: solid-js: 1.9.13 solid-transition-group: 0.2.3(solid-js@1.9.13) - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - assertion-error@2.0.1: {} astral-regex@2.0.0: {} @@ -6357,11 +6038,8 @@ snapshots: possible-typed-array-names: 1.1.0 optional: true - aws-sign2@0.7.0: {} - - aws4@1.13.2: {} - - balanced-match@1.0.2: {} + balanced-match@1.0.2: + optional: true balanced-match@4.0.4: {} @@ -6369,32 +6047,15 @@ snapshots: baseline-browser-mapping@2.10.33: {} - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - binary-extensions@2.3.0: {} - biome@0.3.3: - dependencies: - bluebird: 3.7.2 - chalk: 1.1.3 - commander: 2.20.3 - editor: 1.0.0 - fs-promise: 0.5.0 - inquirer-promise: 0.0.3 - request-promise: 3.0.0 - untildify: 3.0.3 - user-home: 2.0.0 - - bluebird@3.7.2: {} - boolbase@1.0.0: {} brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + optional: true brace-expansion@5.0.6: dependencies: @@ -6414,7 +6075,7 @@ snapshots: buffer-image-size@0.6.4: dependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 buffer@5.7.1: dependencies: @@ -6460,18 +6121,8 @@ snapshots: caniuse-lite@1.0.30001793: {} - caseless@0.12.0: {} - chai@6.2.2: {} - chalk@1.1.3: - dependencies: - ansi-styles: 2.2.1 - escape-string-regexp: 1.0.5 - has-ansi: 2.0.0 - strip-ansi: 3.0.1 - supports-color: 2.0.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6520,16 +6171,8 @@ snapshots: citeproc@2.4.63: {} - cli-cursor@1.0.2: - dependencies: - restore-cursor: 1.0.1 - - cli-width@1.1.1: {} - clippie@4.2.0: {} - code-point-at@1.1.0: {} - codemirror-lang-elixir@4.0.1: dependencies: '@codemirror/language': 6.12.3 @@ -6557,8 +6200,6 @@ snapshots: commander@15.0.0: {} - commander@2.20.3: {} - commander@4.1.1: {} commander@7.2.0: {} @@ -6569,7 +6210,8 @@ snapshots: compare-versions@6.1.1: {} - concat-map@0.0.1: {} + concat-map@0.0.1: + optional: true convert-source-map@2.0.0: {} @@ -6577,12 +6219,8 @@ snapshots: dependencies: browserslist: 4.28.2 - core-js@2.6.12: {} - core-js@3.32.2: {} - core-util-is@1.0.2: {} - cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -6832,10 +6470,6 @@ snapshots: d3: 7.9.0 lodash-es: 4.18.1 - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -6884,11 +6518,6 @@ snapshots: deep-is@0.1.4: {} - deep-rename-keys@0.2.1: - dependencies: - kind-of: 3.2.2 - rename-keys: 1.2.0 - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -6962,13 +6591,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - earlgrey-runtime@0.1.2: - dependencies: - core-js: 2.6.12 - kaiser: 0.0.4 - lodash: 4.18.1 - regenerator-runtime: 0.9.6 - easymde@2.21.0: dependencies: '@types/codemirror': 5.60.17 @@ -6977,13 +6599,6 @@ snapshots: codemirror-spell-checker: 1.1.2 marked: 4.3.0 - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - - editor@1.0.0: {} - electron-to-chromium@1.5.364: {} elkjs@0.9.3: {} @@ -7122,8 +6737,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} escape-string-regexp@4.0.0: {} @@ -7152,7 +6765,7 @@ snapshots: - supports-color optional: true - eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)): + eslint-import-resolver-typescript@4.4.5(eslint-plugin-import-x@4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)): dependencies: debug: 4.4.3 eslint: 10.5.0(jiti@2.7.0) @@ -7163,19 +6776,19 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) - eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) + eslint-plugin-import-x: 4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.5.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) + eslint-import-resolver-typescript: 4.4.5(eslint-plugin-import-x@4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)))(eslint-plugin-import@2.32.0)(eslint@10.5.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color optional: true @@ -7188,10 +6801,9 @@ snapshots: dependencies: eslint: 10.5.0(jiti@2.7.0) - eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)): + eslint-plugin-import-x@4.17.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0(jiti@2.7.0)): dependencies: - '@package-json/types': 0.0.12 - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/types': 8.62.0 comment-parser: 1.4.7 debug: 4.4.3 eslint: 10.5.0(jiti@2.7.0) @@ -7202,12 +6814,12 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7218,7 +6830,7 @@ snapshots: doctrine: 2.1.0 eslint: 10.5.0(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@10.5.0(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -7230,7 +6842,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -7240,7 +6852,7 @@ snapshots: eslint-plugin-playwright@2.10.4(eslint@10.5.0(jiti@2.7.0)): dependencies: eslint: 10.5.0(jiti@2.7.0) - globals: 17.6.0 + globals: 17.7.0 eslint-plugin-regexp@3.1.0(eslint@10.5.0(jiti@2.7.0)): dependencies: @@ -7260,7 +6872,7 @@ snapshots: bytes: 3.1.2 eslint: 10.5.0(jiti@2.7.0) functional-red-black-tree: 1.0.1 - globals: 17.6.0 + globals: 17.7.0 jsx-ast-utils-x: 0.1.0 lodash.merge: 4.6.2 minimatch: 10.2.5 @@ -7281,7 +6893,7 @@ snapshots: detect-indent: 7.0.2 eslint: 10.5.0(jiti@2.7.0) find-up-simple: 1.0.1 - globals: 17.6.0 + globals: 17.7.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 @@ -7300,7 +6912,7 @@ snapshots: postcss-selector-parser: 7.1.1 vue-eslint-parser: 10.4.0(eslint@10.5.0(jiti@2.7.0)) - eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))): + eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@10.5.0(jiti@2.7.0))): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) eslint: 10.5.0(jiti@2.7.0) @@ -7312,7 +6924,7 @@ snapshots: xml-name-validator: 4.0.0 optionalDependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@10.5.0(jiti@2.7.0)) - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint-plugin-wc@3.1.0(eslint@10.5.0(jiti@2.7.0)): dependencies: @@ -7402,18 +7014,8 @@ snapshots: esutils@2.0.3: {} - eventemitter3@2.0.3: {} - - events@3.3.0: {} - - exit-hook@1.1.1: {} - expect-type@1.3.0: {} - extend@3.0.2: {} - - extsprintf@1.3.0: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -7448,11 +7050,6 @@ snapshots: fflate@0.8.2: {} - figures@1.7.0: - dependencies: - escape-string-regexp: 1.0.5 - object-assign: 4.1.1 - file-entry-cache@11.1.3: dependencies: flat-cache: 6.1.22 @@ -7490,14 +7087,6 @@ snapshots: is-callable: 1.2.7 optional: true - forever-agent@0.6.1: {} - - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -7506,23 +7095,6 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 - fs-extra@0.26.7: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 2.4.0 - klaw: 1.3.1 - path-is-absolute: 1.0.1 - rimraf: 2.7.1 - - fs-promise@0.5.0: - dependencies: - any-promise: 1.3.0 - fs-extra: 0.26.7 - mz: 2.7.0 - thenify-all: 1.6.0 - - fs.realpath@1.0.0: {} - fsevents@2.3.2: optional: true @@ -7580,10 +7152,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - getpass@0.1.7: - dependencies: - assert-plus: 1.0.0 - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7592,15 +7160,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - global-modules@2.0.0: dependencies: global-prefix: 3.0.0 @@ -7611,7 +7170,7 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globals@17.6.0: {} + globals@17.7.0: {} globalthis@1.0.4: dependencies: @@ -7640,7 +7199,7 @@ snapshots: happy-dom@20.10.6: dependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 buffer-image-size: 0.6.4 @@ -7651,17 +7210,6 @@ snapshots: - bufferutil - utf-8-validate - har-schema@2.0.0: {} - - har-validator@5.1.5: - dependencies: - ajv: 6.15.0 - har-schema: 2.0.0 - - has-ansi@2.0.0: - dependencies: - ansi-regex: 2.1.1 - has-bigints@1.1.0: optional: true @@ -7718,12 +7266,6 @@ snapshots: transitivePeerDependencies: - supports-color - http-signature@1.2.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.18.0 - https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -7754,38 +7296,10 @@ snapshots: indent-string@5.0.0: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - ini@1.3.8: {} ini@4.1.3: {} - inquirer-promise@0.0.3: - dependencies: - earlgrey-runtime: 0.1.2 - inquirer: 0.11.4 - - inquirer@0.11.4: - dependencies: - ansi-escapes: 1.4.0 - ansi-regex: 2.1.1 - chalk: 1.1.3 - cli-cursor: 1.0.2 - cli-width: 1.1.1 - figures: 1.7.0 - lodash: 3.10.1 - readline2: 1.0.1 - run-async: 0.1.0 - rx-lite: 3.1.2 - string-width: 1.0.2 - strip-ansi: 3.0.1 - through: 2.3.8 - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -7837,8 +7351,6 @@ snapshots: has-tostringtag: 1.0.2 optional: true - is-buffer@1.1.6: {} - is-builtin-module@5.0.0: dependencies: builtin-modules: 5.2.0 @@ -7876,10 +7388,6 @@ snapshots: call-bound: 1.0.4 optional: true - is-fullwidth-code-point@1.0.0: - dependencies: - number-is-nan: 1.0.1 - is-fullwidth-code-point@3.0.0: {} is-generator-function@1.1.2: @@ -7949,8 +7457,6 @@ snapshots: which-typed-array: 1.1.21 optional: true - is-typedarray@1.0.0: {} - is-valid-element-name@1.0.0: dependencies: is-potential-custom-element-name: 1.0.1 @@ -7974,15 +7480,13 @@ snapshots: isexe@2.0.0: {} - isstream@0.1.2: {} - jest-environment-jsdom@29.7.0: dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 25.9.3 + '@types/node': 25.9.4 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 20.0.3 @@ -8006,13 +7510,13 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 25.9.4 jest-util: 29.7.0 jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 25.9.3 + '@types/node': 25.9.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -8034,8 +7538,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@0.1.1: {} - jsdoc-type-pratt-parser@7.2.0: {} jsdom@20.0.3: @@ -8081,12 +7583,8 @@ snapshots: json-schema-traverse@1.0.0: {} - json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - json5@1.0.2: dependencies: minimist: 1.2.8 @@ -8094,25 +7592,10 @@ snapshots: jsonc-parser@3.3.1: {} - jsonfile@2.4.0: - optionalDependencies: - graceful-fs: 4.2.11 - jsonpointer@5.0.1: {} - jsprim@1.4.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - jsx-ast-utils-x@0.1.0: {} - kaiser@0.0.4: - dependencies: - earlgrey-runtime: 0.1.2 - katex@0.16.47: dependencies: commander: 8.3.0 @@ -8131,16 +7614,8 @@ snapshots: khroma@2.1.0: {} - kind-of@3.2.2: - dependencies: - is-buffer: 1.1.6 - kind-of@6.0.3: {} - klaw@1.3.1: - optionalDependencies: - graceful-fs: 4.2.11 - layout-base@1.0.2: {} layout-base@2.0.1: {} @@ -8222,10 +7697,6 @@ snapshots: lodash.truncate@4.4.2: {} - lodash@3.10.1: {} - - lodash@4.18.1: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8274,13 +7745,10 @@ snapshots: marked@4.3.0: {} - material-icon-theme@5.35.0: + material-icon-theme@5.36.1: dependencies: - biome: 0.3.3 chroma-js: 3.2.0 - events: 3.3.0 fast-deep-equal: 3.1.3 - svgson: 5.3.1 math-intrinsics@1.1.0: {} @@ -8510,6 +7978,7 @@ snapshots: minimatch@3.1.5: dependencies: brace-expansion: 1.1.15 + optional: true minimist@1.2.8: {} @@ -8519,8 +7988,6 @@ snapshots: muggle-string@0.4.1: {} - mute-stream@0.0.5: {} - mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -8559,12 +8026,8 @@ snapshots: dependencies: boolbase: 1.0.0 - number-is-nan@1.0.1: {} - nwsapi@2.2.23: {} - oauth-sign@0.9.0: {} - object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -8618,12 +8081,6 @@ snapshots: obug@2.1.1: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@1.1.0: {} - online-3d-viewer@0.18.0: dependencies: '@simonwep/pickr': 1.9.0 @@ -8639,8 +8096,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - os-homedir@1.0.2: {} - own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -8689,8 +8144,6 @@ snapshots: path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -8701,8 +8154,6 @@ snapshots: perfect-debounce@2.1.0: {} - performance-now@2.1.0: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -8713,11 +8164,11 @@ snapshots: pirates@4.0.7: {} - playwright-core@1.61.0: {} + playwright-core@1.61.1: {} - playwright@1.61.0: + playwright@1.61.1: dependencies: - playwright-core: 1.61.0 + playwright-core: 1.61.1 optionalDependencies: fsevents: 2.3.2 @@ -8811,8 +8262,6 @@ snapshots: dependencies: hookified: 2.2.0 - qs@6.5.5: {} - querystringify@2.2.0: {} queue-microtask@1.2.3: {} @@ -8827,12 +8276,6 @@ snapshots: dependencies: picomatch: 2.3.2 - readline2@1.0.1: - dependencies: - code-point-at: 1.1.0 - is-fullwidth-code-point: 1.0.0 - mute-stream: 0.0.5 - refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8849,8 +8292,6 @@ snapshots: which-builtin-type: 1.2.1 optional: true - regenerator-runtime@0.9.6: {} - regexp-ast-analysis@0.7.1: dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8870,37 +8311,6 @@ snapshots: dependencies: jsesc: 3.1.0 - rename-keys@1.2.0: {} - - request-promise@3.0.0: - dependencies: - bluebird: 3.7.2 - lodash: 4.18.1 - request: 2.88.2 - - request@2.88.2: - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.5 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - require-from-string@2.0.2: {} requires-port@1.0.0: {} @@ -8926,43 +8336,34 @@ snapshots: supports-preserve-symlinks-flag: 1.0.0 optional: true - restore-cursor@1.0.1: - dependencies: - exit-hook: 1.1.1 - onetime: 1.1.0 - reusify@1.1.0: {} - rimraf@2.7.1: - dependencies: - glob: 7.2.3 - robust-predicates@3.0.3: {} - rolldown-license-plugin@3.0.9(rolldown@1.0.3): + rolldown-license-plugin@3.0.9(rolldown@1.1.3): dependencies: - rolldown: 1.0.3 + rolldown: 1.1.3 - rolldown@1.0.3: + rolldown@1.1.3: dependencies: - '@oxc-project/types': 0.133.0 + '@oxc-project/types': 0.137.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 + '@rolldown/binding-android-arm64': 1.1.3 + '@rolldown/binding-darwin-arm64': 1.1.3 + '@rolldown/binding-darwin-x64': 1.1.3 + '@rolldown/binding-freebsd-x64': 1.1.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.3 + '@rolldown/binding-linux-arm64-gnu': 1.1.3 + '@rolldown/binding-linux-arm64-musl': 1.1.3 + '@rolldown/binding-linux-ppc64-gnu': 1.1.3 + '@rolldown/binding-linux-s390x-gnu': 1.1.3 + '@rolldown/binding-linux-x64-gnu': 1.1.3 + '@rolldown/binding-linux-x64-musl': 1.1.3 + '@rolldown/binding-openharmony-arm64': 1.1.3 + '@rolldown/binding-wasm32-wasi': 1.1.3 + '@rolldown/binding-win32-arm64-msvc': 1.1.3 + '@rolldown/binding-win32-x64-msvc': 1.1.3 roughjs@4.6.6: dependencies: @@ -8971,10 +8372,6 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 - run-async@0.1.0: - dependencies: - once: 1.4.0 - run-con@1.3.2: dependencies: deep-extend: 0.6.0 @@ -8988,8 +8385,6 @@ snapshots: rw@1.3.3: {} - rx-lite@3.1.2: {} - safe-array-concat@1.1.4: dependencies: call-bind: 1.0.9 @@ -8999,8 +8394,6 @@ snapshots: isarray: 2.0.5 optional: true - safe-buffer@5.2.1: {} - safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -9141,18 +8534,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - sshpk@1.18.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - stable-hash-x@0.2.0: {} stack-utils@2.0.6: @@ -9169,12 +8550,6 @@ snapshots: internal-slot: 1.1.0 optional: true - string-width@1.0.2: - dependencies: - code-point-at: 1.1.0 - is-fullwidth-code-point: 1.0.0 - strip-ansi: 3.0.1 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -9212,10 +8587,6 @@ snapshots: es-object-atoms: 1.1.2 optional: true - strip-ansi@3.0.1: - dependencies: - ansi-regex: 2.1.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -9308,8 +8679,6 @@ snapshots: supports-color@10.2.2: {} - supports-color@2.0.0: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -9333,12 +8702,7 @@ snapshots: picocolors: 1.1.1 sax: 1.6.0 - svgson@5.3.1: - dependencies: - deep-rename-keys: 0.2.1 - xml-reader: 2.4.3 - - swagger-ui-dist@5.32.6: + swagger-ui-dist@5.32.8: dependencies: '@scarf/scarf': 1.4.0 @@ -9399,8 +8763,6 @@ snapshots: throttle-debounce@5.0.2: {} - through@2.3.8: {} - tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -9422,11 +8784,6 @@ snapshots: toastify-js@1.12.0: {} - tough-cookie@2.5.0: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -9460,12 +8817,6 @@ snapshots: tslib@2.8.1: {} - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -9509,12 +8860,12 @@ snapshots: reflect.getprototypeof: 1.0.10 optional: true - typescript-eslint@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3): + typescript-eslint@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.62.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.5.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: @@ -9569,8 +8920,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - untildify@3.0.3: {} - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -9588,46 +8937,34 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - user-home@2.0.0: - dependencies: - os-homedir: 1.0.2 - util-deprecate@1.0.2: {} uuid@14.0.0: {} - uuid@3.4.0: {} - vanilla-colorful@0.7.2: {} - verror@1.10.0: + vite-string-plugin@2.0.4(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 + vite: 8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) - vite-string-plugin@2.0.4(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): - dependencies: - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) - - vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0): + vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.15 - rolldown: 1.0.3 + rolldown: 1.1.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.0 - vitest@4.1.9(@types/node@25.9.3)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.9(@types/node@25.9.4)(happy-dom@20.10.6)(jsdom@20.0.3)(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -9644,10 +8981,10 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.1.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 25.9.4 happy-dom: 20.10.6 jsdom: 20.0.3 transitivePeerDependencies: @@ -9780,25 +9117,14 @@ snapshots: word-wrap@1.2.5: {} - wrappy@1.0.2: {} - write-file-atomic@7.0.1: dependencies: signal-exit: 4.1.0 ws@8.21.0: {} - xml-lexer@0.2.2: - dependencies: - eventemitter3: 2.0.3 - xml-name-validator@4.0.0: {} - xml-reader@2.4.3: - dependencies: - eventemitter3: 2.0.3 - xml-lexer: 0.2.2 - xmlchars@2.2.0: {} yaml@2.9.0: {} diff --git a/renovate.json5 b/renovate.json5 index bbfbb8d78a..86811fd462 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -96,7 +96,7 @@ "postUpdateOptions": ["pnpmDedupe"], "postUpgradeTasks": { "commands": ["make svg"], - "fileFilters": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "public/assets/img/svg/**"], + "fileFilters": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "public/assets/img/svg/**", "options/fileicon/**"], "executionMode": "branch", }, }, diff --git a/vite.config.ts b/vite.config.ts index 85d8626914..8ff0769afd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -45,7 +45,7 @@ function failOnWarningsPlugin(): Rolldown.Plugin { onLog(level) { if (level === 'warn') warningCount++; }, - buildEnd() { + closeBundle() { if (!warningCount) return; throw new Error(`${warningCount} warnings present`); }, diff --git a/web_src/js/modules/i18n.test.ts b/web_src/js/modules/i18n.test.ts index 0cea2d0f6d..de667c888a 100644 --- a/web_src/js/modules/i18n.test.ts +++ b/web_src/js/modules/i18n.test.ts @@ -1,15 +1,10 @@ import {trN} from './i18n.ts'; -import {getCurrentLocale} from '../utils.ts'; - -vi.mock('../utils.ts', () => ({getCurrentLocale: vi.fn()})); test('trN', () => { - vi.mocked(getCurrentLocale).mockReturnValue('en-US'); - expect(trN(0, '%d job', '%d jobs')).toEqual('0 jobs'); - expect(trN(1, '%d job', '%d jobs')).toEqual('1 job'); - expect(trN(2, '%d job', '%d jobs')).toEqual('2 jobs'); - expect(trN(1000, '%d job', '%d jobs')).toEqual('1000 jobs'); + expect(trN(0, '%d job', '%d jobs', {lang: 'en-US'})).toEqual('0 jobs'); + expect(trN(1, '%d job', '%d jobs', {lang: 'en-US'})).toEqual('1 job'); + expect(trN(2, '%d job', '%d jobs', {lang: 'en-US'})).toEqual('2 jobs'); + expect(trN(1000, '%d job', '%d jobs', {lang: 'en-US'})).toEqual('1000 jobs'); // languages without a distinct singular always use the plural form - vi.mocked(getCurrentLocale).mockReturnValue('zh-CN'); - expect(trN(1, '%d job', '%d jobs')).toEqual('1 jobs'); + expect(trN(1, '%d job', '%d jobs', {lang: 'zh-CN'})).toEqual('1 jobs'); }); diff --git a/web_src/js/modules/i18n.ts b/web_src/js/modules/i18n.ts index f31b88c856..8ec3ef13bc 100644 --- a/web_src/js/modules/i18n.ts +++ b/web_src/js/modules/i18n.ts @@ -1,7 +1,7 @@ import {getCurrentLocale} from '../utils.ts'; /** frontend `Locale.TrN`: pick the `_1` or `_n` form for `count` and interpolate `%d` */ -export function trN(count: number, form1: string, formN: string): string { - const form = new Intl.PluralRules(getCurrentLocale()).select(count) === 'one' ? form1 : formN; +export function trN(count: number, form1: string, formN: string, {lang = getCurrentLocale()}: {lang?: string} = {}): string { + const form = new Intl.PluralRules(lang).select(count) === 'one' ? form1 : formN; return form.replace('%d', String(count)); } diff --git a/web_src/js/render/plugins/frontend-openapi-swagger.ts b/web_src/js/render/plugins/frontend-openapi-swagger.ts index 99410fd496..15f866f2d8 100644 --- a/web_src/js/render/plugins/frontend-openapi-swagger.ts +++ b/web_src/js/render/plugins/frontend-openapi-swagger.ts @@ -1,15 +1,11 @@ import type {FrontendRenderFunc} from '../plugin.ts'; import {initSwaggerUI} from '../swagger.ts'; -// HINT: SWAGGER-CSS-IMPORT: this import is also necessary when swagger is used as a frontend external render -// But it can't share the same CSS file with the standalone page: it triggers our Vite manifest parser's bug -// Although single top-level "await import(css)" can work, it requires es2022. -// Otherwise, single function-level "await import(css)" can't work due to Vite's dependency analysis and bundling. +// HINT: SWAGGER-CSS-IMPORT: these styles are for the render only. import '../../../css/swagger-render.css'; export const frontendRender: FrontendRenderFunc = async (opts): Promise => { try { - await import('../../../css/swagger-render.css'); await initSwaggerUI(opts.container, {specText: opts.contentString()}); return true; } catch (error) { -- 2.54.0 From 4ce63a1d57791c53342ae25049997e2f721cf014 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 29 Jun 2026 21:06:25 +0800 Subject: [PATCH 052/169] chore: various UI problems (#38263) 1. fix dirty "list" styles for "githooks" and "webhooks" 2. fix git hook edit page layout 3. fix codemirror editor styles 4. fix incorrect "ui attached header" width --- templates/repo/settings/githook_edit.tmpl | 20 ++++++------ templates/repo/settings/githooks.tmpl | 9 +++--- .../repo/settings/webhook/base_list.tmpl | 11 ++++--- templates/repo/settings/webhook/history.tmpl | 4 +-- web_src/css/admin.css | 6 ---- web_src/css/modules/codeeditor.css | 22 +++++++------ web_src/css/modules/header.css | 2 ++ web_src/css/modules/menu.css | 1 + web_src/css/repo.css | 32 ------------------- web_src/css/repo/file-view.css | 2 -- 10 files changed, 38 insertions(+), 71 deletions(-) diff --git a/templates/repo/settings/githook_edit.tmpl b/templates/repo/settings/githook_edit.tmpl index 07ea7fb4fd..4339443954 100644 --- a/templates/repo/settings/githook_edit.tmpl +++ b/templates/repo/settings/githook_edit.tmpl @@ -8,19 +8,19 @@

-

{{ctx.Locale.Tr "repo.settings.githook_edit_desc"}}

+ {{ctx.Locale.Tr "repo.settings.githook_edit_desc"}} +
+
{{with .Hook}} -
- -
-
-
- -
+ +
{{end}}
+
+ +
{{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl index 77aa503a6b..874a4f095d 100644 --- a/templates/repo/settings/githooks.tmpl +++ b/templates/repo/settings/githooks.tmpl @@ -4,13 +4,14 @@ {{ctx.Locale.Tr "repo.settings.githooks"}}
-
-
{{ctx.Locale.Tr "repo.settings.githooks_desc"}}
+
{{ctx.Locale.Tr "repo.settings.githooks_desc"}}
+ {{if .Hooks}}
{{end}} +
{{range .Hooks}} -
+
{{svg "octicon-dot-fill" 22}} {{.Name}} - {{svg "octicon-pencil"}} + {{svg "octicon-pencil"}}
{{end}}
diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl index 8c4096ddc5..2725df85e8 100644 --- a/templates/repo/settings/webhook/base_list.tmpl +++ b/templates/repo/settings/webhook/base_list.tmpl @@ -8,17 +8,18 @@
-
-
{{.Description}}
+
{{.Description}}
+ {{if .Webhooks}}
{{end}} +
{{range .Webhooks}} -
+
{{svg "octicon-dot-fill" 22}} - {{svg "octicon-pencil"}} - {{svg "octicon-pencil"}} + {{svg "octicon-trash"}} diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl index 8c31b467d5..d57913042b 100644 --- a/templates/repo/settings/webhook/history.tmpl +++ b/templates/repo/settings/webhook/history.tmpl @@ -14,7 +14,7 @@ {{end}}
-
+
{{range .History}}
@@ -32,7 +32,7 @@ {{DateUtils.TimeSince .Delivered}}
-
+
-
+
{{end}} diff --git a/types.d.ts b/types.d.ts index 639bd56ad3..2a3447e80c 100644 --- a/types.d.ts +++ b/types.d.ts @@ -47,21 +47,3 @@ declare module '@citation-js/core' { declare module '@citation-js/plugin-software-formats' {} declare module '@citation-js/plugin-bibtex' {} declare module '@citation-js/plugin-csl' {} - -declare module 'vue-bar-graph' { - import type {DefineComponent} from 'vue'; - - interface BarGraphPoint { - value: number; - label: string; - } - - export const VueBarGraph: DefineComponent<{ - points?: Array; - barColor?: string; - textColor?: string; - textAltColor?: string; - height?: number; - labelHeight?: number; - }>; -} diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 82e70b11ae..85c9048943 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -1,6 +1,13 @@