Merge branch 'main' into hocr

This commit is contained in:
Icereed 2025-02-12 05:48:25 +01:00 committed by GitHub
commit f0a73ed263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 293 additions and 111 deletions

View file

@ -82,7 +82,7 @@ RUN sed -i \
RUN CGO_ENABLED=1 GOMAXPROCS=$(nproc) go build -tags musl -o paperless-gpt .
# Stage 3: Create a lightweight image with just the binary
FROM alpine:latest
FROM alpine:3.21.2
ENV GIN_MODE=release

View file

@ -179,35 +179,35 @@ services:
# **Note:** When using Ollama, ensure that the Ollama server is running and accessible from the paperless-gpt container.
| Variable | Description | Required |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------- |
| `PAPERLESS_BASE_URL` | URL of your paperless-ngx instance (e.g. `http://paperless-ngx:8000`). | Yes |
| `PAPERLESS_API_TOKEN` | API token for paperless-ngx. Generate one in paperless-ngx admin. | Yes |
| `PAPERLESS_PUBLIC_URL` | Public URL for Paperless (if different from `PAPERLESS_BASE_URL`). | No |
| `MANUAL_TAG` | Tag for manual processing. Default: `paperless-gpt`. | No |
| `AUTO_TAG` | Tag for auto processing. Default: `paperless-gpt-auto`. | No |
| `LLM_PROVIDER` | AI backend (`openai` or `ollama`). | Yes |
| `LLM_MODEL` | AI model name, e.g. `gpt-4o`, `gpt-3.5-turbo`, `deepseek-r1:8b`. | Yes |
| `OPENAI_API_KEY` | OpenAI API key (required if using OpenAI). | Cond. |
| `OPENAI_BASE_URL` | OpenAI base URL (optional, if using a custom OpenAI compatible service like LiteLLM). | No |
| `LLM_LANGUAGE` | Likely language for documents (e.g. `English`). Default: `English`. | No |
| `OLLAMA_HOST` | Ollama server URL (e.g. `http://host.docker.internal:11434`). | No |
| `OCR_PROVIDER` | OCR provider to use (`llm` or `google_docai`). Default: `llm`. | No |
| `VISION_LLM_PROVIDER` | AI backend for LLM OCR (`openai` or `ollama`). Required if OCR_PROVIDER is `llm`. | Cond. |
| `VISION_LLM_MODEL` | Model name for LLM OCR (e.g. `minicpm-v`). Required if OCR_PROVIDER is `llm`. | Cond. |
| `GOOGLE_PROJECT_ID` | Google Cloud project ID. Required if OCR_PROVIDER is `google_docai`. | Cond. |
| `GOOGLE_LOCATION` | Google Cloud region (e.g. `us`, `eu`). Required if OCR_PROVIDER is `google_docai`. | Cond. |
| `GOOGLE_PROCESSOR_ID` | Document AI processor ID. Required if OCR_PROVIDER is `google_docai`. | Cond. |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to the mounted Google service account key. Required if OCR_PROVIDER is `google_docai`. | Cond. |
| `AUTO_OCR_TAG` | Tag for automatically processing docs with OCR. Default: `paperless-gpt-ocr-auto`. | No |
| `LOG_LEVEL` | Application log level (`info`, `debug`, `warn`, `error`). Default: `info`. | No |
| `LISTEN_INTERFACE` | Network interface to listen on. Default: `:8080`. | No |
| `AUTO_GENERATE_TITLE` | Generate titles automatically if `paperless-gpt-auto` is used. Default: `true`. | No |
| `AUTO_GENERATE_TAGS` | Generate tags automatically if `paperless-gpt-auto` is used. Default: `true`. | No |
| `AUTO_GENERATE_CORRESPONDENTS` | Generate correspondents automatically if `paperless-gpt-auto` is used. Default: `true`. | No |
| `OCR_LIMIT_PAGES` | Limit the number of pages for OCR. Set to `0` for no limit. Default: `5`. | No |
| `TOKEN_LIMIT` | Maximum tokens allowed for prompts/content. Set to `0` to disable limit. Useful for smaller LLMs. | No |
| `CORRESPONDENT_BLACK_LIST` | A comma-separated list of names to exclude from the correspondents suggestions. Example: `John Doe, Jane Smith`. | No |
| Variable | Description | Required | Default |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------- | ---------------------- |
| `PAPERLESS_BASE_URL` | URL of your paperless-ngx instance (e.g. `http://paperless-ngx:8000`). | Yes | |
| `PAPERLESS_API_TOKEN` | API token for paperless-ngx. Generate one in paperless-ngx admin. | Yes | |
| `PAPERLESS_PUBLIC_URL` | Public URL for Paperless (if different from `PAPERLESS_BASE_URL`). | No | |
| `MANUAL_TAG` | Tag for manual processing. | No | paperless-gpt |
| `AUTO_TAG` | Tag for auto processing. | No | paperless-gpt-auto |
| `LLM_PROVIDER` | AI backend (`openai` or `ollama`). | Yes | |
| `LLM_MODEL` | AI model name, e.g. `gpt-4o`, `gpt-3.5-turbo`, `deepseek-r1:8b`. | Yes | |
| `OPENAI_API_KEY` | OpenAI API key (required if using OpenAI). | Cond. | |
| `OPENAI_BASE_URL` | OpenAI base URL (optional, if using a custom OpenAI compatible service like LiteLLM). | No | |
| `LLM_LANGUAGE` | Likely language for documents (e.g. `English`). | No | English |
| `OLLAMA_HOST` | Ollama server URL (e.g. `http://host.docker.internal:11434`). | No | |
| `OCR_PROVIDER` | OCR provider to use (`llm` or `google_docai`). | No | llm |
| `VISION_LLM_PROVIDER` | AI backend for LLM OCR (`openai` or `ollama`). Required if OCR_PROVIDER is `llm`. | Cond. | |
| `VISION_LLM_MODEL` | Model name for LLM OCR (e.g. `minicpm-v`). Required if OCR_PROVIDER is `llm`. | Cond. | |
| `GOOGLE_PROJECT_ID` | Google Cloud project ID. Required if OCR_PROVIDER is `google_docai`. | Cond. | |
| `GOOGLE_LOCATION` | Google Cloud region (e.g. `us`, `eu`). Required if OCR_PROVIDER is `google_docai`. | Cond. | |
| `GOOGLE_PROCESSOR_ID` | Document AI processor ID. Required if OCR_PROVIDER is `google_docai`. | Cond. | |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to the mounted Google service account key. Required if OCR_PROVIDER is `google_docai`. | Cond. | |
| `AUTO_OCR_TAG` | Tag for automatically processing docs with OCR. | No | paperless-gpt-ocr-auto |
| `LOG_LEVEL` | Application log level (`info`, `debug`, `warn`, `error`). | No | info |
| `LISTEN_INTERFACE` | Network interface to listen on. | No | 8080 |
| `AUTO_GENERATE_TITLE` | Generate titles automatically if `paperless-gpt-auto` is used. | No | true |
| `AUTO_GENERATE_TAGS` | Generate tags automatically if `paperless-gpt-auto` is used. | No | true |
| `AUTO_GENERATE_CORRESPONDENTS` | Generate correspondents automatically if `paperless-gpt-auto` is used. | No | true |
| `OCR_LIMIT_PAGES` | Limit the number of pages for OCR. Set to `0` for no limit. | No | 5 |
| `TOKEN_LIMIT` | Maximum tokens allowed for prompts/content. Set to `0` to disable limit. Useful for smaller LLMs. | No | |
| `CORRESPONDENT_BLACK_LIST` | A comma-separated list of names to exclude from the correspondents suggestions. Example: `John Doe, Jane Smith`. | No | |
### Custom Prompt Templates

57
main.go
View file

@ -9,6 +9,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"slices"
"strings"
"sync"
"text/template"
@ -187,6 +188,21 @@ func main() {
ocrProvider: ocrProvider,
}
if app.isOcrEnabled() {
fmt.Printf("Using %s as manual OCR tag\n", manualOcrTag)
fmt.Printf("Using %s as auto OCR tag\n", autoOcrTag)
rawLimitOcrPages := os.Getenv("OCR_LIMIT_PAGES")
if rawLimitOcrPages == "" {
limitOcrPages = 5
} else {
var err error
limitOcrPages, err = strconv.Atoi(rawLimitOcrPages)
if err != nil {
log.Fatalf("Invalid OCR_LIMIT_PAGES value: %v", err)
}
}
}
// Start background process for auto-tagging
go func() {
minBackoffDuration := 10 * time.Second
@ -197,7 +213,7 @@ func main() {
for {
processedCount, err := func() (int, error) {
count := 0
if isOcrEnabled() {
if app.isOcrEnabled() {
ocrCount, err := app.processAutoOcrTagDocuments()
if err != nil {
return 0, fmt.Errorf("error in processAutoOcrTagDocuments: %w", err)
@ -256,7 +272,7 @@ func main() {
// Endpoint to see if user enabled OCR
api.GET("/experimental/ocr", func(c *gin.Context) {
enabled := isOcrEnabled()
enabled := app.isOcrEnabled()
c.JSON(http.StatusOK, gin.H{"enabled": enabled})
})
@ -366,8 +382,8 @@ func initLogger() {
})
}
func isOcrEnabled() bool {
return visionLlmModel != "" && visionLlmProvider != ""
func (app *App) isOcrEnabled() bool {
return app.ocrProvider != nil
}
// validateOrDefaultEnvVars ensures all necessary environment variables are set
@ -385,16 +401,10 @@ func validateOrDefaultEnvVars() {
if manualOcrTag == "" {
manualOcrTag = "paperless-gpt-ocr"
}
if isOcrEnabled() {
fmt.Printf("Using %s as manual OCR tag\n", manualOcrTag)
}
if autoOcrTag == "" {
autoOcrTag = "paperless-gpt-ocr-auto"
}
if isOcrEnabled() {
fmt.Printf("Using %s as auto OCR tag\n", autoOcrTag)
}
if paperlessBaseURL == "" {
log.Fatal("Please set the PAPERLESS_BASE_URL environment variable.")
@ -420,19 +430,6 @@ func validateOrDefaultEnvVars() {
log.Fatal("Please set the OPENAI_API_KEY environment variable for OpenAI provider.")
}
if isOcrEnabled() {
rawLimitOcrPages := os.Getenv("OCR_LIMIT_PAGES")
if rawLimitOcrPages == "" {
limitOcrPages = 5
} else {
var err error
limitOcrPages, err = strconv.Atoi(rawLimitOcrPages)
if err != nil {
log.Fatalf("Invalid OCR_LIMIT_PAGES value: %v", err)
}
}
}
// Initialize token limit from environment variable
if limit := os.Getenv("TOKEN_LIMIT"); limit != "" {
if parsed, err := strconv.Atoi(limit); err == nil {
@ -466,7 +463,14 @@ func (app *App) processAutoTagDocuments() (int, error) {
log.Debugf("Found at least %d remaining documents with tag %s", len(documents), autoTag)
processedCount := 0
for _, document := range documents {
// Skip documents that have the autoOcrTag
if slices.Contains(document.Tags, autoOcrTag) {
log.Debugf("Skipping document %d as it has the OCR tag %s", document.ID, autoOcrTag)
continue
}
docLogger := documentLogger(document.ID)
docLogger.Info("Processing document for auto-tagging")
@ -479,17 +483,18 @@ func (app *App) processAutoTagDocuments() (int, error) {
suggestions, err := app.generateDocumentSuggestions(ctx, suggestionRequest, docLogger)
if err != nil {
return 0, fmt.Errorf("error generating suggestions for document %d: %w", document.ID, err)
return processedCount, fmt.Errorf("error generating suggestions for document %d: %w", document.ID, err)
}
err = app.Client.UpdateDocuments(ctx, suggestions, app.Database, false)
if err != nil {
return 0, fmt.Errorf("error updating document %d: %w", document.ID, err)
return processedCount, fmt.Errorf("error updating document %d: %w", document.ID, err)
}
docLogger.Info("Successfully processed document")
processedCount++
}
return len(documents), nil
return processedCount, nil
}
// processAutoOcrTagDocuments handles the background auto-tagging of OCR documents

177
main_test.go Normal file
View file

@ -0,0 +1,177 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"slices"
"testing"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProcessAutoTagDocuments(t *testing.T) {
// Initialize required global variables
autoTag = "paperless-gpt-auto"
autoOcrTag = "paperless-gpt-ocr-auto"
// Initialize templates
var err error
titleTemplate, err = template.New("title").Funcs(sprig.FuncMap()).Parse("")
require.NoError(t, err)
tagTemplate, err = template.New("tag").Funcs(sprig.FuncMap()).Parse("")
require.NoError(t, err)
correspondentTemplate, err = template.New("correspondent").Funcs(sprig.FuncMap()).Parse("")
require.NoError(t, err)
// Create test environment
env := newTestEnv(t)
defer env.teardown()
// Set up test cases
testCases := []struct {
name string
documents []Document
expectedCount int
expectedError string
updateResponse int // HTTP status code for update response
}{
{
name: "Skip document with autoOcrTag",
documents: []Document{
{
ID: 1,
Title: "Doc with OCR tag",
Tags: []string{autoTag, autoOcrTag},
},
{
ID: 2,
Title: "Doc without OCR tag",
Tags: []string{autoTag},
},
{
ID: 3,
Title: "Doc with OCR tag",
Tags: []string{autoTag, autoOcrTag},
},
},
expectedCount: 1,
updateResponse: http.StatusOK,
},
{
name: "No documents to process",
documents: []Document{},
expectedCount: 0,
updateResponse: http.StatusOK,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Mock the GetAllTags response
env.setMockResponse("/api/tags/", func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"results": []map[string]interface{}{
{"id": 1, "name": autoTag},
{"id": 2, "name": autoOcrTag},
{"id": 3, "name": "other-tag"},
},
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
})
// Mock the GetDocumentsByTags response
env.setMockResponse("/api/documents/", func(w http.ResponseWriter, r *http.Request) {
response := GetDocumentsApiResponse{
Results: make([]GetDocumentApiResponseResult, len(tc.documents)),
}
for i, doc := range tc.documents {
tagIds := make([]int, len(doc.Tags))
for j, tagName := range doc.Tags {
switch tagName {
case autoTag:
tagIds[j] = 1
case autoOcrTag:
tagIds[j] = 2
default:
tagIds[j] = 3
}
}
response.Results[i] = GetDocumentApiResponseResult{
ID: doc.ID,
Title: doc.Title,
Tags: tagIds,
Content: "Test content",
}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
})
// Mock the correspondent creation endpoint
env.setMockResponse("/api/correspondents/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
// Mock successful correspondent creation
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]interface{}{
"id": 3,
"name": "test response",
})
} else {
// Mock GET response for existing correspondents
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"results": []map[string]interface{}{
{"id": 1, "name": "Alpha"},
{"id": 2, "name": "Beta"},
},
})
}
})
// Create test app
app := &App{
Client: env.client,
Database: env.db,
LLM: &mockLLM{}, // Use mock LLM from app_llm_test.go
}
// Set auto-generate flags
autoGenerateTitle = "true"
autoGenerateTags = "true"
autoGenerateCorrespondents = "true"
// Mock the document update responses
for _, doc := range tc.documents {
if !slices.Contains(doc.Tags, autoOcrTag) {
updatePath := fmt.Sprintf("/api/documents/%d/", doc.ID)
env.setMockResponse(updatePath, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(tc.updateResponse)
json.NewEncoder(w).Encode(map[string]interface{}{
"id": doc.ID,
"title": "Updated " + doc.Title,
"tags": []int{1, 3}, // Mock updated tag IDs
})
})
}
}
// Run the test
count, err := app.processAutoTagDocuments()
// Verify results
if tc.expectedError != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.expectedError)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedCount, count)
}
})
}
}

View file

@ -1663,17 +1663,17 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz",
"integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz",
"integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/type-utils": "8.23.0",
"@typescript-eslint/utils": "8.23.0",
"@typescript-eslint/visitor-keys": "8.23.0",
"@typescript-eslint/scope-manager": "8.24.0",
"@typescript-eslint/type-utils": "8.24.0",
"@typescript-eslint/utils": "8.24.0",
"@typescript-eslint/visitor-keys": "8.24.0",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@ -1693,16 +1693,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz",
"integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz",
"integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/visitor-keys": "8.23.0",
"@typescript-eslint/scope-manager": "8.24.0",
"@typescript-eslint/types": "8.24.0",
"@typescript-eslint/typescript-estree": "8.24.0",
"@typescript-eslint/visitor-keys": "8.24.0",
"debug": "^4.3.4"
},
"engines": {
@ -1718,14 +1718,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz",
"integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz",
"integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.23.0"
"@typescript-eslint/types": "8.24.0",
"@typescript-eslint/visitor-keys": "8.24.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1736,14 +1736,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz",
"integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz",
"integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "8.23.0",
"@typescript-eslint/utils": "8.23.0",
"@typescript-eslint/typescript-estree": "8.24.0",
"@typescript-eslint/utils": "8.24.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.0.1"
},
@ -1760,9 +1760,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz",
"integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz",
"integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==",
"dev": true,
"license": "MIT",
"engines": {
@ -1774,14 +1774,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz",
"integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz",
"integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.23.0",
"@typescript-eslint/visitor-keys": "8.23.0",
"@typescript-eslint/types": "8.24.0",
"@typescript-eslint/visitor-keys": "8.24.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -1827,16 +1827,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz",
"integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz",
"integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.23.0",
"@typescript-eslint/typescript-estree": "8.23.0"
"@typescript-eslint/scope-manager": "8.24.0",
"@typescript-eslint/types": "8.24.0",
"@typescript-eslint/typescript-estree": "8.24.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1851,13 +1851,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz",
"integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz",
"integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.23.0",
"@typescript-eslint/types": "8.24.0",
"eslint-visitor-keys": "^4.2.0"
},
"engines": {
@ -2983,9 +2983,9 @@
}
},
"node_modules/eslint": {
"version": "9.20.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.0.tgz",
"integrity": "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==",
"version": "9.20.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz",
"integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4942,9 +4942,9 @@
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"license": "ISC",
"bin": {
@ -5460,15 +5460,15 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.23.0.tgz",
"integrity": "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==",
"version": "8.24.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.0.tgz",
"integrity": "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.23.0",
"@typescript-eslint/parser": "8.23.0",
"@typescript-eslint/utils": "8.23.0"
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"@typescript-eslint/utils": "8.24.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"