mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 22:29:24 +00:00
Merge remote-tracking branch 'origin/react-overhaul-tauri-integration' into infra/tauri-actions
This commit is contained in:
commit
df9cbf1986
@ -5,7 +5,8 @@
|
|||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"Bash(./gradlew:*)",
|
"Bash(./gradlew:*)",
|
||||||
"Bash(grep:*)",
|
"Bash(grep:*)",
|
||||||
"Bash(cat:*)"
|
"Bash(cat:*)",
|
||||||
|
"Bash(find:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,9 @@
|
|||||||
"EditorConfig.EditorConfig", // EditorConfig support for maintaining consistent coding styles
|
"EditorConfig.EditorConfig", // EditorConfig support for maintaining consistent coding styles
|
||||||
"ms-azuretools.vscode-docker", // Docker extension for Visual Studio Code
|
"ms-azuretools.vscode-docker", // Docker extension for Visual Studio Code
|
||||||
"charliermarsh.ruff", // Ruff extension for Ruff language support
|
"charliermarsh.ruff", // Ruff extension for Ruff language support
|
||||||
"github.vscode-github-actions" // GitHub Actions extension for Visual Studio Code
|
"github.vscode-github-actions", // GitHub Actions extension for Visual Studio Code
|
||||||
|
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
||||||
|
"redhat.vscode-yaml" // YAML extension for Visual Studio Code
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
# Formatting
|
# Formatting
|
||||||
5f771b785130154ed47952635b7acef371ffe0ec
|
5f771b785130154ed47952635b7acef371ffe0ec
|
||||||
|
7fa5e130d99227c2202ebddfdd91348176ec0c7b
|
||||||
|
14d4fbb2a36195eedb034785e5a5ff6a47f268c6
|
||||||
|
ee8030c1c4148062cde15c49c67d04ef03930c55
|
||||||
|
fcd41924f5f261febfa9d9a92994671f3ebc97d6
|
||||||
|
|
||||||
# Normalize files
|
# Normalize files
|
||||||
55d4fda01b2f39f5b7d7b4fda5214bd7ff0fd5dd
|
55d4fda01b2f39f5b7d7b4fda5214bd7ff0fd5dd
|
||||||
|
14
.gitattributes
vendored
14
.gitattributes
vendored
@ -1,10 +1,10 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
|
|
||||||
# Ignore all JavaScript files in a directory
|
# Ignore all JavaScript files in a directory
|
||||||
stirling-pdf/src/main/resources/static/pdfjs/* linguist-vendored
|
app/core/src/main/resources/static/pdfjs/* linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/pdfjs/** linguist-vendored
|
app/core/src/main/resources/static/pdfjs/** linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/pdfjs-legacy/* linguist-vendored
|
app/core/src/main/resources/static/pdfjs-legacy/* linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/pdfjs-legacy/** linguist-vendored
|
app/core/src/main/resources/static/pdfjs-legacy/** linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
app/core/src/main/resources/static/css/bootstrap-icons.css linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
app/core/src/main/resources/static/css/bootstrap.min.css linguist-vendored
|
||||||
stirling-pdf/src/main/resources/static/css/fonts/* linguist-vendored
|
app/core/src/main/resources/static/css/fonts/* linguist-vendored
|
||||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1,2 +1,2 @@
|
|||||||
# All PRs to V1 must be approved by Frooodle
|
# All PRs to V1 must be approved by Frooodle
|
||||||
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh
|
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh @EthanHealy01
|
||||||
|
29
.github/config/.files.yaml
vendored
Normal file
29
.github/config/.files.yaml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
build: &build
|
||||||
|
- build.gradle
|
||||||
|
- app/(common|core|proprietary)/build.gradle
|
||||||
|
|
||||||
|
app: &app
|
||||||
|
- app/(common|core|proprietary)/src/main/java/**
|
||||||
|
|
||||||
|
openapi: &openapi
|
||||||
|
- build.gradle
|
||||||
|
- app/(common|core|proprietary)/build.gradle
|
||||||
|
- app/(common|core|proprietary)/src/main/java/**
|
||||||
|
|
||||||
|
project: &project
|
||||||
|
- app/(common|core|proprietary)/src/(main|test)/java/**
|
||||||
|
- app/(common|core|proprietary)/build.gradle
|
||||||
|
- 'app/(common|core|proprietary)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*'
|
||||||
|
- exampleYmlFiles/**
|
||||||
|
- gradle/**
|
||||||
|
- libs/**
|
||||||
|
- testing/**
|
||||||
|
- build.gradle
|
||||||
|
- Dockerfile
|
||||||
|
- Dockerfile.fat
|
||||||
|
- Dockerfile.ultra-lite
|
||||||
|
- gradle.properties
|
||||||
|
- gradlew
|
||||||
|
- gradlew.bat
|
||||||
|
- launch4jConfig.xml
|
||||||
|
- settings.gradle
|
1
.github/config/dependency-review-config.yml
vendored
Normal file
1
.github/config/dependency-review-config.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
allow-ghsas: GHSA-wrw7-89jp-8q8g
|
3
.github/config/repo_devs.json
vendored
3
.github/config/repo_devs.json
vendored
@ -7,6 +7,7 @@
|
|||||||
"sbplat",
|
"sbplat",
|
||||||
"reecebrowne",
|
"reecebrowne",
|
||||||
"DarioGii",
|
"DarioGii",
|
||||||
"ConnorYoh"
|
"ConnorYoh",
|
||||||
|
"EthanHealy01"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
105
.github/labeler-config-srvaroa.yml
vendored
105
.github/labeler-config-srvaroa.yml
vendored
@ -2,88 +2,101 @@ version: 1
|
|||||||
labels:
|
labels:
|
||||||
|
|
||||||
- label: "Bugfix"
|
- label: "Bugfix"
|
||||||
title: '^fix:.*'
|
title: '^fix(\([^)]*\))?:|^fix:.*'
|
||||||
|
|
||||||
- label: "enhancement"
|
- label: "enhancement"
|
||||||
title: '^feat:.*'
|
title: '^feat(\([^)]*\))?:|^feat:.*'
|
||||||
|
|
||||||
- label: "build"
|
- label: "build"
|
||||||
title: '^build:.*'
|
title: '^build(\([^)]*\))?:|^build:.*'
|
||||||
|
|
||||||
- label: "chore"
|
- label: "chore"
|
||||||
title: '^chore:.*'
|
title: '^chore(\([^)]*\))?:|^chore:.*'
|
||||||
|
|
||||||
- label: "ci"
|
- label: "ci"
|
||||||
title: '^ci:.*'
|
title: '^ci(\([^)]*\))?:|^ci:.*'
|
||||||
|
|
||||||
|
- label: "ci"
|
||||||
|
title: '^.*\(ci\):.*'
|
||||||
|
|
||||||
- label: "perf"
|
- label: "perf"
|
||||||
title: '^perf:.*'
|
title: '^perf(\([^)]*\))?:|^perf:.*'
|
||||||
|
|
||||||
- label: "refactor"
|
- label: "refactor"
|
||||||
title: '^refactor:.*'
|
title: '^refactor(\([^)]*\))?:|^refactor:.*'
|
||||||
|
|
||||||
- label: "revert"
|
- label: "revert"
|
||||||
title: '^revert:.*'
|
title: '^revert(\([^)]*\))?:|^revert:.*'
|
||||||
|
|
||||||
- label: "style"
|
- label: "style"
|
||||||
title: '^style:.*'
|
title: '^style(\([^)]*\))?:|^style:.*'
|
||||||
|
|
||||||
- label: "Documentation"
|
- label: "Documentation"
|
||||||
title: '^docs:.*'
|
title: '^docs(\([^)]*\))?:|^docs:.*'
|
||||||
|
|
||||||
|
- label: "Documentation"
|
||||||
|
title: '^.*\(docs\):.*'
|
||||||
|
|
||||||
|
- label: "dependencies"
|
||||||
|
title: '^deps(\([^)]*\))?:|^deps:.*'
|
||||||
|
|
||||||
|
- label: "dependencies"
|
||||||
|
title: '^.*\(deps\):.*'
|
||||||
|
|
||||||
- label: 'API'
|
- label: 'API'
|
||||||
title: '.*openapi.*'
|
title: '.*openapi.*|.*swagger.*|.*api.*'
|
||||||
|
|
||||||
- label: 'Translation'
|
- label: 'Translation'
|
||||||
files:
|
files:
|
||||||
- 'stirling-pdf/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}.properties'
|
- 'app/core/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}.properties'
|
||||||
- 'scripts/ignore_translation.toml'
|
- 'scripts/ignore_translation.toml'
|
||||||
- 'stirling-pdf/src/main/resources/templates/fragments/languages.html'
|
- 'app/core/src/main/resources/templates/fragments/languages.html'
|
||||||
- '.github/scripts/check_language_properties.py'
|
- '.github/scripts/check_language_properties.py'
|
||||||
|
|
||||||
- label: 'Front End'
|
- label: 'Front End'
|
||||||
files:
|
files:
|
||||||
- 'stirling-pdf/src/main/resources/templates/.*'
|
- 'app/core/src/main/resources/templates/.*'
|
||||||
- 'proprietary/src/main/resources/templates/.*'
|
- 'app/proprietary/src/main/resources/templates/.*'
|
||||||
- 'stirling-pdf/src/main/resources/static/.*'
|
- 'app/core/src/main/resources/static/.*'
|
||||||
- 'proprietary/src/main/resources/static/.*'
|
- 'app/proprietary/src/main/resources/static/.*'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/controller/web/.*'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/UI/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/UI/.*'
|
||||||
- 'proprietary/src/main/java/stirling/software/proprietary/security/controller/web/.*'
|
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/.*'
|
||||||
|
|
||||||
- label: 'Java'
|
- label: 'Java'
|
||||||
files:
|
files:
|
||||||
- 'common/src/main/java/.*.java'
|
- 'app/common/src/main/java/.*.java'
|
||||||
- 'proprietary/src/main/java/.*.java'
|
- 'app/proprietary/src/main/java/.*.java'
|
||||||
- 'stirling-pdf/src/main/java/.*.java'
|
- 'app/core/src/main/java/.*.java'
|
||||||
|
|
||||||
- label: 'Back End'
|
- label: 'Back End'
|
||||||
files:
|
files:
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/config/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/config/.*'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/controller/.*'
|
||||||
- 'stirling-pdf/src/main/resources/settings.yml.template'
|
- 'app/core/src/main/resources/settings.yml.template'
|
||||||
- 'stirling-pdf/src/main/resources/application.properties'
|
- 'app/core/src/main/resources/application.properties'
|
||||||
- 'stirling-pdf/src/main/resources/banner.txt'
|
- 'app/core/src/main/resources/banner.txt'
|
||||||
- 'scripts/png_to_webp.py'
|
- 'app/core/src/main/resources/static/python/png_to_webp.py'
|
||||||
- 'split_photos.py'
|
- 'app/core/src/main/resources/static/python/split_photos.py'
|
||||||
- 'application.properties'
|
- 'application.properties'
|
||||||
|
|
||||||
- label: 'Security'
|
- label: 'Security'
|
||||||
files:
|
files:
|
||||||
- 'proprietary/src/main/java/stirling/software/proprietary/security/.*'
|
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/.*'
|
||||||
- 'scripts/download-security-jar.sh'
|
- 'scripts/download-security-jar.sh'
|
||||||
- '.github/workflows/dependency-review.yml'
|
- '.github/workflows/dependency-review.yml'
|
||||||
- '.github/workflows/scorecards.yml'
|
- '.github/workflows/scorecards.yml'
|
||||||
|
|
||||||
- label: 'API'
|
- label: 'API'
|
||||||
files:
|
files:
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
- 'app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
- 'app/core/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/controller/api/.*'
|
||||||
- 'stirling-pdf/src/main/java/stirling/software/SPDF/model/api/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/model/api/.*'
|
||||||
- 'proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*'
|
- 'app/core/src/main/java/stirling/software/SPDF/service/ApiDocService.java'
|
||||||
- 'scripts/png_to_webp.py'
|
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*'
|
||||||
- 'split_photos.py'
|
- 'app/core/src/main/resources/static/python/png_to_webp.py'
|
||||||
|
- 'app/core/src/main/resources/static/python/split_photos.py'
|
||||||
- '.github/workflows/swagger.yml'
|
- '.github/workflows/swagger.yml'
|
||||||
|
|
||||||
- label: 'Documentation'
|
- label: 'Documentation'
|
||||||
@ -115,13 +128,15 @@ labels:
|
|||||||
- '.editorconfig'
|
- '.editorconfig'
|
||||||
- '.pre-commit-config'
|
- '.pre-commit-config'
|
||||||
- '.github/workflows/pre_commit.yml'
|
- '.github/workflows/pre_commit.yml'
|
||||||
- 'HowToAddNewLanguage.md'
|
- 'devGuide/.*'
|
||||||
|
- 'devTools/.*'
|
||||||
|
- 'devTools/.*'
|
||||||
|
|
||||||
- label: 'Test'
|
- label: 'Test'
|
||||||
files:
|
files:
|
||||||
- 'common/src/test/.*'
|
- 'app/common/src/test/.*'
|
||||||
- 'proprietary/src/test/.*'
|
- 'app/proprietary/src/test/.*'
|
||||||
- 'stirling-pdf/src/test/.*'
|
- 'app/core/src/test/.*'
|
||||||
- 'testing/.*'
|
- 'testing/.*'
|
||||||
- '.github/workflows/scorecards.yml'
|
- '.github/workflows/scorecards.yml'
|
||||||
- 'exampleYmlFiles/test_cicd.yml'
|
- 'exampleYmlFiles/test_cicd.yml'
|
||||||
@ -137,6 +152,6 @@ labels:
|
|||||||
- 'gradlew.bat'
|
- 'gradlew.bat'
|
||||||
- 'settings.gradle'
|
- 'settings.gradle'
|
||||||
- 'build.gradle'
|
- 'build.gradle'
|
||||||
- 'common/build.gradle'
|
- 'app/common/build.gradle'
|
||||||
- 'proprietary/build.gradle'
|
- 'app/proprietary/build.gradle'
|
||||||
- 'stirling-pdf/build.gradle'
|
- 'app/core/build.gradle'
|
||||||
|
86
.github/labeler-config.yml
vendored
86
.github/labeler-config.yml
vendored
@ -1,86 +0,0 @@
|
|||||||
Translation:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/messages_*_*.properties'
|
|
||||||
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/fragments/languages.html'
|
|
||||||
|
|
||||||
Front End:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/templates/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/static/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/**'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/UI/**/*'
|
|
||||||
|
|
||||||
Java:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'common/src/main/java/**/*.java'
|
|
||||||
- any-glob-to-any-file: 'proprietary/src/main/java/**/*.java'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/**/*.java'
|
|
||||||
|
|
||||||
Back End:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/settings.yml.template'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/application.properties'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/resources/banner.txt'
|
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
|
||||||
|
|
||||||
Security:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'proprietary/src/main/java/stirling/software/proprietary/security/**/*'
|
|
||||||
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/dependency-review.yml'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
|
||||||
|
|
||||||
API:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/controller/api/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/main/java/stirling/software/SPDF/model/api/**/*'
|
|
||||||
- any-glob-to-any-file: 'scripts/png_to_webp.py'
|
|
||||||
- any-glob-to-any-file: 'split_photos.py'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/swagger.yml'
|
|
||||||
|
|
||||||
Documentation:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: '**/*.md'
|
|
||||||
- any-glob-to-any-file: 'scripts/counter_translation.py'
|
|
||||||
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
|
|
||||||
|
|
||||||
Docker:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: '.github/workflows/build.yml'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/push-docker.yml'
|
|
||||||
- any-glob-to-any-file: 'Dockerfile'
|
|
||||||
- any-glob-to-any-file: 'Dockerfile.fat'
|
|
||||||
- any-glob-to-any-file: 'Dockerfile.ultra-lite'
|
|
||||||
- any-glob-to-any-file: 'exampleYmlFiles/*.yml'
|
|
||||||
- any-glob-to-any-file: 'scripts/download-security-jar.sh'
|
|
||||||
- any-glob-to-any-file: 'scripts/init.sh'
|
|
||||||
- any-glob-to-any-file: 'scripts/init-without-ocr.sh'
|
|
||||||
- any-glob-to-any-file: 'scripts/installFonts.sh'
|
|
||||||
- any-glob-to-any-file: 'test.sh'
|
|
||||||
- any-glob-to-any-file: 'test2.sh'
|
|
||||||
|
|
||||||
Devtools:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: '.devcontainer/**/*'
|
|
||||||
- any-glob-to-any-file: 'Dockerfile.dev'
|
|
||||||
|
|
||||||
Test:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: 'cucumber/**/*'
|
|
||||||
- any-glob-to-any-file: 'common/src/test/**/*'
|
|
||||||
- any-glob-to-any-file: 'proprietary/src/test/**/*'
|
|
||||||
- any-glob-to-any-file: 'stirling-pdf/src/test/**/*'
|
|
||||||
- any-glob-to-any-file: 'src/testing/**/*'
|
|
||||||
- any-glob-to-any-file: '.pre-commit-config'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/pre_commit.yml'
|
|
||||||
- any-glob-to-any-file: '.github/workflows/scorecards.yml'
|
|
||||||
|
|
||||||
Github:
|
|
||||||
- changed-files:
|
|
||||||
- any-glob-to-any-file: '.github/**/*'
|
|
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@ -175,3 +175,6 @@
|
|||||||
description: "This PR changes 1000+ lines ignoring generated files."
|
description: "This PR changes 1000+ lines ignoring generated files."
|
||||||
- name: "to research"
|
- name: "to research"
|
||||||
color: "FBCA04"
|
color: "FBCA04"
|
||||||
|
- name: "pr-deployed"
|
||||||
|
color: "00FF00"
|
||||||
|
description: "Pull request has been deployed to a test environment"
|
||||||
|
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
@ -1,5 +1,6 @@
|
|||||||
# Description of Changes
|
# Description of Changes
|
||||||
|
|
||||||
|
<!--
|
||||||
Please provide a summary of the changes, including:
|
Please provide a summary of the changes, including:
|
||||||
|
|
||||||
- What was changed
|
- What was changed
|
||||||
@ -7,6 +8,7 @@ Please provide a summary of the changes, including:
|
|||||||
- Any challenges encountered
|
- Any challenges encountered
|
||||||
|
|
||||||
Closes #(issue_number)
|
Closes #(issue_number)
|
||||||
|
-->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -15,15 +17,15 @@ Closes #(issue_number)
|
|||||||
### General
|
### General
|
||||||
|
|
||||||
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
- [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
||||||
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable)
|
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable)
|
||||||
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable)
|
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable)
|
||||||
- [ ] I have performed a self-review of my own code
|
- [ ] I have performed a self-review of my own code
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed)
|
- [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed)
|
||||||
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
- [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only)
|
||||||
|
|
||||||
### UI Changes (if applicable)
|
### UI Changes (if applicable)
|
||||||
|
|
||||||
@ -31,4 +33,4 @@ Closes #(issue_number)
|
|||||||
|
|
||||||
### Testing (if applicable)
|
### Testing (if applicable)
|
||||||
|
|
||||||
- [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
|
- [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
|
||||||
|
12
.github/scripts/check_language_properties.py
vendored
12
.github/scripts/check_language_properties.py
vendored
@ -197,7 +197,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
if len(file_list) == 1:
|
if len(file_list) == 1:
|
||||||
file_arr = file_list[0].split()
|
file_arr = file_list[0].split()
|
||||||
base_dir = os.path.abspath(
|
base_dir = os.path.abspath(
|
||||||
os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")
|
os.path.join(os.getcwd(), "app", "core", "src", "main", "resources")
|
||||||
)
|
)
|
||||||
|
|
||||||
for file_path in file_arr:
|
for file_path in file_arr:
|
||||||
@ -219,13 +219,14 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
# only local windows command
|
# only local windows command
|
||||||
not file_normpath.startswith(
|
not file_normpath.startswith(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
"", "stirling-pdf", "src", "main", "resources", "messages_"
|
"", "app", "core", "src", "main", "resources", "messages_"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
and not file_normpath.startswith(
|
and not file_normpath.startswith(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
os.getcwd(),
|
os.getcwd(),
|
||||||
"stirling-pdf",
|
"app",
|
||||||
|
"core",
|
||||||
"src",
|
"src",
|
||||||
"main",
|
"main",
|
||||||
"resources",
|
"resources",
|
||||||
@ -328,7 +329,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
|
|||||||
report.append("## ❌ Overall Check Status: **_Failed_**")
|
report.append("## ❌ Overall Check Status: **_Failed_**")
|
||||||
report.append("")
|
report.append("")
|
||||||
report.append(
|
report.append(
|
||||||
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/stirling-pdf/src/main/resources/messages_en_GB.properties)"
|
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/core/src/main/resources/messages_en_GB.properties)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
report.append("## ✅ Overall Check Status: **_Success_**")
|
report.append("## ✅ Overall Check Status: **_Success_**")
|
||||||
@ -389,7 +390,8 @@ if __name__ == "__main__":
|
|||||||
file_list = glob.glob(
|
file_list = glob.glob(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
os.getcwd(),
|
os.getcwd(),
|
||||||
"stirling-pdf",
|
"app",
|
||||||
|
"core",
|
||||||
"src",
|
"src",
|
||||||
"main",
|
"main",
|
||||||
"resources",
|
"resources",
|
||||||
|
32
.github/scripts/requirements_pre_commit.txt
vendored
32
.github/scripts/requirements_pre_commit.txt
vendored
@ -2,7 +2,7 @@
|
|||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_pre_commit.txt' '.github\scripts\requirements_pre_commit.in'
|
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_pre_commit.txt' --strip-extras '.github\scripts\requirements_pre_commit.in'
|
||||||
#
|
#
|
||||||
cfgv==3.4.0 \
|
cfgv==3.4.0 \
|
||||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||||
@ -12,25 +12,25 @@ distlib==0.3.9 \
|
|||||||
--hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \
|
--hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \
|
||||||
--hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403
|
--hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
filelock==3.16.1 \
|
filelock==3.18.0 \
|
||||||
--hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \
|
--hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \
|
||||||
--hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435
|
--hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
identify==2.6.5 \
|
identify==2.6.12 \
|
||||||
--hash=sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566 \
|
--hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \
|
||||||
--hash=sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc
|
--hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
nodeenv==1.9.1 \
|
nodeenv==1.9.1 \
|
||||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
platformdirs==4.3.6 \
|
platformdirs==4.3.8 \
|
||||||
--hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \
|
--hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \
|
||||||
--hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb
|
--hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
pre-commit==4.0.1 \
|
pre-commit==4.2.0 \
|
||||||
--hash=sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2 \
|
--hash=sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146 \
|
||||||
--hash=sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878
|
--hash=sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd
|
||||||
# via -r .github\scripts\requirements_pre_commit.in
|
# via -r .github\scripts\requirements_pre_commit.in
|
||||||
pyyaml==6.0.2 \
|
pyyaml==6.0.2 \
|
||||||
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
|
||||||
@ -87,7 +87,7 @@ pyyaml==6.0.2 \
|
|||||||
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
|
||||||
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
virtualenv==20.28.1 \
|
virtualenv==20.31.2 \
|
||||||
--hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \
|
--hash=sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11 \
|
||||||
--hash=sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329
|
--hash=sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
|
8
.github/scripts/requirements_sync_readme.txt
vendored
8
.github/scripts/requirements_sync_readme.txt
vendored
@ -2,9 +2,9 @@
|
|||||||
# This file is autogenerated by pip-compile with Python 3.10
|
# This file is autogenerated by pip-compile with Python 3.10
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_sync_readme.txt' '.github\scripts\requirements_sync_readme.in'
|
# pip-compile --generate-hashes --output-file='.github\scripts\requirements_sync_readme.txt' --strip-extras '.github\scripts\requirements_sync_readme.in'
|
||||||
#
|
#
|
||||||
tomlkit==0.13.2 \
|
tomlkit==0.13.3 \
|
||||||
--hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \
|
--hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \
|
||||||
--hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79
|
--hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0
|
||||||
# via -r .github\scripts\requirements_sync_readme.in
|
# via -r .github\scripts\requirements_sync_readme.in
|
||||||
|
500
.github/workflows/PR-Auto-Deploy-V2.yml
vendored
Normal file
500
.github/workflows/PR-Auto-Deploy-V2.yml
vendored
Normal file
@ -0,0 +1,500 @@
|
|||||||
|
name: Auto PR V2 Deployment
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, closed]
|
||||||
|
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-pr:
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
should_deploy: ${{ steps.check-conditions.outputs.should_deploy }}
|
||||||
|
pr_number: ${{ github.event.number }}
|
||||||
|
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
||||||
|
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Check deployment conditions
|
||||||
|
id: check-conditions
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
run: |
|
||||||
|
echo "PR Title: $PR_TITLE"
|
||||||
|
echo "PR Author: $PR_AUTHOR"
|
||||||
|
echo "PR Branch: $PR_BRANCH"
|
||||||
|
echo "PR Base Branch: ${{ github.event.pull_request.base.ref }}"
|
||||||
|
|
||||||
|
# Define authorized users
|
||||||
|
authorized_users=(
|
||||||
|
"Frooodle"
|
||||||
|
"sf298"
|
||||||
|
"Ludy87"
|
||||||
|
"LaserKaspar"
|
||||||
|
"sbplat"
|
||||||
|
"reecebrowne"
|
||||||
|
"DarioGii"
|
||||||
|
"ConnorYoh"
|
||||||
|
"EthanHealy01"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if author is in the authorized list
|
||||||
|
is_authorized=false
|
||||||
|
for user in "${authorized_users[@]}"; do
|
||||||
|
if [[ "$PR_AUTHOR" == "$user" ]]; then
|
||||||
|
is_authorized=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If PR is targeting V2 and user is authorized, deploy unconditionally
|
||||||
|
PR_BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
|
||||||
|
if [[ "$PR_BASE_BRANCH" == "V2" && "$is_authorized" == "true" ]]; then
|
||||||
|
echo "✅ Deployment forced: PR targets V2 and author is authorized."
|
||||||
|
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Otherwise, continue with original keyword checks
|
||||||
|
has_v2_keyword=false
|
||||||
|
if [[ "$PR_TITLE" =~ [Vv]2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?[Tt]wo ]]; then
|
||||||
|
has_v2_keyword=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
has_branch_keyword=false
|
||||||
|
if [[ "$PR_BRANCH" =~ [Vv]2 ]] || [[ "$PR_BRANCH" =~ [Rr]eact ]]; then
|
||||||
|
has_branch_keyword=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$is_authorized" == "true" && ( "$has_v2_keyword" == "true" || "$has_branch_keyword" == "true" ) ]]; then
|
||||||
|
echo "✅ Deployment conditions met"
|
||||||
|
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "❌ Deployment conditions not met"
|
||||||
|
echo " - Authorized user: $is_authorized"
|
||||||
|
echo " - Has V2 keyword in title: $has_v2_keyword"
|
||||||
|
echo " - Has V2/React keyword in branch: $has_branch_keyword"
|
||||||
|
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get PR repository and ref
|
||||||
|
id: get-pr-info
|
||||||
|
if: steps.check-conditions.outputs.should_deploy == 'true'
|
||||||
|
run: |
|
||||||
|
# For forks, use the full repository name, for internal PRs use the current repo
|
||||||
|
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
|
||||||
|
repository="${{ github.event.pull_request.head.repo.full_name }}"
|
||||||
|
else
|
||||||
|
repository="${{ github.repository }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "repository=$repository" >> $GITHUB_OUTPUT
|
||||||
|
echo "ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
deploy-v2-pr:
|
||||||
|
needs: check-pr
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: needs.check-pr.outputs.should_deploy == 'true'
|
||||||
|
# Concurrency control - only one deployment per PR at a time
|
||||||
|
concurrency:
|
||||||
|
group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout main repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Add deployment started comment
|
||||||
|
id: deployment-started
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||||
|
|
||||||
|
// Delete previous V2 deployment comments to avoid clutter
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const v2Comments = comments.filter(comment =>
|
||||||
|
comment.body.includes('🚀 **Auto-deploying V2 version**') ||
|
||||||
|
comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') ||
|
||||||
|
comment.body.includes('❌ **V2 Auto-deployment failed**')
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const comment of v2Comments) {
|
||||||
|
console.log(`Deleting old V2 comment: ${comment.id}`);
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new deployment started comment
|
||||||
|
const { data: newComment } = await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.`
|
||||||
|
});
|
||||||
|
|
||||||
|
return newComment.id;
|
||||||
|
|
||||||
|
- name: Checkout PR
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: ${{ needs.check-pr.outputs.pr_repository }}
|
||||||
|
ref: ${{ needs.check-pr.outputs.pr_ref }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0 # Fetch full history for commit hash detection
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
|
- name: Get commit hashes for frontend and backend
|
||||||
|
id: commit-hashes
|
||||||
|
run: |
|
||||||
|
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
|
||||||
|
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$FRONTEND_HASH" ]; then
|
||||||
|
FRONTEND_HASH="no-frontend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get last commit that touched backend code, docker/backend, or docker/compose
|
||||||
|
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$BACKEND_HASH" ]; then
|
||||||
|
BACKEND_HASH="no-backend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Frontend hash: $FRONTEND_HASH"
|
||||||
|
echo "Backend hash: $BACKEND_HASH"
|
||||||
|
|
||||||
|
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Short hashes for tags
|
||||||
|
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
|
||||||
|
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
|
||||||
|
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if frontend image exists
|
||||||
|
id: check-frontend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if backend image exists
|
||||||
|
id: check-backend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push V2 frontend image
|
||||||
|
if: steps.check-frontend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/frontend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Build and push V2 backend image
|
||||||
|
if: steps.check-backend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/backend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Deploy V2 to VPS
|
||||||
|
id: deploy
|
||||||
|
run: |
|
||||||
|
# Use same port strategy as regular PRs - just the PR number
|
||||||
|
V2_PORT=${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
BACKEND_PORT=$((V2_PORT + 10000)) # Backend on higher port to avoid conflicts
|
||||||
|
|
||||||
|
# Create docker-compose for V2 with separate frontend and backend
|
||||||
|
cat > docker-compose.yml << EOF
|
||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf-v2-backend:
|
||||||
|
container_name: stirling-pdf-v2-backend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
ports:
|
||||||
|
- "${BACKEND_PORT}:8080" # Backend API port
|
||||||
|
volumes:
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
|
UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||||
|
UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Frontend/Backend Split Architecture"
|
||||||
|
UI_APPNAMENAVBAR: "V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||||
|
restart: on-failure:5
|
||||||
|
|
||||||
|
stirling-pdf-v2-frontend:
|
||||||
|
container_name: stirling-pdf-v2-frontend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
ports:
|
||||||
|
- "${V2_PORT}:80" # Frontend port (same as regular PRs)
|
||||||
|
environment:
|
||||||
|
VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:${BACKEND_PORT}"
|
||||||
|
depends_on:
|
||||||
|
- stirling-pdf-v2-backend
|
||||||
|
restart: on-failure:5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy to VPS
|
||||||
|
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose-v2.yml
|
||||||
|
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
|
||||||
|
# Create V2 PR-specific directories
|
||||||
|
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs}
|
||||||
|
|
||||||
|
# Move docker-compose file to correct location
|
||||||
|
mv /tmp/docker-compose-v2.yml /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml
|
||||||
|
|
||||||
|
# Stop any existing container and clean up
|
||||||
|
cd /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
docker-compose down --remove-orphans 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start the new container
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Clean up unused Docker resources to save space
|
||||||
|
docker system prune -af --volumes
|
||||||
|
|
||||||
|
# Clean up old backend/frontend images (older than 2 weeks)
|
||||||
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
# Set port for output
|
||||||
|
echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Post V2 deployment URL to PR
|
||||||
|
if: success()
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||||
|
const v2Port = ${{ steps.deploy.outputs.v2_port }};
|
||||||
|
|
||||||
|
// Delete the "deploying..." comment since we're posting the final result
|
||||||
|
const deploymentStartedId = ${{ steps.deployment-started.outputs.result }};
|
||||||
|
if (deploymentStartedId) {
|
||||||
|
console.log(`Deleting deployment started comment: ${deploymentStartedId}`);
|
||||||
|
try {
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: deploymentStartedId
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Could not delete deployment started comment: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${v2Port}`;
|
||||||
|
|
||||||
|
const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
|
||||||
|
`Your V2 PR with the new frontend/backend split architecture has been deployed!\n\n` +
|
||||||
|
`🔗 **V2 Test URL:** [${deploymentUrl}](${deploymentUrl})\n\n` +
|
||||||
|
`_This deployment will be automatically cleaned up when the PR is closed._\n\n` +
|
||||||
|
`🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanup-v2-deployment:
|
||||||
|
if: github.event.action == 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Clean up V2 deployment comments
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ github.event.pull_request.number }};
|
||||||
|
|
||||||
|
// Find and delete V2 deployment comments
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const v2Comments = comments.filter(c =>
|
||||||
|
c.body?.includes("## 🚀 V2 Auto-Deployment Complete!") &&
|
||||||
|
c.user?.type === "Bot"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const comment of v2Comments) {
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
console.log(`Deleted V2 deployment comment (ID: ${comment.id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Cleanup V2 deployment
|
||||||
|
run: |
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||||
|
if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
|
||||||
|
echo "Found V2 PR directory, proceeding with cleanup..."
|
||||||
|
|
||||||
|
# Stop and remove V2 containers
|
||||||
|
cd /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||||
|
docker-compose down || true
|
||||||
|
|
||||||
|
# Go back to root before removal
|
||||||
|
cd /
|
||||||
|
|
||||||
|
# Remove V2 PR-specific directories
|
||||||
|
rm -rf /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
# Clean up V2 containers by name (in case compose cleanup missed them)
|
||||||
|
docker rm -f stirling-pdf-v2-frontend-pr-${{ github.event.pull_request.number }} || true
|
||||||
|
docker rm -f stirling-pdf-v2-backend-pr-${{ github.event.pull_request.number }} || true
|
||||||
|
|
||||||
|
echo "V2 cleanup completed"
|
||||||
|
else
|
||||||
|
echo "V2 PR directory not found, nothing to clean up"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
|
||||||
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true"
|
||||||
|
|
||||||
|
# Note: We don't remove the commit-based images since they can be reused across PRs
|
||||||
|
# Only remove PR-specific containers and directories
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Cleanup temporary files
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
rm -f ../private.key
|
||||||
|
continue-on-error: true
|
||||||
|
|
73
.github/workflows/PR-Demo-Comment-with-react.yml
vendored
73
.github/workflows/PR-Demo-Comment-with-react.yml
vendored
@ -6,15 +6,13 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
issues: write # Required for adding reactions to comments
|
pull-requests: read
|
||||||
pull-requests: read # Required for reading PR information
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-comment:
|
check-comment:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
pull-requests: read
|
|
||||||
if: |
|
if: |
|
||||||
github.event.issue.pull_request &&
|
github.event.issue.pull_request &&
|
||||||
(
|
(
|
||||||
@ -30,6 +28,7 @@ jobs:
|
|||||||
github.event.comment.user.login == 'sbplat' ||
|
github.event.comment.user.login == 'sbplat' ||
|
||||||
github.event.comment.user.login == 'reecebrowne' ||
|
github.event.comment.user.login == 'reecebrowne' ||
|
||||||
github.event.comment.user.login == 'DarioGii' ||
|
github.event.comment.user.login == 'DarioGii' ||
|
||||||
|
github.event.comment.user.login == 'EthanHealy01' ||
|
||||||
github.event.comment.user.login == 'ConnorYoh'
|
github.event.comment.user.login == 'ConnorYoh'
|
||||||
)
|
)
|
||||||
outputs:
|
outputs:
|
||||||
@ -42,14 +41,18 @@ jobs:
|
|||||||
enable_enterprise: ${{ steps.check-pro-flag.outputs.enable_enterprise }}
|
enable_enterprise: ${{ steps.check-pro-flag.outputs.enable_enterprise }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
# Generate GitHub App token
|
- name: Checkout PR
|
||||||
- name: Generate GitHub App Token
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
id: generate-token
|
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
@ -122,7 +125,7 @@ jobs:
|
|||||||
id: add-eyes-reaction
|
id: add-eyes-reaction
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
console.log(`Adding eyes reaction to comment ID: ${context.payload.comment.id}`);
|
console.log(`Adding eyes reaction to comment ID: ${context.payload.comment.id}`);
|
||||||
try {
|
try {
|
||||||
@ -144,18 +147,23 @@ jobs:
|
|||||||
needs: check-comment
|
needs: check-comment
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
issues: write
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Generate GitHub App Token
|
- name: Checkout PR
|
||||||
id: generate-token
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.GH_APP_ID }}
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
@ -165,7 +173,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: ${{ needs.check-comment.outputs.pr_repository }}
|
repository: ${{ needs.check-comment.outputs.pr_repository }}
|
||||||
ref: ${{ needs.check-comment.outputs.pr_ref }}
|
ref: ${{ needs.check-comment.outputs.pr_ref }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||||
@ -187,12 +195,6 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Get version number
|
|
||||||
id: versionNumber
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
|
||||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
with:
|
with:
|
||||||
@ -296,7 +298,7 @@ jobs:
|
|||||||
if: success()
|
if: success()
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
console.log(`Adding rocket reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
console.log(`Adding rocket reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
||||||
try {
|
try {
|
||||||
@ -312,11 +314,26 @@ jobs:
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add label to PR
|
||||||
|
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
|
||||||
|
try {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
labels: ['pr-deployed']
|
||||||
|
});
|
||||||
|
console.log(`Added 'pr-deployed' label to PR #${prNumber}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to add label to PR: ${error.message}`);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
- name: Add failure reaction to comment
|
- name: Add failure reaction to comment
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
console.log(`Adding -1 reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
console.log(`Adding -1 reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
|
||||||
try {
|
try {
|
||||||
@ -336,7 +353,7 @@ jobs:
|
|||||||
if: success()
|
if: success()
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const { GITHUB_REPOSITORY } = process.env;
|
const { GITHUB_REPOSITORY } = process.env;
|
||||||
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||||
@ -356,3 +373,11 @@ jobs:
|
|||||||
issue_number: prNumber,
|
issue_number: prNumber,
|
||||||
body: commentBody
|
body: commentBody
|
||||||
});
|
});
|
||||||
|
|
||||||
|
- name: Cleanup temporary files
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "Cleaning up temporary files..."
|
||||||
|
rm -f ../private.key docker-compose.yml
|
||||||
|
echo "Cleanup complete."
|
||||||
|
continue-on-error: true
|
||||||
|
90
.github/workflows/PR-Demo-cleanup.yml
vendored
90
.github/workflows/PR-Demo-cleanup.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: PR Deployment cleanup
|
name: PR Deployment cleanup
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [opened, synchronize, reopened, closed]
|
types: [opened, synchronize, reopened, closed]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -13,25 +13,99 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
|
if: github.event.action == 'closed'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
if: github.event.action == 'closed'
|
issues: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout PR
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Remove 'pr-deployed' label if present
|
||||||
|
id: remove-label-comment
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const prNumber = ${{ github.event.pull_request.number }};
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
// Get all labels on the PR
|
||||||
|
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasLabel = labels.some(label => label.name === 'pr-deployed');
|
||||||
|
|
||||||
|
if (hasLabel) {
|
||||||
|
console.log("Label 'pr-deployed' found. Removing...");
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
name: 'pr-deployed'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Label 'pr-deployed' not found. Nothing to do.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find existing bot comments about the deployment
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber
|
||||||
|
});
|
||||||
|
const deploymentComments = comments.filter(c =>
|
||||||
|
c.body?.includes("## 🚀 PR Test Deployment") &&
|
||||||
|
c.user?.type === "Bot"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deploymentComments.length > 0) {
|
||||||
|
for (const comment of deploymentComments) {
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
console.log(`Deleted deployment comment (ID: ${comment.id})`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("No matching deployment comments found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set flag if either label or comment was present
|
||||||
|
const hasDeploymentComment = deploymentComments.length > 0;
|
||||||
|
core.setOutput('present', (hasLabel || hasDeploymentComment) ? 'true' : 'false');
|
||||||
|
|
||||||
|
|
||||||
- name: Set up SSH
|
- name: Set up SSH
|
||||||
|
if: steps.remove-label-comment.outputs.present == 'true'
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh/
|
mkdir -p ~/.ssh/
|
||||||
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
sudo chmod 600 ../private.key
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
- name: Cleanup PR deployment
|
- name: Cleanup PR deployment
|
||||||
|
if: steps.remove-label-comment.outputs.present == 'true'
|
||||||
id: cleanup
|
id: cleanup
|
||||||
run: |
|
run: |
|
||||||
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||||
@ -57,3 +131,11 @@ jobs:
|
|||||||
echo "NO_CLEANUP_NEEDED"
|
echo "NO_CLEANUP_NEEDED"
|
||||||
fi
|
fi
|
||||||
ENDSSH
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Cleanup temporary files
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "Cleaning up temporary files..."
|
||||||
|
rm -f ../private.key
|
||||||
|
echo "Cleanup complete."
|
||||||
|
continue-on-error: true
|
||||||
|
2
.github/workflows/ai_pr_title_review.yml
vendored
2
.github/workflows/ai_pr_title_review.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
27
.github/workflows/auto-labeler.yml
vendored
27
.github/workflows/auto-labeler.yml
vendored
@ -1,27 +0,0 @@
|
|||||||
name: "Pull Request Labeler"
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, synchronize]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
labeler:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Harden Runner
|
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Apply Labels
|
|
||||||
uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
configuration-path: .github/labeler-config.yml
|
|
||||||
sync-labels: true
|
|
2
.github/workflows/auto-labelerV2.yml
vendored
2
.github/workflows/auto-labelerV2.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
159
.github/workflows/build.yml
vendored
159
.github/workflows/build.yml
vendored
@ -1,122 +1,172 @@
|
|||||||
name: Build repo
|
name: Build and Test Workflow
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main", "V2", "V2-gha"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
files-changed:
|
||||||
|
name: detect what files changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 3
|
||||||
|
outputs:
|
||||||
|
build: ${{ steps.changes.outputs.build }}
|
||||||
|
app: ${{ steps.changes.outputs.app }}
|
||||||
|
project: ${{ steps.changes.outputs.project }}
|
||||||
|
openapi: ${{ steps.changes.outputs.openapi }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Check for file changes
|
||||||
|
uses: dorny/paths-filter@v3.0.2
|
||||||
|
id: changes
|
||||||
|
with:
|
||||||
|
filters: .github/config/.files.yaml
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
jdk-version: [17, 21]
|
jdk-version: [17, 21]
|
||||||
spring-security: [true, false]
|
spring-security: [true, false]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.jdk-version }}
|
java-version: ${{ matrix.jdk-version }}
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4.4.1
|
||||||
|
with:
|
||||||
|
gradle-version: 8.14
|
||||||
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
||||||
|
|
||||||
- name: Check Test Reports Exist
|
- name: Check Test Reports Exist
|
||||||
id: check-reports
|
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
declare -a dirs=(
|
declare -a dirs=(
|
||||||
"stirling-pdf/build/reports/tests/"
|
"app/core/build/reports/tests/"
|
||||||
"stirling-pdf/build/test-results/"
|
"app/core/build/test-results/"
|
||||||
"common/build/reports/tests/"
|
"app/common/build/reports/tests/"
|
||||||
"common/build/test-results/"
|
"app/common/build/test-results/"
|
||||||
"proprietary/build/reports/tests/"
|
"app/proprietary/build/reports/tests/"
|
||||||
"proprietary/build/test-results/"
|
"app/proprietary/build/test-results/"
|
||||||
)
|
)
|
||||||
missing_reports=()
|
|
||||||
for dir in "${dirs[@]}"; do
|
for dir in "${dirs[@]}"; do
|
||||||
if [ ! -d "$dir" ]; then
|
if [ ! -d "$dir" ]; then
|
||||||
missing_reports+=("$dir")
|
echo "Missing $dir"
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ ${#missing_reports[@]} -gt 0 ]; then
|
|
||||||
echo "ERROR: The following required test report directories are missing:"
|
|
||||||
printf '%s\n' "${missing_reports[@]}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "All required test report directories are present"
|
done
|
||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
||||||
path: |
|
path: |
|
||||||
stirling-pdf/build/reports/tests/
|
app/**/build/reports/tests/
|
||||||
stirling-pdf/build/test-results/
|
app/**/build/test-results/
|
||||||
stirling-pdf/build/reports/problems/
|
app/**/build/reports/problems/
|
||||||
common/build/reports/tests/
|
|
||||||
common/build/test-results/
|
|
||||||
common/build/reports/problems/
|
|
||||||
proprietary/build/reports/tests/
|
|
||||||
proprietary/build/test-results/
|
|
||||||
proprietary/build/reports/problems/
|
|
||||||
build/reports/problems/
|
build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
check-licence:
|
check-generateOpenApiDocs:
|
||||||
|
if: needs.files-changed.outputs.openapi == 'true'
|
||||||
|
needs: [files-changed]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "temurin"
|
||||||
|
- uses: gradle/actions/setup-gradle@v4.4.1
|
||||||
|
- name: Generate OpenAPI documentation
|
||||||
|
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||||
|
- name: Upload OpenAPI Documentation
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: openapi-docs
|
||||||
|
path: ./SwaggerDoc.json
|
||||||
|
|
||||||
|
frontend-validation:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: cd frontend && npm ci
|
||||||
|
- name: Build frontend
|
||||||
|
run: cd frontend && npm run build
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: cd frontend && npm test --passWithNoTests --watchAll=false || true
|
||||||
|
- name: Upload frontend build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: frontend-build
|
||||||
|
path: frontend/dist/
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
|
check-licence:
|
||||||
|
if: needs.files-changed.outputs.build == 'true'
|
||||||
|
needs: [files-changed, build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4.7.1
|
||||||
|
with:
|
||||||
|
java-version: "17"
|
||||||
|
distribution: "temurin"
|
||||||
- name: check the licenses for compatibility
|
- name: check the licenses for compatibility
|
||||||
run: ./gradlew clean checkLicense
|
run: ./gradlew clean checkLicense
|
||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
- name: FAILED - check the licenses for compatibility
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: dependencies-without-allowed-license.json
|
name: dependencies-without-allowed-license.json
|
||||||
path: |
|
path: build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
docker-compose-tests:
|
docker-compose-tests:
|
||||||
|
if: needs.files-changed.outputs.project == 'true'
|
||||||
|
needs: files-changed
|
||||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
|
||||||
# (github.event_name == 'pull_request' &&
|
# (github.event_name == 'pull_request' &&
|
||||||
# contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
|
# contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
|
||||||
@ -135,7 +185,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -146,7 +196,7 @@ jobs:
|
|||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
@ -161,6 +211,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: 'pip' # caching pip dependencies
|
cache: 'pip' # caching pip dependencies
|
||||||
|
cache-dependency-path: ./testing/cucumber/requirements.txt
|
||||||
|
|
||||||
- name: Pip requirements
|
- name: Pip requirements
|
||||||
run: |
|
run: |
|
||||||
|
14
.github/workflows/check_properties.yml
vendored
14
.github/workflows/check_properties.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
paths:
|
paths:
|
||||||
- "stirling-pdf/src/main/resources/messages_*.properties"
|
- "app/core/src/main/resources/messages_*.properties"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # Allow read access to repository content
|
contents: read # Allow read access to repository content
|
||||||
@ -18,7 +18,7 @@ jobs:
|
|||||||
pull-requests: write # Allow writing to pull requests
|
pull-requests: write # Allow writing to pull requests
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Get changed files and filter for properties files, handle case where no matches are found
|
# Get changed files and filter for properties files, handle case where no matches are found
|
||||||
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^stirling-pdf/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt || echo "No matching properties files found in PR"
|
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^app/core/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt || echo "No matching properties files found in PR"
|
||||||
# Check if any files were found
|
# Check if any files were found
|
||||||
if [ ! -s changed_files.txt ]; then
|
if [ ! -s changed_files.txt ]; then
|
||||||
echo "No properties files changed in this PR"
|
echo "No properties files changed in this PR"
|
||||||
@ -117,7 +117,7 @@ jobs:
|
|||||||
const changedFiles = files
|
const changedFiles = files
|
||||||
.filter(file =>
|
.filter(file =>
|
||||||
file.status !== "removed" &&
|
file.status !== "removed" &&
|
||||||
/^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename)
|
/^app\/core\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename)
|
||||||
)
|
)
|
||||||
.map(file => file.filename);
|
.map(file => file.filename);
|
||||||
|
|
||||||
@ -157,12 +157,12 @@ jobs:
|
|||||||
|
|
||||||
// Determine reference file
|
// Determine reference file
|
||||||
let referenceFilePath;
|
let referenceFilePath;
|
||||||
if (changedFiles.includes("stirling-pdf/src/main/resources/messages_en_GB.properties")) {
|
if (changedFiles.includes("app/core/src/main/resources/messages_en_GB.properties")) {
|
||||||
console.log("Using PR branch reference file.");
|
console.log("Using PR branch reference file.");
|
||||||
const { data: fileContent } = await github.rest.repos.getContent({
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
owner: prRepoOwner,
|
owner: prRepoOwner,
|
||||||
repo: prRepoName,
|
repo: prRepoName,
|
||||||
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
|
path: "app/core/src/main/resources/messages_en_GB.properties",
|
||||||
ref: branch,
|
ref: branch,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ jobs:
|
|||||||
const { data: fileContent } = await github.rest.repos.getContent({
|
const { data: fileContent } = await github.rest.repos.getContent({
|
||||||
owner: repoOwner,
|
owner: repoOwner,
|
||||||
repo: repoName,
|
repo: repoName,
|
||||||
path: "stirling-pdf/src/main/resources/messages_en_GB.properties",
|
path: "app/core/src/main/resources/messages_en_GB.properties",
|
||||||
ref: "main",
|
ref: "main",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -25,3 +25,5 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: "Dependency Review"
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
|
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1
|
||||||
|
with:
|
||||||
|
config-file: './.github/config/dependency-review-config.yml'
|
||||||
|
217
.github/workflows/frontend-licenses-update.yml
vendored
Normal file
217
.github/workflows/frontend-licenses-update.yml
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
name: Frontend License Report Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- V2
|
||||||
|
paths:
|
||||||
|
- "frontend/package.json"
|
||||||
|
- "frontend/package-lock.json"
|
||||||
|
- "frontend/scripts/generate-licenses.js"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- V2
|
||||||
|
paths:
|
||||||
|
- "frontend/package.json"
|
||||||
|
- "frontend/package-lock.json"
|
||||||
|
- "frontend/scripts/generate-licenses.js"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-frontend-license-report:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
repository-projects: write # Required for enabling automerge
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Generate frontend license report
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm run generate-licenses
|
||||||
|
|
||||||
|
- name: Check for license warnings
|
||||||
|
run: |
|
||||||
|
if [ -f "frontend/src/assets/license-warnings.json" ]; then
|
||||||
|
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# PR Event: Check licenses and comment on PR
|
||||||
|
- name: Delete previous license check comments
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = context.issue.number;
|
||||||
|
|
||||||
|
// Get all comments on the PR
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter for license check comments
|
||||||
|
const licenseComments = comments.filter(comment =>
|
||||||
|
comment.body.includes('## ✅ Frontend License Check Passed') ||
|
||||||
|
comment.body.includes('## ❌ Frontend License Check Failed')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete old license check comments
|
||||||
|
for (const comment of licenseComments) {
|
||||||
|
console.log(`Deleting old license check comment: ${comment.id}`);
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Comment on PR - License Check Results
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = context.issue.number;
|
||||||
|
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
|
||||||
|
|
||||||
|
let commentBody;
|
||||||
|
|
||||||
|
if (hasWarnings) {
|
||||||
|
// Read warnings file to get specific issues
|
||||||
|
const fs = require('fs');
|
||||||
|
let warningDetails = '';
|
||||||
|
try {
|
||||||
|
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
|
||||||
|
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
|
||||||
|
} catch (e) {
|
||||||
|
warningDetails = 'Unable to read warning details';
|
||||||
|
}
|
||||||
|
|
||||||
|
commentBody = `## ❌ Frontend License Check Failed
|
||||||
|
|
||||||
|
The frontend license check has detected compatibility warnings that require review:
|
||||||
|
|
||||||
|
${warningDetails}
|
||||||
|
|
||||||
|
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
|
||||||
|
|
||||||
|
_This check will fail the PR until license issues are resolved._`;
|
||||||
|
} else {
|
||||||
|
commentBody = `## ✅ Frontend License Check Passed
|
||||||
|
|
||||||
|
All frontend licenses have been validated and no compatibility warnings were detected.
|
||||||
|
|
||||||
|
The frontend license report has been updated successfully.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Fail workflow if license warnings exist (PR only)
|
||||||
|
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||||
|
run: |
|
||||||
|
echo "❌ License warnings detected. Failing the workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Push Event: Commit license files and create PR
|
||||||
|
- name: Commit changes (Push only)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
git add frontend/src/assets/3rdPartyLicenses.json
|
||||||
|
# Note: Do NOT commit license-warnings.json - it's only for PR review
|
||||||
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Prepare PR body (Push only)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
PR_BODY="Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot]
|
||||||
|
|
||||||
|
This PR updates the frontend license report based on changes to package.json dependencies."
|
||||||
|
|
||||||
|
if [ "${{ env.LICENSE_WARNINGS_EXIST }}" = "true" ]; then
|
||||||
|
PR_BODY="$PR_BODY
|
||||||
|
|
||||||
|
## ⚠️ License Compatibility Warnings
|
||||||
|
|
||||||
|
The following licenses may require review for corporate compatibility:
|
||||||
|
|
||||||
|
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
|
||||||
|
|
||||||
|
Please review these licenses to ensure they are acceptable for your use case."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PR_BODY<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$PR_BODY" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create Pull Request (Push only)
|
||||||
|
id: cpr
|
||||||
|
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
|
with:
|
||||||
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
commit-message: "Update Frontend 3rd Party Licenses"
|
||||||
|
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||||
|
author: ${{ steps.setup-bot.outputs.committer }}
|
||||||
|
signoff: true
|
||||||
|
branch: update-frontend-3rd-party-licenses
|
||||||
|
base: V2
|
||||||
|
title: "Update Frontend 3rd Party Licenses"
|
||||||
|
body: ${{ env.PR_BODY }}
|
||||||
|
labels: Licenses,github-actions,frontend
|
||||||
|
draft: false
|
||||||
|
delete-branch: true
|
||||||
|
sign-commits: true
|
||||||
|
|
||||||
|
- name: Enable Pull Request Automerge (Push only)
|
||||||
|
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'false'
|
||||||
|
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
|
||||||
|
- name: Add review required label (Push only)
|
||||||
|
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||||
|
run: gh pr edit "${{ steps.cpr.outputs.pull-request-number }}" --add-label "license-review-required"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
8
.github/workflows/licenses-update.yml
vendored
8
.github/workflows/licenses-update.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
repository-projects: write # Required for enabling automerge
|
repository-projects: write # Required for enabling automerge
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ jobs:
|
|||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "adopt"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
@ -57,11 +57,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Move and rename license file
|
- name: Move and rename license file
|
||||||
run: |
|
run: |
|
||||||
mv build/reports/dependency-license/index.json stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
|
mv build/reports/dependency-license/index.json app/core/src/main/resources/static/3rdPartyLicenses.json
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
run: |
|
run: |
|
||||||
git add stirling-pdf/src/main/resources/static/3rdPartyLicenses.json
|
git add app/core/src/main/resources/static/3rdPartyLicenses.json
|
||||||
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
2
.github/workflows/manage-label.yml
vendored
2
.github/workflows/manage-label.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
34
.github/workflows/multiOSReleases.yml
vendored
34
.github/workflows/multiOSReleases.yml
vendored
@ -21,27 +21,31 @@ jobs:
|
|||||||
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
versionMac: ${{ steps.versionNumberMac.outputs.versionNumberMac }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
# Get version number
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
|
||||||
|
# ✅ Get version from Gradle
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
VERSION=$(./gradlew printVersion --quiet | tail -1)
|
||||||
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# ✅ Get Mac-specific version from Gradle
|
||||||
- name: Get version number mac
|
- name: Get version number mac
|
||||||
id: versionNumberMac
|
id: versionNumberMac
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
VERSION_MAC=$(./gradlew printMacVersion --quiet | tail -1)
|
||||||
CURRENT_YEAR=$(date +'%Y')
|
echo "versionNumberMac=$VERSION_MAC" >> $GITHUB_OUTPUT
|
||||||
IFS='.' read -r -a VERSION_PARTS <<< "$VERSION"
|
|
||||||
MAC_VERSION="$CURRENT_YEAR.${VERSION_PARTS[1]:-0}.${VERSION_PARTS[2]:-0}"
|
|
||||||
echo "versionNumberMac=$MAC_VERSION" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
build-portable:
|
build-portable:
|
||||||
needs: read_versions
|
needs: read_versions
|
||||||
@ -56,7 +60,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -82,7 +86,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir ./binaries
|
mkdir ./binaries
|
||||||
mv ./build/launch4j/Stirling-PDF.exe ./binaries/win-Stirling-PDF-portable-Server${{ matrix.file_suffix }}.exe
|
mv ./build/launch4j/Stirling-PDF.exe ./binaries/win-Stirling-PDF-portable-Server${{ matrix.file_suffix }}.exe
|
||||||
mv ./build/libs/Stirling-PDF-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
mv ./app/core/build/libs/stirling-pdf-${{ needs.read_versions.outputs.version }}.jar ./binaries/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
@ -106,7 +110,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -144,7 +148,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -234,7 +238,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -248,7 +252,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@ -297,7 +301,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
3
.github/workflows/pre_commit.yml
vendored
3
.github/workflows/pre_commit.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.12
|
python-version: 3.12
|
||||||
cache: 'pip' # caching pip dependencies
|
cache: 'pip' # caching pip dependencies
|
||||||
|
cache-dependency-path: ./.github/scripts/requirements_pre_commit.txt
|
||||||
|
|
||||||
- name: Run Pre-Commit Hooks
|
- name: Run Pre-Commit Hooks
|
||||||
run: |
|
run: |
|
||||||
|
10
.github/workflows/push-docker.yml
vendored
10
.github/workflows/push-docker.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
|
||||||
with:
|
with:
|
||||||
cosign-release: "v2.4.1"
|
cosign-release: "v2.4.1"
|
||||||
|
|
||||||
@ -77,6 +77,7 @@ jobs:
|
|||||||
- name: Generate tags
|
- name: Generate tags
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
@ -86,11 +87,11 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
|
||||||
|
|
||||||
- name: Build and push main Dockerfile
|
- name: Build and push main Dockerfile
|
||||||
id: build-push-regular
|
id: build-push-regular
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
@ -153,7 +154,6 @@ jobs:
|
|||||||
- name: Generate tags fat
|
- name: Generate tags fat
|
||||||
id: meta3
|
id: meta3
|
||||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||||
if: github.ref != 'refs/heads/main'
|
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||||
@ -163,11 +163,11 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
|
||||||
|
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
|
||||||
- name: Build and push main Dockerfile fat
|
- name: Build and push main Dockerfile fat
|
||||||
id: build-push-fat
|
id: build-push-fat
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
if: github.ref != 'refs/heads/main'
|
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: .
|
context: .
|
||||||
|
8
.github/workflows/releaseArtifacts.yml
vendored
8
.github/workflows/releaseArtifacts.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
version: ${{ steps.versionNumber.outputs.versionNumber }}
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # v3.9.0
|
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
|
||||||
|
|
||||||
- name: Generate key pair
|
- name: Generate key pair
|
||||||
run: cosign generate-key-pair
|
run: cosign generate-key-pair
|
||||||
@ -161,7 +161,7 @@ jobs:
|
|||||||
file_suffix: ""
|
file_suffix: ""
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -74,6 +74,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
2
.github/workflows/sonarqube.yml
vendored
2
.github/workflows/sonarqube.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
4
.github/workflows/swagger.yml
vendored
4
.github/workflows/swagger.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ jobs:
|
|||||||
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
|
|
||||||
- name: Generate Swagger documentation
|
- name: Generate Swagger documentation
|
||||||
run: ./gradlew generateOpenApiDocs
|
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||||
|
|
||||||
- name: Upload Swagger Documentation to SwaggerHub
|
- name: Upload Swagger Documentation to SwaggerHub
|
||||||
run: ./gradlew swaggerhubUpload
|
run: ./gradlew swaggerhubUpload
|
||||||
|
16
.github/workflows/sync_files.yml
vendored
16
.github/workflows/sync_files.yml
vendored
@ -8,8 +8,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "build.gradle"
|
- "build.gradle"
|
||||||
- "README.md"
|
- "README.md"
|
||||||
- "stirling-pdf/src/main/resources/messages_*.properties"
|
- "app/core/src/main/resources/messages_*.properties"
|
||||||
- "stirling-pdf/src/main/resources/static/3rdPartyLicenses.json"
|
- "app/core/src/main/resources/static/3rdPartyLicenses.json"
|
||||||
- "scripts/ignore_translation.toml"
|
- "scripts/ignore_translation.toml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -41,11 +41,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Sync translation property files
|
- name: Sync translation property files
|
||||||
run: |
|
run: |
|
||||||
python .github/scripts/check_language_properties.py --reference-file "stirling-pdf/src/main/resources/messages_en_GB.properties" --branch main
|
python .github/scripts/check_language_properties.py --reference-file "app/core/src/main/resources/messages_en_GB.properties" --branch main
|
||||||
|
|
||||||
- name: Commit translation files
|
- name: Commit translation files
|
||||||
run: |
|
run: |
|
||||||
git add stirling-pdf/src/main/resources/messages_*.properties
|
git add app/core/src/main/resources/messages_*.properties
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected"
|
git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -57,8 +57,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Run git add
|
- name: Run git add
|
||||||
run: |
|
run: |
|
||||||
git add README.md
|
git add README.md scripts/ignore_translation.toml
|
||||||
git diff --staged --quiet || git commit -m ":memo: Sync README.md" || echo "No changes detected"
|
git diff --staged --quiet || git commit -m ":memo: Sync README.md & scripts/ignore_translation.toml" || echo "No changes detected"
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: always()
|
if: always()
|
||||||
@ -101,4 +101,4 @@ jobs:
|
|||||||
sign-commits: true
|
sign-commits: true
|
||||||
add-paths: |
|
add-paths: |
|
||||||
README.md
|
README.md
|
||||||
stirling-pdf/src/main/resources/messages_*.properties
|
app/core/src/main/resources/messages_*.properties
|
||||||
|
16
.github/workflows/testdriver.yml
vendored
16
.github/workflows/testdriver.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@ -25,6 +25,11 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
||||||
|
with:
|
||||||
|
gradle-version: 8.14
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
env:
|
env:
|
||||||
@ -105,12 +110,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Run TestDriver.ai
|
- name: Run TestDriver.ai
|
||||||
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
|
||||||
with:
|
with:
|
||||||
@ -134,7 +144,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -27,6 +27,7 @@ clientWebUI/
|
|||||||
!cucumber/exampleFiles/
|
!cucumber/exampleFiles/
|
||||||
!cucumber/exampleFiles/example_html.zip
|
!cucumber/exampleFiles/example_html.zip
|
||||||
exampleYmlFiles/stirling/
|
exampleYmlFiles/stirling/
|
||||||
|
stirling/
|
||||||
/testing/file_snapshots
|
/testing/file_snapshots
|
||||||
SwaggerDoc.json
|
SwaggerDoc.json
|
||||||
|
|
||||||
@ -125,9 +126,12 @@ SwaggerDoc.json
|
|||||||
*.rar
|
*.rar
|
||||||
*.db
|
*.db
|
||||||
/build
|
/build
|
||||||
/stirling-pdf/build
|
/app/core/build
|
||||||
/common/build
|
/app/common/build
|
||||||
/proprietary/build
|
/app/proprietary/build
|
||||||
|
common/build
|
||||||
|
proprietary/build
|
||||||
|
stirling-pdf/build
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
@ -6,10 +6,10 @@ repos:
|
|||||||
args:
|
args:
|
||||||
- --fix
|
- --fix
|
||||||
- --line-length=127
|
- --line-length=127
|
||||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
|
||||||
exclude: (split_photos.py)
|
exclude: (split_photos.py)
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$
|
files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
|
||||||
exclude: (split_photos.py)
|
exclude: (split_photos.py)
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.4.1
|
rev: v2.4.1
|
||||||
@ -20,7 +20,7 @@ repos:
|
|||||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||||
- --quiet-level=2
|
- --quiet-level=2
|
||||||
files: \.(html|css|js|py|md)$
|
files: \.(html|css|js|py|md)$
|
||||||
exclude: (.vscode|.devcontainer|stirling-pdf/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
exclude: (.vscode|.devcontainer|app/core/src/main/resources|app/proprietary/src/main/resources|Dockerfile|.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js)
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.27.2
|
rev: v8.27.2
|
||||||
hooks:
|
hooks:
|
||||||
@ -34,3 +34,13 @@ repos:
|
|||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
files: ^.*(\.js|\.java|\.py|\.yml)$
|
files: ^.*(\.js|\.java|\.py|\.yml)$
|
||||||
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
exclude: ^(.*/pdfjs.*|.*/thirdParty.*|bootstrap.*|.*\.min\..*|.*diff\.js|\.github/workflows/.*$)
|
||||||
|
# - repo: https://github.com/thibaudcolas/pre-commit-stylelint
|
||||||
|
# rev: v16.21.1
|
||||||
|
# hooks:
|
||||||
|
# - id: stylelint
|
||||||
|
# additional_dependencies:
|
||||||
|
# - stylelint@16.21.1
|
||||||
|
# - stylelint-config-standard@38.0.0
|
||||||
|
# - "@stylistic/stylelint-plugin@3.1.3"
|
||||||
|
# files: \.(css)$
|
||||||
|
# args: [--fix]
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -17,5 +17,7 @@
|
|||||||
"GitHub.vscode-pull-request-github", // GitHub Pull Requests extension for Visual Studio Code
|
"GitHub.vscode-pull-request-github", // GitHub Pull Requests extension for Visual Studio Code
|
||||||
"charliermarsh.ruff", // Ruff code formatter for Python to follow the Ruff Style Guide
|
"charliermarsh.ruff", // Ruff code formatter for Python to follow the Ruff Style Guide
|
||||||
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
|
||||||
|
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
|
||||||
|
"redhat.vscode-yaml", // YAML extension for Visual Studio Code
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
33
.vscode/settings.json
vendored
33
.vscode/settings.json
vendored
@ -3,12 +3,18 @@
|
|||||||
"editor.guides.bracketPairs": "active",
|
"editor.guides.bracketPairs": "active",
|
||||||
"editor.guides.bracketPairsHorizontal": "active",
|
"editor.guides.bracketPairsHorizontal": "active",
|
||||||
"cSpell.enabled": false,
|
"cSpell.enabled": false,
|
||||||
|
"[feature]": {
|
||||||
|
"editor.defaultFormatter": "alexkrechik.cucumberautocomplete"
|
||||||
|
},
|
||||||
"[java]": {
|
"[java]": {
|
||||||
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
|
"editor.defaultFormatter": "josevseb.google-java-format-for-vs-code"
|
||||||
},
|
},
|
||||||
"[jsonc]": {
|
"[jsonc]": {
|
||||||
"editor.defaultFormatter": "vscode.json-language-features"
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
},
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "stylelint.vscode-stylelint"
|
||||||
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "vscode.json-language-features"
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
},
|
},
|
||||||
@ -27,6 +33,9 @@
|
|||||||
"[gradle]": {
|
"[gradle]": {
|
||||||
"editor.defaultFormatter": "vscjava.vscode-gradle"
|
"editor.defaultFormatter": "vscjava.vscode-gradle"
|
||||||
},
|
},
|
||||||
|
"[yaml]": {
|
||||||
|
"editor.defaultFormatter": "redhat.vscode-yaml"
|
||||||
|
},
|
||||||
"java.compile.nullAnalysis.mode": "automatic",
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
"java.configuration.updateBuildConfiguration": "interactive",
|
"java.configuration.updateBuildConfiguration": "interactive",
|
||||||
"java.format.enabled": true,
|
"java.format.enabled": true,
|
||||||
@ -70,13 +79,17 @@
|
|||||||
".venv*/",
|
".venv*/",
|
||||||
".vscode/",
|
".vscode/",
|
||||||
"bin/",
|
"bin/",
|
||||||
"common/bin/",
|
"app/core/bin/",
|
||||||
"proprietary/bin/",
|
"app/common/bin/",
|
||||||
|
"app/proprietary/bin/",
|
||||||
"build/",
|
"build/",
|
||||||
"common/build/",
|
"app/core/build/",
|
||||||
"proprietary/build/",
|
"app/common/build/",
|
||||||
|
"app/proprietary/build/",
|
||||||
"configs/",
|
"configs/",
|
||||||
|
"app/core/configs/",
|
||||||
"customFiles/",
|
"customFiles/",
|
||||||
|
"app/core/customFiles/",
|
||||||
"docs/",
|
"docs/",
|
||||||
"exampleYmlFiles",
|
"exampleYmlFiles",
|
||||||
"gradle/",
|
"gradle/",
|
||||||
@ -88,8 +101,9 @@
|
|||||||
".git-blame-ignore-revs",
|
".git-blame-ignore-revs",
|
||||||
".gitattributes",
|
".gitattributes",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
"common/.gitignore",
|
"app/core/.gitignore",
|
||||||
"proprietary/.gitignore",
|
"app/common/.gitignore",
|
||||||
|
"app/proprietary/.gitignore",
|
||||||
".pre-commit-config.yaml",
|
".pre-commit-config.yaml",
|
||||||
],
|
],
|
||||||
// Enables signature help in Java.
|
// Enables signature help in Java.
|
||||||
@ -119,9 +133,10 @@
|
|||||||
"html.format.indentHandlebars": true,
|
"html.format.indentHandlebars": true,
|
||||||
"html.format.preserveNewLines": true,
|
"html.format.preserveNewLines": true,
|
||||||
"html.format.maxPreserveNewLines": 2,
|
"html.format.maxPreserveNewLines": 2,
|
||||||
|
"stylelint.configFile": "devTools/.stylelintrc.json",
|
||||||
"java.project.sourcePaths": [
|
"java.project.sourcePaths": [
|
||||||
"stirling-pdf/src/main/java",
|
"app/core/src/main/java",
|
||||||
"common/src/main/java",
|
"app/common/src/main/java",
|
||||||
"proprietary/src/main/java"
|
"app/proprietary/src/main/java"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
72
CLAUDE.md
72
CLAUDE.md
@ -25,23 +25,54 @@ Set `DOCKER_ENABLE_SECURITY=true` environment variable to enable security featur
|
|||||||
- **Proxy Configuration**: Vite proxies `/api/*` calls to backend (localhost:8080)
|
- **Proxy Configuration**: Vite proxies `/api/*` calls to backend (localhost:8080)
|
||||||
- **Build Process**: DO NOT run build scripts manually - builds are handled by CI/CD pipelines
|
- **Build Process**: DO NOT run build scripts manually - builds are handled by CI/CD pipelines
|
||||||
- **Package Installation**: DO NOT run npm install commands - package management handled separately
|
- **Package Installation**: DO NOT run npm install commands - package management handled separately
|
||||||
|
- **Deployment Options**:
|
||||||
|
- **Desktop App**: `npm run tauri-build` (native desktop application)
|
||||||
|
- **Web Server**: `npm run build` then serve dist/ folder
|
||||||
|
- **Development**: `npm run tauri-dev` for desktop dev mode
|
||||||
|
|
||||||
#### Tailwind CSS Setup (if not already installed)
|
#### Multi-Tool Workflow Architecture
|
||||||
```bash
|
Frontend designed for **stateful document processing**:
|
||||||
cd frontend
|
- Users upload PDFs once, then chain tools (split → merge → compress → view)
|
||||||
npm install -D tailwindcss postcss autoprefixer
|
- File state and processing results persist across tool switches
|
||||||
npx tailwindcss init -p
|
- No file reloading between tools - performance critical for large PDFs (up to 100GB+)
|
||||||
```
|
|
||||||
|
#### FileContext - Central State Management
|
||||||
|
**Location**: `src/contexts/FileContext.tsx`
|
||||||
|
- **Active files**: Currently loaded PDFs and their variants
|
||||||
|
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
|
||||||
|
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
|
||||||
|
- **IndexedDB persistence**: File storage with thumbnail caching
|
||||||
|
- **Preview system**: Tools can preview results (e.g., Split → Viewer → back to Split) without context pollution
|
||||||
|
|
||||||
|
**Critical**: All file operations go through FileContext. Don't bypass with direct file handling.
|
||||||
|
|
||||||
|
#### Processing Services
|
||||||
|
- **enhancedPDFProcessingService**: Background PDF parsing and manipulation
|
||||||
|
- **thumbnailGenerationService**: Web Worker-based with main-thread fallback
|
||||||
|
- **fileStorage**: IndexedDB with LRU cache management
|
||||||
|
|
||||||
|
#### Memory Management Strategy
|
||||||
|
**Why manual cleanup exists**: Large PDFs (up to 100GB+) through multiple tools accumulate:
|
||||||
|
- PDF.js documents that need explicit .destroy() calls
|
||||||
|
- Blob URLs from tool outputs that need revocation
|
||||||
|
- Web Workers that need termination
|
||||||
|
Without cleanup: browser crashes with memory leaks.
|
||||||
|
|
||||||
|
#### Tool Development
|
||||||
|
- **Pattern**: Follow `src/tools/Split.tsx` as reference implementation
|
||||||
|
- **File Access**: Tools receive `selectedFiles` prop (computed from activeFiles based on user selection)
|
||||||
|
- **File Selection**: Users select files in FileEditor (tool mode) → stored as IDs → computed to File objects for tools
|
||||||
|
- **Integration**: All files are part of FileContext ecosystem - automatic memory management and operation tracking
|
||||||
|
- **Parameters**: Tool parameter handling patterns still being standardized
|
||||||
|
- **Preview Integration**: Tools can implement preview functionality (see Split tool's thumbnail preview)
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
- **Backend**: Spring Boot application with Thymeleaf templating
|
- **Backend**: Spring Boot application with Thymeleaf templating
|
||||||
- **Frontend**: React-based SPA in `/frontend` directory (replacing legacy Thymeleaf templates)
|
- **Frontend**: React-based SPA in `/frontend` directory (Thymeleaf templates fully replaced)
|
||||||
- **Current Status**: Active development to replace Thymeleaf UI with modern React SPA
|
|
||||||
- **File Storage**: IndexedDB for client-side file persistence and thumbnails
|
- **File Storage**: IndexedDB for client-side file persistence and thumbnails
|
||||||
- **Internationalization**: JSON-based translations (converted from backend .properties)
|
- **Internationalization**: JSON-based translations (converted from backend .properties)
|
||||||
- **URL Parameters**: Deep linking support for tool states and configurations
|
|
||||||
- **PDF Processing**: PDFBox for core PDF operations, LibreOffice for conversions, PDF.js for client-side rendering
|
- **PDF Processing**: PDFBox for core PDF operations, LibreOffice for conversions, PDF.js for client-side rendering
|
||||||
- **Security**: Spring Security with optional authentication (controlled by `DOCKER_ENABLE_SECURITY`)
|
- **Security**: Spring Security with optional authentication (controlled by `DOCKER_ENABLE_SECURITY`)
|
||||||
- **Configuration**: YAML-based configuration with environment variable overrides
|
- **Configuration**: YAML-based configuration with environment variable overrides
|
||||||
@ -59,9 +90,8 @@ npx tailwindcss init -p
|
|||||||
- **Pipeline System**: Automated PDF processing workflows via `PipelineController`
|
- **Pipeline System**: Automated PDF processing workflows via `PipelineController`
|
||||||
- **Security Layer**: Authentication, authorization, and user management (when enabled)
|
- **Security Layer**: Authentication, authorization, and user management (when enabled)
|
||||||
|
|
||||||
### Template System (Legacy + Modern)
|
### Component Architecture
|
||||||
- **Legacy Thymeleaf Templates**: Located in `src/main/resources/templates/` (being phased out)
|
- **React Components**: Located in `frontend/src/components/` and `frontend/src/tools/`
|
||||||
- **Modern React Components**: Located in `frontend/src/components/` and `frontend/src/tools/`
|
|
||||||
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
|
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
|
||||||
- **Internationalization**:
|
- **Internationalization**:
|
||||||
- Backend: `messages_*.properties` files
|
- Backend: `messages_*.properties` files
|
||||||
@ -91,13 +121,14 @@ npx tailwindcss init -p
|
|||||||
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
|
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
|
||||||
5. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
|
5. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
|
||||||
|
|
||||||
## Frontend Migration Notes
|
## Frontend Architecture Status
|
||||||
|
|
||||||
- **Current Branch**: `feature/react-overhaul` - Active React SPA development
|
- **Core Status**: React SPA architecture complete with multi-tool workflow support
|
||||||
- **Migration Status**: Core tools (Split, Merge, Compress) converted to React with URL parameter support
|
- **State Management**: FileContext handles all file operations and tool navigation
|
||||||
- **File Management**: Implemented IndexedDB storage with thumbnail generation using PDF.js
|
- **File Processing**: Production-ready with memory management for large PDF workflows (up to 100GB+)
|
||||||
- **Tools Architecture**: Each tool receives `params` and `updateParams` for URL state synchronization
|
- **Tool Integration**: Standardized tool interface - see `src/tools/Split.tsx` as reference
|
||||||
- **Remaining Work**: Convert remaining Thymeleaf templates to React components
|
- **Preview System**: Tool results can be previewed without polluting file context (Split tool example)
|
||||||
|
- **Performance**: Web Worker thumbnails, IndexedDB persistence, background processing
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
|
|
||||||
@ -108,6 +139,11 @@ npx tailwindcss init -p
|
|||||||
- **Backend**: Designed to be stateless - files are processed in memory/temp locations only
|
- **Backend**: Designed to be stateless - files are processed in memory/temp locations only
|
||||||
- **Frontend**: Uses IndexedDB for client-side file storage and caching (with thumbnails)
|
- **Frontend**: Uses IndexedDB for client-side file storage and caching (with thumbnails)
|
||||||
- **Security**: When `DOCKER_ENABLE_SECURITY=false`, security-related classes are excluded from compilation
|
- **Security**: When `DOCKER_ENABLE_SECURITY=false`, security-related classes are excluded from compilation
|
||||||
|
- **FileContext**: All file operations MUST go through FileContext - never bypass with direct File handling
|
||||||
|
- **Memory Management**: Manual cleanup required for PDF.js documents and blob URLs - don't remove cleanup code
|
||||||
|
- **Tool Development**: New tools should follow Split tool pattern (`src/tools/Split.tsx`)
|
||||||
|
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||||
|
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||||
|
|
||||||
## Communication Style
|
## Communication Style
|
||||||
- Be direct and to the point
|
- Be direct and to the point
|
||||||
|
@ -25,7 +25,7 @@ Please make sure your Pull Request adheres to the following guidelines:
|
|||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
|
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](devGuide/HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
@ -37,7 +37,18 @@ First, make sure you've read the section [Pull Requests](#pull-requests).
|
|||||||
|
|
||||||
If, at any point in time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
|
If, at any point in time, you have a question, please feel free to ask in the same issue thread or in our [Discord](https://discord.gg/FJUSXUSYec).
|
||||||
|
|
||||||
Developers should review our [Developer Guide](DeveloperGuide.md)
|
## Developer Documentation
|
||||||
|
|
||||||
|
For technical guides, setup instructions, and development resources, please see our [Developer Documentation](devGuide/) which includes:
|
||||||
|
|
||||||
|
- [Developer Guide](devGuide/DeveloperGuide.md) - Main setup and architecture guide
|
||||||
|
- [Exception Handling Guide](devGuide/EXCEPTION_HANDLING_GUIDE.md) - Error handling patterns and i18n
|
||||||
|
- [Translation Guide](devGuide/HowToAddNewLanguage.md) - Adding new languages
|
||||||
|
- And more in the [devGuide folder](devGuide/)
|
||||||
|
|
||||||
|
For configuration and usage guides, see:
|
||||||
|
- [Database Guide](DATABASE.md) - Database setup and configuration
|
||||||
|
- [OCR Guide](HowToUseOCR.md) - OCR setup and configuration
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
# dockerfile.dev
|
|
||||||
|
|
||||||
# Basisimage: Gradle mit JDK 17 (Debian-basiert)
|
|
||||||
FROM gradle:8.14-jdk17
|
|
||||||
|
|
||||||
# Als Root-Benutzer arbeiten, um benötigte Pakete zu installieren
|
|
||||||
USER root
|
|
||||||
|
|
||||||
# Set GRADLE_HOME und füge Gradle zum PATH hinzu
|
|
||||||
ENV GRADLE_HOME=/opt/gradle
|
|
||||||
ENV PATH="$GRADLE_HOME/bin:$PATH"
|
|
||||||
|
|
||||||
# Update und Installation zusätzlicher Pakete (Debian/Ubuntu-basiert)
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
sudo \
|
|
||||||
libreoffice \
|
|
||||||
poppler-utils \
|
|
||||||
qpdf \
|
|
||||||
# settings.yml | tessdataDir: /usr/share/tesseract-ocr/5/tessdata
|
|
||||||
tesseract-ocr \
|
|
||||||
tesseract-ocr-eng \
|
|
||||||
fonts-terminus fonts-dejavu fonts-font-awesome fonts-noto fonts-noto-core fonts-noto-cjk fonts-noto-extra fonts-liberation fonts-linuxlibertine \
|
|
||||||
python3-uno \
|
|
||||||
python3-venv \
|
|
||||||
# ss -tln
|
|
||||||
iproute2 \
|
|
||||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Setze die Environment Variable für setuptools
|
|
||||||
ENV SETUPTOOLS_USE_DISTUTILS=local \
|
|
||||||
STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
|
|
||||||
TMPDIR=/tmp/stirling-pdf \
|
|
||||||
TEMP=/tmp/stirling-pdf \
|
|
||||||
TMP=/tmp/stirling-pdf
|
|
||||||
|
|
||||||
# Installation der benötigten Python-Pakete
|
|
||||||
RUN python3 -m venv --system-site-packages /opt/venv \
|
|
||||||
&& . /opt/venv/bin/activate \
|
|
||||||
&& pip install --upgrade pip setuptools \
|
|
||||||
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
|
|
||||||
|
|
||||||
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
|
|
||||||
COPY . /workspace
|
|
||||||
|
|
||||||
RUN mkdir -p /tmp/stirling-pdf \
|
|
||||||
&& adduser --disabled-password --gecos '' devuser \
|
|
||||||
&& chown -R devuser:devuser /home/devuser /workspace /tmp/stirling-pdf
|
|
||||||
RUN echo "devuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/devuser \
|
|
||||||
&& chmod 0440 /etc/sudoers.d/devuser
|
|
||||||
|
|
||||||
# Setze das Arbeitsverzeichnis (wird später per Bind-Mount überschrieben)
|
|
||||||
WORKDIR /workspace
|
|
||||||
|
|
||||||
RUN chmod +x /workspace/.devcontainer/git-init.sh
|
|
||||||
RUN sudo chmod +x /workspace/.devcontainer/init-setup.sh
|
|
||||||
|
|
||||||
# Wechsel zum Nicht‑Root Benutzer
|
|
||||||
USER devuser
|
|
4
LICENSE
4
LICENSE
@ -4,8 +4,8 @@ Copyright (c) 2025 Stirling PDF Inc.
|
|||||||
|
|
||||||
Portions of this software are licensed as follows:
|
Portions of this software are licensed as follows:
|
||||||
|
|
||||||
* All content that resides under the "proprietary/" directory of this repository,
|
* All content that resides under the "app/proprietary/" directory of this repository,
|
||||||
if that directory exists, is licensed under the license defined in "proprietary/LICENSE".
|
if that directory exists, is licensed under the license defined in "app/proprietary/LICENSE".
|
||||||
* Content outside of the above mentioned directories or restrictions above is
|
* Content outside of the above mentioned directories or restrictions above is
|
||||||
available under the MIT License as defined below.
|
available under the MIT License as defined below.
|
||||||
|
|
||||||
|
80
README.md
80
README.md
@ -29,7 +29,7 @@ All documentation available at [https://docs.stirlingpdf.com/](https://docs.stir
|
|||||||
- API for integration with external scripts
|
- API for integration with external scripts
|
||||||
- Optional Login and Authentication support (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security) for documentation)
|
- Optional Login and Authentication support (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security) for documentation)
|
||||||
- Database Backup and Import (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/DATABASE) for documentation)
|
- Database Backup and Import (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/DATABASE) for documentation)
|
||||||
- Enterprise features like SSO see [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
- Enterprise features like SSO (see [here](https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration) for documentation)
|
||||||
|
|
||||||
## PDF Features
|
## PDF Features
|
||||||
|
|
||||||
@ -116,47 +116,47 @@ Stirling-PDF currently supports 40 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Slovenian (Slovenščina) (sl_SI) |  |
|
| Slovenian (Slovenščina) (sl_SI) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
| Tibetan (བོད་ཡིག་) (bo_CN) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
| Malayalam (മലയാളം) (ml_IN) |  |
|
| Malayalam (മലയാളം) (ml_IN) |  |
|
||||||
|
|
||||||
## Stirling PDF Enterprise
|
## Stirling PDF Enterprise
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ Check out our [Enterprise docs](https://docs.stirlingpdf.com/Pro)
|
|||||||
|
|
||||||
Join our community:
|
Join our community:
|
||||||
- [Contribution Guidelines](CONTRIBUTING.md)
|
- [Contribution Guidelines](CONTRIBUTING.md)
|
||||||
- [Translation Guide (How to add custom languages)](HowToAddNewLanguage.md)
|
- [Translation Guide (How to add custom languages)](devGuide/HowToAddNewLanguage.md)
|
||||||
|
- [Developer Guide](devGuide/DeveloperGuide.md)
|
||||||
- [Issue Tracker](https://github.com/Stirling-Tools/Stirling-PDF/issues)
|
- [Issue Tracker](https://github.com/Stirling-Tools/Stirling-PDF/issues)
|
||||||
- [Discord Community](https://discord.gg/HYmhKj45pU)
|
- [Discord Community](https://discord.gg/HYmhKj45pU)
|
||||||
- [Developer Guide](DeveloperGuide.md)
|
|
||||||
|
2
common/.gitignore → app/common/.gitignore
vendored
2
common/.gitignore → app/common/.gitignore
vendored
@ -124,7 +124,7 @@ SwaggerDoc.json
|
|||||||
*.rar
|
*.rar
|
||||||
*.db
|
*.db
|
||||||
/build
|
/build
|
||||||
/common/build/
|
/app/common/build/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
@ -21,12 +21,12 @@ dependencies {
|
|||||||
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||||
api 'com.fathzer:javaluator:3.0.6'
|
api 'com.fathzer:javaluator:3.0.6'
|
||||||
api 'com.posthog.java:posthog:1.2.0'
|
api 'com.posthog.java:posthog:1.2.0'
|
||||||
api 'org.apache.commons:commons-lang3:3.17.0'
|
api 'org.apache.commons:commons-lang3:3.18.0'
|
||||||
api 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor
|
api 'com.drewnoakes:metadata-extractor:2.19.0' // Image metadata extractor
|
||||||
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||||
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
||||||
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||||
api 'org.snakeyaml:snakeyaml-engine:2.9'
|
api 'org.snakeyaml:snakeyaml-engine:2.10'
|
||||||
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
||||||
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
||||||
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.3'
|
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.3'
|
@ -43,6 +43,11 @@ public class AutoJobAspect {
|
|||||||
// This aspect will run before any audit aspects due to @Order(0)
|
// This aspect will run before any audit aspects due to @Order(0)
|
||||||
// Extract parameters from the request and annotation
|
// Extract parameters from the request and annotation
|
||||||
boolean async = Boolean.parseBoolean(request.getParameter("async"));
|
boolean async = Boolean.parseBoolean(request.getParameter("async"));
|
||||||
|
log.debug(
|
||||||
|
"AutoJobAspect: Processing {} {} with async={}",
|
||||||
|
request.getMethod(),
|
||||||
|
request.getRequestURI(),
|
||||||
|
async);
|
||||||
long timeout = autoJobPostMapping.timeout();
|
long timeout = autoJobPostMapping.timeout();
|
||||||
int retryCount = autoJobPostMapping.retryCount();
|
int retryCount = autoJobPostMapping.retryCount();
|
||||||
boolean trackProgress = autoJobPostMapping.trackProgress();
|
boolean trackProgress = autoJobPostMapping.trackProgress();
|
||||||
@ -54,19 +59,8 @@ public class AutoJobAspect {
|
|||||||
retryCount,
|
retryCount,
|
||||||
trackProgress);
|
trackProgress);
|
||||||
|
|
||||||
// Copy and process arguments
|
// Process arguments in-place to avoid type mismatch issues
|
||||||
// In a test environment, we might need to update the original objects for verification
|
Object[] args = processArgsInPlace(joinPoint.getArgs(), async);
|
||||||
boolean isTestEnvironment = false;
|
|
||||||
try {
|
|
||||||
isTestEnvironment = Class.forName("org.junit.jupiter.api.Test") != null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
// Not in a test environment
|
|
||||||
}
|
|
||||||
|
|
||||||
Object[] args =
|
|
||||||
isTestEnvironment
|
|
||||||
? processArgsInPlace(joinPoint.getArgs(), async)
|
|
||||||
: copyAndProcessArgs(joinPoint.getArgs(), async);
|
|
||||||
|
|
||||||
// Extract queueable and resourceWeight parameters and validate
|
// Extract queueable and resourceWeight parameters and validate
|
||||||
boolean queueable = autoJobPostMapping.queueable();
|
boolean queueable = autoJobPostMapping.queueable();
|
||||||
@ -230,78 +224,8 @@ public class AutoJobAspect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates deep copies of arguments when needed to avoid mutating the original objects
|
* Processes arguments in-place to handle file resolution and async file persistence. This
|
||||||
* Particularly important for PDFFile objects that might be reused by Spring
|
* approach avoids type mismatch issues by modifying the original objects directly.
|
||||||
*
|
|
||||||
* @param originalArgs The original arguments
|
|
||||||
* @param async Whether this is an async operation
|
|
||||||
* @return A new array with safely processed arguments
|
|
||||||
*/
|
|
||||||
private Object[] copyAndProcessArgs(Object[] originalArgs, boolean async) {
|
|
||||||
if (originalArgs == null || originalArgs.length == 0) {
|
|
||||||
return originalArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object[] processedArgs = new Object[originalArgs.length];
|
|
||||||
|
|
||||||
// Copy all arguments
|
|
||||||
for (int i = 0; i < originalArgs.length; i++) {
|
|
||||||
Object arg = originalArgs[i];
|
|
||||||
|
|
||||||
if (arg instanceof PDFFile pdfFile) {
|
|
||||||
// Create a copy of PDFFile to avoid mutating the original
|
|
||||||
// Using direct property access instead of reflection for better performance
|
|
||||||
PDFFile pdfFileCopy = new PDFFile();
|
|
||||||
pdfFileCopy.setFileId(pdfFile.getFileId());
|
|
||||||
pdfFileCopy.setFileInput(pdfFile.getFileInput());
|
|
||||||
|
|
||||||
// Case 1: fileId is provided but no fileInput
|
|
||||||
if (pdfFileCopy.getFileInput() == null && pdfFileCopy.getFileId() != null) {
|
|
||||||
try {
|
|
||||||
log.debug("Using fileId {} to get file content", pdfFileCopy.getFileId());
|
|
||||||
MultipartFile file = fileStorage.retrieveFile(pdfFileCopy.getFileId());
|
|
||||||
pdfFileCopy.setFileInput(file);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Failed to resolve file by ID: " + pdfFileCopy.getFileId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Case 2: For async requests, we need to make a copy of the MultipartFile
|
|
||||||
else if (async && pdfFileCopy.getFileInput() != null) {
|
|
||||||
try {
|
|
||||||
log.debug("Making persistent copy of uploaded file for async processing");
|
|
||||||
MultipartFile originalFile = pdfFileCopy.getFileInput();
|
|
||||||
String fileId = fileStorage.storeFile(originalFile);
|
|
||||||
|
|
||||||
// Store the fileId for later reference
|
|
||||||
pdfFileCopy.setFileId(fileId);
|
|
||||||
|
|
||||||
// Replace the original MultipartFile with our persistent copy
|
|
||||||
MultipartFile persistentFile = fileStorage.retrieveFile(fileId);
|
|
||||||
pdfFileCopy.setFileInput(persistentFile);
|
|
||||||
|
|
||||||
log.debug("Created persistent file copy with fileId: {}", fileId);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Failed to create persistent copy of uploaded file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processedArgs[i] = pdfFileCopy;
|
|
||||||
} else {
|
|
||||||
// For non-PDFFile objects, just pass the original reference
|
|
||||||
// If other classes need copy-on-write, add them here
|
|
||||||
processedArgs[i] = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes arguments in-place for testing purposes This is similar to our original
|
|
||||||
* implementation before introducing copy-on-write It's only used in test environments to
|
|
||||||
* maintain test compatibility
|
|
||||||
*
|
*
|
||||||
* @param originalArgs The original arguments
|
* @param originalArgs The original arguments
|
||||||
* @param async Whether this is an async operation
|
* @param async Whether this is an async operation
|
@ -6,7 +6,6 @@ import java.nio.file.Path;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -24,7 +23,6 @@ public class TempFileShutdownHook implements DisposableBean {
|
|||||||
|
|
||||||
private final TempFileRegistry registry;
|
private final TempFileRegistry registry;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public TempFileShutdownHook(TempFileRegistry registry) {
|
public TempFileShutdownHook(TempFileRegistry registry) {
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
|
|
@ -10,7 +10,6 @@ import java.util.Properties;
|
|||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -21,6 +20,7 @@ import org.springframework.core.env.Environment;
|
|||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -148,23 +148,10 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "activeSecurity")
|
@Bean(name = "activeSecurity")
|
||||||
public boolean activeSecurity() {
|
|
||||||
String disableAdditionalFeatures = env.getProperty("DISABLE_ADDITIONAL_FEATURES");
|
|
||||||
|
|
||||||
if (disableAdditionalFeatures != null) {
|
|
||||||
// DISABLE_ADDITIONAL_FEATURES=true means security OFF, so return false
|
|
||||||
// DISABLE_ADDITIONAL_FEATURES=false means security ON, so return true
|
|
||||||
return !Boolean.parseBoolean(disableAdditionalFeatures);
|
|
||||||
}
|
|
||||||
|
|
||||||
return env.getProperty("DOCKER_ENABLE_SECURITY", Boolean.class, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean(name = "missingActiveSecurity")
|
|
||||||
@ConditionalOnMissingClass(
|
|
||||||
"stirling.software.proprietary.security.configuration.SecurityConfiguration")
|
|
||||||
public boolean missingActiveSecurity() {
|
public boolean missingActiveSecurity() {
|
||||||
return true;
|
return ClassUtils.isPresent(
|
||||||
|
"stirling.software.proprietary.security.configuration.SecurityConfiguration",
|
||||||
|
this.getClass().getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "directoryFilter")
|
@Bean(name = "directoryFilter")
|
@ -14,6 +14,7 @@ public class InstallationPathConfig {
|
|||||||
private static final String CONFIG_PATH;
|
private static final String CONFIG_PATH;
|
||||||
private static final String CUSTOM_FILES_PATH;
|
private static final String CUSTOM_FILES_PATH;
|
||||||
private static final String CLIENT_WEBUI_PATH;
|
private static final String CLIENT_WEBUI_PATH;
|
||||||
|
private static final String SCRIPTS_PATH;
|
||||||
|
|
||||||
// Config paths
|
// Config paths
|
||||||
private static final String SETTINGS_PATH;
|
private static final String SETTINGS_PATH;
|
||||||
@ -36,6 +37,7 @@ public class InstallationPathConfig {
|
|||||||
// Initialize config paths
|
// Initialize config paths
|
||||||
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
|
||||||
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
|
||||||
|
SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
|
||||||
|
|
||||||
// Initialize custom file paths
|
// Initialize custom file paths
|
||||||
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
|
||||||
@ -89,6 +91,10 @@ public class InstallationPathConfig {
|
|||||||
return CLIENT_WEBUI_PATH;
|
return CLIENT_WEBUI_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getScriptsPath() {
|
||||||
|
return SCRIPTS_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getSettingsPath() {
|
public static String getSettingsPath() {
|
||||||
return SETTINGS_PATH;
|
return SETTINGS_PATH;
|
||||||
}
|
}
|
@ -545,6 +545,8 @@ public class ApplicationProperties {
|
|||||||
private int calibreSessionLimit;
|
private int calibreSessionLimit;
|
||||||
private int qpdfSessionLimit;
|
private int qpdfSessionLimit;
|
||||||
private int tesseractSessionLimit;
|
private int tesseractSessionLimit;
|
||||||
|
private int ghostscriptSessionLimit;
|
||||||
|
private int ocrMyPdfSessionLimit;
|
||||||
|
|
||||||
public int getQpdfSessionLimit() {
|
public int getQpdfSessionLimit() {
|
||||||
return qpdfSessionLimit > 0 ? qpdfSessionLimit : 2;
|
return qpdfSessionLimit > 0 ? qpdfSessionLimit : 2;
|
||||||
@ -577,6 +579,14 @@ public class ApplicationProperties {
|
|||||||
public int getCalibreSessionLimit() {
|
public int getCalibreSessionLimit() {
|
||||||
return calibreSessionLimit > 0 ? calibreSessionLimit : 1;
|
return calibreSessionLimit > 0 ? calibreSessionLimit : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGhostscriptSessionLimit() {
|
||||||
|
return ghostscriptSessionLimit > 0 ? ghostscriptSessionLimit : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOcrMyPdfSessionLimit() {
|
||||||
|
return ocrMyPdfSessionLimit > 0 ? ocrMyPdfSessionLimit : 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@ -589,6 +599,8 @@ public class ApplicationProperties {
|
|||||||
private long calibreTimeoutMinutes;
|
private long calibreTimeoutMinutes;
|
||||||
private long tesseractTimeoutMinutes;
|
private long tesseractTimeoutMinutes;
|
||||||
private long qpdfTimeoutMinutes;
|
private long qpdfTimeoutMinutes;
|
||||||
|
private long ghostscriptTimeoutMinutes;
|
||||||
|
private long ocrMyPdfTimeoutMinutes;
|
||||||
|
|
||||||
public long getTesseractTimeoutMinutes() {
|
public long getTesseractTimeoutMinutes() {
|
||||||
return tesseractTimeoutMinutes > 0 ? tesseractTimeoutMinutes : 30;
|
return tesseractTimeoutMinutes > 0 ? tesseractTimeoutMinutes : 30;
|
||||||
@ -621,6 +633,14 @@ public class ApplicationProperties {
|
|||||||
public long getCalibreTimeoutMinutes() {
|
public long getCalibreTimeoutMinutes() {
|
||||||
return calibreTimeoutMinutes > 0 ? calibreTimeoutMinutes : 30;
|
return calibreTimeoutMinutes > 0 ? calibreTimeoutMinutes : 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getGhostscriptTimeoutMinutes() {
|
||||||
|
return ghostscriptTimeoutMinutes > 0 ? ghostscriptTimeoutMinutes : 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOcrMyPdfTimeoutMinutes() {
|
||||||
|
return ocrMyPdfTimeoutMinutes > 0 ? ocrMyPdfTimeoutMinutes : 30;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package stirling.software.common.model.job;
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -26,14 +29,8 @@ public class JobResult {
|
|||||||
/** Error message if the job failed */
|
/** Error message if the job failed */
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
/** The file ID of the result file, if applicable */
|
/** List of result files for jobs that produce files */
|
||||||
private String fileId;
|
@JsonIgnore private List<ResultFile> resultFiles;
|
||||||
|
|
||||||
/** Original file name, if applicable */
|
|
||||||
private String originalFileName;
|
|
||||||
|
|
||||||
/** MIME type of the result, if applicable */
|
|
||||||
private String contentType;
|
|
||||||
|
|
||||||
/** Time when the job was created */
|
/** Time when the job was created */
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@ -64,21 +61,6 @@ public class JobResult {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark this job as complete with a file result
|
|
||||||
*
|
|
||||||
* @param fileId The file ID of the result
|
|
||||||
* @param originalFileName The original file name
|
|
||||||
* @param contentType The content type of the file
|
|
||||||
*/
|
|
||||||
public void completeWithFile(String fileId, String originalFileName, String contentType) {
|
|
||||||
this.complete = true;
|
|
||||||
this.fileId = fileId;
|
|
||||||
this.originalFileName = originalFileName;
|
|
||||||
this.contentType = contentType;
|
|
||||||
this.completedAt = LocalDateTime.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this job as complete with a general result
|
* Mark this job as complete with a general result
|
||||||
*
|
*
|
||||||
@ -101,6 +83,67 @@ public class JobResult {
|
|||||||
this.completedAt = LocalDateTime.now();
|
this.completedAt = LocalDateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this job as complete with multiple file results
|
||||||
|
*
|
||||||
|
* @param resultFiles The list of result files
|
||||||
|
*/
|
||||||
|
public void completeWithFiles(List<ResultFile> resultFiles) {
|
||||||
|
this.complete = true;
|
||||||
|
this.resultFiles = new ArrayList<>(resultFiles);
|
||||||
|
this.completedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark this job as complete with a single file result (convenience method)
|
||||||
|
*
|
||||||
|
* @param fileId The file ID of the result
|
||||||
|
* @param fileName The file name
|
||||||
|
* @param contentType The content type of the file
|
||||||
|
* @param fileSize The size of the file in bytes
|
||||||
|
*/
|
||||||
|
public void completeWithSingleFile(
|
||||||
|
String fileId, String fileName, String contentType, long fileSize) {
|
||||||
|
ResultFile resultFile =
|
||||||
|
ResultFile.builder()
|
||||||
|
.fileId(fileId)
|
||||||
|
.fileName(fileName)
|
||||||
|
.contentType(contentType)
|
||||||
|
.fileSize(fileSize)
|
||||||
|
.build();
|
||||||
|
completeWithFiles(List.of(resultFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this job has file results
|
||||||
|
*
|
||||||
|
* @return true if this job has file results, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean hasFiles() {
|
||||||
|
return resultFiles != null && !resultFiles.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this job has multiple file results
|
||||||
|
*
|
||||||
|
* @return true if this job has multiple file results, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean hasMultipleFiles() {
|
||||||
|
return resultFiles != null && resultFiles.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all result files
|
||||||
|
*
|
||||||
|
* @return List of result files
|
||||||
|
*/
|
||||||
|
public List<ResultFile> getAllResultFiles() {
|
||||||
|
if (resultFiles != null && !resultFiles.isEmpty()) {
|
||||||
|
return Collections.unmodifiableList(resultFiles);
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a note to this job
|
* Add a note to this job
|
||||||
*
|
*
|
@ -0,0 +1,26 @@
|
|||||||
|
package stirling.software.common.model.job;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/** Represents a single file result from a job execution */
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ResultFile {
|
||||||
|
|
||||||
|
/** The file ID for accessing the file */
|
||||||
|
private String fileId;
|
||||||
|
|
||||||
|
/** The original file name */
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
/** MIME type of the file */
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
/** Size of the file in bytes */
|
||||||
|
private long fileSize;
|
||||||
|
}
|
@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.api.PDFFile;
|
import stirling.software.common.model.api.PDFFile;
|
||||||
import stirling.software.common.util.ApplicationContextProvider;
|
import stirling.software.common.util.ApplicationContextProvider;
|
||||||
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
import stirling.software.common.util.TempFileManager;
|
import stirling.software.common.util.TempFileManager;
|
||||||
import stirling.software.common.util.TempFileRegistry;
|
import stirling.software.common.util.TempFileRegistry;
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
*/
|
*/
|
||||||
public PDDocument load(File file, boolean readOnly) throws IOException {
|
public PDDocument load(File file, boolean readOnly) throws IOException {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new IllegalArgumentException("File cannot be null");
|
throw ExceptionUtils.createNullArgumentException("File");
|
||||||
}
|
}
|
||||||
|
|
||||||
long fileSize = file.length();
|
long fileSize = file.length();
|
||||||
@ -109,7 +110,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
*/
|
*/
|
||||||
public PDDocument load(Path path, boolean readOnly) throws IOException {
|
public PDDocument load(Path path, boolean readOnly) throws IOException {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
throw new IllegalArgumentException("File cannot be null");
|
throw ExceptionUtils.createNullArgumentException("File");
|
||||||
}
|
}
|
||||||
|
|
||||||
long fileSize = Files.size(path);
|
long fileSize = Files.size(path);
|
||||||
@ -130,7 +131,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
/** Load a PDF from byte array with automatic optimization and read-only option. */
|
/** Load a PDF from byte array with automatic optimization and read-only option. */
|
||||||
public PDDocument load(byte[] input, boolean readOnly) throws IOException {
|
public PDDocument load(byte[] input, boolean readOnly) throws IOException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
throw new IllegalArgumentException("Input bytes cannot be null");
|
throw ExceptionUtils.createNullArgumentException("Input bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
long dataSize = input.length;
|
long dataSize = input.length;
|
||||||
@ -151,7 +152,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
/** Load a PDF from InputStream with automatic optimization and read-only option. */
|
/** Load a PDF from InputStream with automatic optimization and read-only option. */
|
||||||
public PDDocument load(InputStream input, boolean readOnly) throws IOException {
|
public PDDocument load(InputStream input, boolean readOnly) throws IOException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
throw new IllegalArgumentException("InputStream cannot be null");
|
throw ExceptionUtils.createNullArgumentException("InputStream");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we don't know the size upfront, buffer to a temp file
|
// Since we don't know the size upfront, buffer to a temp file
|
||||||
@ -174,7 +175,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
public PDDocument load(InputStream input, String password, boolean readOnly)
|
public PDDocument load(InputStream input, String password, boolean readOnly)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
throw new IllegalArgumentException("InputStream cannot be null");
|
throw ExceptionUtils.createNullArgumentException("InputStream");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we don't know the size upfront, buffer to a temp file
|
// Since we don't know the size upfront, buffer to a temp file
|
||||||
@ -292,9 +293,32 @@ public class CustomPDFDocumentFactory {
|
|||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unsupported source type: " + source.getClass());
|
throw new IllegalArgumentException("Unsupported source type: " + source.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureResourceCacheIfNeeded(document, contentSize);
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure resource cache based on content size and memory constraints. Disables resource
|
||||||
|
* cache for large files or when memory is low to prevent OOM errors.
|
||||||
|
*/
|
||||||
|
private void configureResourceCacheIfNeeded(PDDocument document, long contentSize) {
|
||||||
|
if (contentSize > LARGE_FILE_THRESHOLD) {
|
||||||
|
document.setResourceCache(null);
|
||||||
|
} else {
|
||||||
|
// Check current memory status for smaller files
|
||||||
|
long maxMemory = Runtime.getRuntime().maxMemory();
|
||||||
|
long usedMemory =
|
||||||
|
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||||
|
double freeMemoryPercent = (double) (maxMemory - usedMemory) / maxMemory * 100;
|
||||||
|
|
||||||
|
if (freeMemoryPercent < MIN_FREE_MEMORY_PERCENTAGE) {
|
||||||
|
document.setResourceCache(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Load a PDF with password protection using adaptive loading strategies */
|
/** Load a PDF with password protection using adaptive loading strategies */
|
||||||
private PDDocument loadAdaptivelyWithPassword(Object source, long contentSize, String password)
|
private PDDocument loadAdaptivelyWithPassword(Object source, long contentSize, String password)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -313,6 +337,9 @@ public class CustomPDFDocumentFactory {
|
|||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unsupported source type: " + source.getClass());
|
throw new IllegalArgumentException("Unsupported source type: " + source.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureResourceCacheIfNeeded(document, contentSize);
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +381,12 @@ public class CustomPDFDocumentFactory {
|
|||||||
|
|
||||||
private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
|
private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
try {
|
||||||
return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);
|
return Loader.loadPDF(new DeletingRandomAccessFile(file), "", null, null, cache);
|
||||||
|
} catch (IOException e) {
|
||||||
|
ExceptionUtils.logException("PDF loading from file", e);
|
||||||
|
throw ExceptionUtils.handlePdfException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache)
|
private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache)
|
||||||
@ -366,7 +398,13 @@ public class CustomPDFDocumentFactory {
|
|||||||
Files.write(tempFile, bytes);
|
Files.write(tempFile, bytes);
|
||||||
return loadFromFile(tempFile.toFile(), size, cache);
|
return loadFromFile(tempFile.toFile(), size, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
return Loader.loadPDF(bytes, "", null, null, cache);
|
return Loader.loadPDF(bytes, "", null, null, cache);
|
||||||
|
} catch (IOException e) {
|
||||||
|
ExceptionUtils.logException("PDF loading from bytes", e);
|
||||||
|
throw ExceptionUtils.handlePdfException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PDDocument createNewDocument(MemoryUsageSetting settings) throws IOException {
|
public PDDocument createNewDocument(MemoryUsageSetting settings) throws IOException {
|
||||||
@ -399,7 +437,7 @@ public class CustomPDFDocumentFactory {
|
|||||||
try {
|
try {
|
||||||
document.setAllSecurityToBeRemoved(true);
|
document.setAllSecurityToBeRemoved(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Decryption failed", e);
|
ExceptionUtils.logException("PDF decryption", e);
|
||||||
throw new IOException("PDF decryption failed", e);
|
throw new IOException("PDF decryption failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -131,14 +131,46 @@ public class FileStorage {
|
|||||||
return Files.exists(filePath);
|
return Files.exists(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a file by its ID without loading the content into memory
|
||||||
|
*
|
||||||
|
* @param fileId The ID of the file
|
||||||
|
* @return The size of the file in bytes
|
||||||
|
* @throws IOException If the file doesn't exist or can't be read
|
||||||
|
*/
|
||||||
|
public long getFileSize(String fileId) throws IOException {
|
||||||
|
Path filePath = getFilePath(fileId);
|
||||||
|
|
||||||
|
if (!Files.exists(filePath)) {
|
||||||
|
throw new IOException("File not found with ID: " + fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Files.size(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the path for a file ID
|
* Get the path for a file ID
|
||||||
*
|
*
|
||||||
* @param fileId The ID of the file
|
* @param fileId The ID of the file
|
||||||
* @return The path to the file
|
* @return The path to the file
|
||||||
|
* @throws IllegalArgumentException if fileId contains path traversal characters or resolves
|
||||||
|
* outside base directory
|
||||||
*/
|
*/
|
||||||
private Path getFilePath(String fileId) {
|
private Path getFilePath(String fileId) {
|
||||||
return Path.of(tempDirPath).resolve(fileId);
|
// Validate fileId to prevent path traversal
|
||||||
|
if (fileId.contains("..") || fileId.contains("/") || fileId.contains("\\")) {
|
||||||
|
throw new IllegalArgumentException("Invalid file ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path basePath = Path.of(tempDirPath).normalize().toAbsolutePath();
|
||||||
|
Path resolvedPath = basePath.resolve(fileId).normalize();
|
||||||
|
|
||||||
|
// Ensure resolved path is within the base directory
|
||||||
|
if (!resolvedPath.startsWith(basePath)) {
|
||||||
|
throw new IllegalArgumentException("File ID resolves to an invalid path");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -1,15 +1,26 @@
|
|||||||
package stirling.software.common.service;
|
package stirling.software.common.service;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.github.pixee.security.ZipSecurity;
|
||||||
|
|
||||||
import jakarta.annotation.PreDestroy;
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
@ -17,6 +28,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import stirling.software.common.model.job.JobResult;
|
import stirling.software.common.model.job.JobResult;
|
||||||
import stirling.software.common.model.job.JobStats;
|
import stirling.software.common.model.job.JobStats;
|
||||||
|
import stirling.software.common.model.job.ResultFile;
|
||||||
|
|
||||||
/** Manages async tasks and their results */
|
/** Manages async tasks and their results */
|
||||||
@Service
|
@Service
|
||||||
@ -80,8 +92,53 @@ public class TaskManager {
|
|||||||
public void setFileResult(
|
public void setFileResult(
|
||||||
String jobId, String fileId, String originalFileName, String contentType) {
|
String jobId, String fileId, String originalFileName, String contentType) {
|
||||||
JobResult jobResult = getOrCreateJobResult(jobId);
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
jobResult.completeWithFile(fileId, originalFileName, contentType);
|
|
||||||
log.debug("Set file result for job ID: {} with file ID: {}", jobId, fileId);
|
// Check if this is a ZIP file that should be extracted
|
||||||
|
if (isZipFile(contentType, originalFileName)) {
|
||||||
|
try {
|
||||||
|
List<ResultFile> extractedFiles =
|
||||||
|
extractZipToIndividualFiles(fileId, originalFileName);
|
||||||
|
if (!extractedFiles.isEmpty()) {
|
||||||
|
jobResult.completeWithFiles(extractedFiles);
|
||||||
|
log.debug(
|
||||||
|
"Set multiple file results for job ID: {} with {} files extracted from ZIP",
|
||||||
|
jobId,
|
||||||
|
extractedFiles.size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to extract ZIP file for job {}: {}. Falling back to single file result.",
|
||||||
|
jobId,
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle as single file using new ResultFile approach
|
||||||
|
try {
|
||||||
|
long fileSize = fileStorage.getFileSize(fileId);
|
||||||
|
jobResult.completeWithSingleFile(fileId, originalFileName, contentType, fileSize);
|
||||||
|
log.debug("Set single file result for job ID: {} with file ID: {}", jobId, fileId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to get file size for job {}: {}. Using size 0.", jobId, e.getMessage());
|
||||||
|
jobResult.completeWithSingleFile(fileId, originalFileName, contentType, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the result of a task as multiple files
|
||||||
|
*
|
||||||
|
* @param jobId The job ID
|
||||||
|
* @param resultFiles The list of result files
|
||||||
|
*/
|
||||||
|
public void setMultipleFileResults(String jobId, List<ResultFile> resultFiles) {
|
||||||
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
|
jobResult.completeWithFiles(resultFiles);
|
||||||
|
log.debug(
|
||||||
|
"Set multiple file results for job ID: {} with {} files",
|
||||||
|
jobId,
|
||||||
|
resultFiles.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,7 +161,7 @@ public class TaskManager {
|
|||||||
public void setComplete(String jobId) {
|
public void setComplete(String jobId) {
|
||||||
JobResult jobResult = getOrCreateJobResult(jobId);
|
JobResult jobResult = getOrCreateJobResult(jobId);
|
||||||
if (jobResult.getResult() == null
|
if (jobResult.getResult() == null
|
||||||
&& jobResult.getFileId() == null
|
&& !jobResult.hasFiles()
|
||||||
&& jobResult.getError() == null) {
|
&& jobResult.getError() == null) {
|
||||||
// If no result or error has been set, mark it as complete with an empty result
|
// If no result or error has been set, mark it as complete with an empty result
|
||||||
jobResult.completeWithResult("Task completed successfully");
|
jobResult.completeWithResult("Task completed successfully");
|
||||||
@ -186,7 +243,7 @@ public class TaskManager {
|
|||||||
failedJobs++;
|
failedJobs++;
|
||||||
} else {
|
} else {
|
||||||
successfulJobs++;
|
successfulJobs++;
|
||||||
if (result.getFileId() != null) {
|
if (result.hasFiles()) {
|
||||||
fileResultJobs++;
|
fileResultJobs++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,17 +307,8 @@ public class TaskManager {
|
|||||||
&& result.getCompletedAt() != null
|
&& result.getCompletedAt() != null
|
||||||
&& result.getCompletedAt().isBefore(expiryThreshold)) {
|
&& result.getCompletedAt().isBefore(expiryThreshold)) {
|
||||||
|
|
||||||
// If the job has a file result, delete the file
|
// Clean up file results
|
||||||
if (result.getFileId() != null) {
|
cleanupJobFiles(result, entry.getKey());
|
||||||
try {
|
|
||||||
fileStorage.deleteFile(result.getFileId());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn(
|
|
||||||
"Failed to delete file for job {}: {}",
|
|
||||||
entry.getKey(),
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the job result
|
// Remove the job result
|
||||||
jobResults.remove(entry.getKey());
|
jobResults.remove(entry.getKey());
|
||||||
@ -290,4 +338,129 @@ public class TaskManager {
|
|||||||
cleanupExecutor.shutdownNow();
|
cleanupExecutor.shutdownNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check if a file is a ZIP file based on content type and filename */
|
||||||
|
private boolean isZipFile(String contentType, String fileName) {
|
||||||
|
if (contentType != null
|
||||||
|
&& (contentType.equals("application/zip")
|
||||||
|
|| contentType.equals("application/x-zip-compressed"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName != null && fileName.toLowerCase().endsWith(".zip")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract a ZIP file into individual files and store them */
|
||||||
|
private List<ResultFile> extractZipToIndividualFiles(
|
||||||
|
String zipFileId, String originalZipFileName) throws IOException {
|
||||||
|
List<ResultFile> extractedFiles = new ArrayList<>();
|
||||||
|
|
||||||
|
MultipartFile zipFile = fileStorage.retrieveFile(zipFileId);
|
||||||
|
|
||||||
|
try (ZipInputStream zipIn =
|
||||||
|
ZipSecurity.createHardenedInputStream(
|
||||||
|
new ByteArrayInputStream(zipFile.getBytes()))) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zipIn.getNextEntry()) != null) {
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
// Use buffered reading for memory safety
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = zipIn.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
byte[] fileContent = out.toByteArray();
|
||||||
|
|
||||||
|
String contentType = determineContentType(entry.getName());
|
||||||
|
String individualFileId = fileStorage.storeBytes(fileContent, entry.getName());
|
||||||
|
|
||||||
|
ResultFile resultFile =
|
||||||
|
ResultFile.builder()
|
||||||
|
.fileId(individualFileId)
|
||||||
|
.fileName(entry.getName())
|
||||||
|
.contentType(contentType)
|
||||||
|
.fileSize(fileContent.length)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
extractedFiles.add(resultFile);
|
||||||
|
log.debug(
|
||||||
|
"Extracted file: {} (size: {} bytes)",
|
||||||
|
entry.getName(),
|
||||||
|
fileContent.length);
|
||||||
|
}
|
||||||
|
zipIn.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the original ZIP file after extraction
|
||||||
|
try {
|
||||||
|
fileStorage.deleteFile(zipFileId);
|
||||||
|
log.debug("Cleaned up original ZIP file: {}", zipFileId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to clean up original ZIP file {}: {}", zipFileId, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determine content type based on file extension */
|
||||||
|
private String determineContentType(String fileName) {
|
||||||
|
if (fileName == null) {
|
||||||
|
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerName = fileName.toLowerCase();
|
||||||
|
if (lowerName.endsWith(".pdf")) {
|
||||||
|
return MediaType.APPLICATION_PDF_VALUE;
|
||||||
|
} else if (lowerName.endsWith(".txt")) {
|
||||||
|
return MediaType.TEXT_PLAIN_VALUE;
|
||||||
|
} else if (lowerName.endsWith(".json")) {
|
||||||
|
return MediaType.APPLICATION_JSON_VALUE;
|
||||||
|
} else if (lowerName.endsWith(".xml")) {
|
||||||
|
return MediaType.APPLICATION_XML_VALUE;
|
||||||
|
} else if (lowerName.endsWith(".jpg") || lowerName.endsWith(".jpeg")) {
|
||||||
|
return MediaType.IMAGE_JPEG_VALUE;
|
||||||
|
} else if (lowerName.endsWith(".png")) {
|
||||||
|
return MediaType.IMAGE_PNG_VALUE;
|
||||||
|
} else {
|
||||||
|
return MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clean up files associated with a job result */
|
||||||
|
private void cleanupJobFiles(JobResult result, String jobId) {
|
||||||
|
// Clean up all result files
|
||||||
|
if (result.hasFiles()) {
|
||||||
|
for (ResultFile resultFile : result.getAllResultFiles()) {
|
||||||
|
try {
|
||||||
|
fileStorage.deleteFile(resultFile.getFileId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to delete file {} for job {}: {}",
|
||||||
|
resultFile.getFileId(),
|
||||||
|
jobId,
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the ResultFile metadata for a given file ID by searching through all job results */
|
||||||
|
public ResultFile findResultFileByFileId(String fileId) {
|
||||||
|
for (JobResult jobResult : jobResults.values()) {
|
||||||
|
if (jobResult.hasFiles()) {
|
||||||
|
for (ResultFile resultFile : jobResult.getAllResultFiles()) {
|
||||||
|
if (fileId.equals(resultFile.getFileId())) {
|
||||||
|
return resultFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
@ -154,19 +154,22 @@ public class TempFileCleanupService {
|
|||||||
boolean containerMode = isContainerMode();
|
boolean containerMode = isContainerMode();
|
||||||
int unregisteredDeletedCount = cleanupUnregisteredFiles(containerMode, true, maxAgeMillis);
|
int unregisteredDeletedCount = cleanupUnregisteredFiles(containerMode, true, maxAgeMillis);
|
||||||
|
|
||||||
|
if (registeredDeletedCount > 0
|
||||||
|
|| unregisteredDeletedCount > 0
|
||||||
|
|| directoriesDeletedCount > 0) {
|
||||||
log.info(
|
log.info(
|
||||||
"Scheduled cleanup complete. Deleted {} registered files, {} unregistered files, {} directories",
|
"Scheduled cleanup complete. Deleted {} registered files, {} unregistered files, {} directories",
|
||||||
registeredDeletedCount,
|
registeredDeletedCount,
|
||||||
unregisteredDeletedCount,
|
unregisteredDeletedCount,
|
||||||
directoriesDeletedCount);
|
directoriesDeletedCount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform startup cleanup of stale temporary files from previous runs. This is especially
|
* Perform startup cleanup of stale temporary files from previous runs. This is especially
|
||||||
* important in Docker environments where temp files persist between container restarts.
|
* important in Docker environments where temp files persist between container restarts.
|
||||||
*/
|
*/
|
||||||
private void runStartupCleanup() {
|
private void runStartupCleanup() {
|
||||||
log.info("Running startup temporary file cleanup");
|
|
||||||
boolean containerMode = isContainerMode();
|
boolean containerMode = isContainerMode();
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
@ -178,7 +181,6 @@ public class TempFileCleanupService {
|
|||||||
long maxAgeMillis = containerMode ? 0 : 24 * 60 * 60 * 1000; // 0 or 24 hours
|
long maxAgeMillis = containerMode ? 0 : 24 * 60 * 60 * 1000; // 0 or 24 hours
|
||||||
|
|
||||||
int totalDeletedCount = cleanupUnregisteredFiles(containerMode, false, maxAgeMillis);
|
int totalDeletedCount = cleanupUnregisteredFiles(containerMode, false, maxAgeMillis);
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
"Startup cleanup complete. Deleted {} temporary files/directories",
|
"Startup cleanup complete. Deleted {} temporary files/directories",
|
||||||
totalDeletedCount);
|
totalDeletedCount);
|
||||||
@ -225,7 +227,7 @@ public class TempFileCleanupService {
|
|||||||
tempDir -> {
|
tempDir -> {
|
||||||
try {
|
try {
|
||||||
String phase = isScheduled ? "scheduled" : "startup";
|
String phase = isScheduled ? "scheduled" : "startup";
|
||||||
log.info(
|
log.debug(
|
||||||
"Scanning directory for {} cleanup: {}",
|
"Scanning directory for {} cleanup: {}",
|
||||||
phase,
|
phase,
|
||||||
tempDir);
|
tempDir);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user