mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f2c4e7205 | ||
|
|
9c37562029 | ||
|
|
37a31b3783 | ||
|
|
87212c8315 | ||
|
|
e5f239468b | ||
|
|
35db8e6d77 | ||
|
|
b235bda9e9 | ||
|
|
5ae61ee0ad | ||
|
|
34ff6a899b | ||
|
|
7e08a8d4a6 | ||
|
|
854804708a | ||
|
|
bff4983265 | ||
|
|
2c233b62f9 | ||
|
|
4792fe1d30 | ||
|
|
554f0cd785 | ||
|
|
791598114e | ||
|
|
86b4fb6c01 | ||
|
|
852c89fed3 | ||
|
|
51950a618b |
+106
-69
@@ -15,9 +15,16 @@ version: '3'
|
||||
# stripping; cmd.exe also requires `.\` (not bare `gradlew.bat`)
|
||||
# because modern Windows excludes cwd from cmd's search path.
|
||||
|
||||
vars:
|
||||
GRADLE: '{{if eq OS "windows"}}cmd /c ".\gradlew.bat"{{else}}./gradlew{{end}}'
|
||||
TEST: '{{.GRADLE}} test --no-daemon'
|
||||
FORMAT: '{{.GRADLE}} spotlessApply'
|
||||
FORMATCHECK: '{{.GRADLE}} spotlessCheck'
|
||||
CLEAN: '{{.GRADLE}} clean'
|
||||
|
||||
tasks:
|
||||
dev:
|
||||
desc: "Start backend dev server"
|
||||
desc: "Start the backend dev server"
|
||||
cmds:
|
||||
- task: dev:proprietary
|
||||
vars:
|
||||
@@ -28,7 +35,7 @@ tasks:
|
||||
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN}}'
|
||||
|
||||
dev:proprietary:
|
||||
desc: "Start backend dev server in proprietary mode"
|
||||
desc: "Start the backend dev server in proprietary mode"
|
||||
# `dotenv:` reads from the root Taskfile's directory (".") because this
|
||||
# subtaskfile is included with `dir: .`. Local overrides in
|
||||
# .env.proprietary.local win over the committed .env.proprietary defaults.
|
||||
@@ -43,22 +50,16 @@ tasks:
|
||||
env:
|
||||
SERVER_PORT: '{{.PORT}}'
|
||||
cmds:
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
|
||||
platforms: [windows]
|
||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}./gradlew :stirling-pdf:bootRun'
|
||||
platforms: [linux, darwin]
|
||||
- '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}{{.GRADLE}} :stirling-pdf:bootRun'
|
||||
|
||||
dev:bundled:
|
||||
desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)"
|
||||
desc: "Start the backend with the frontend bundled into the JAR"
|
||||
ignore_error: true
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat clean bootRun -PbuildWithFrontend=true"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
|
||||
platforms: [linux, darwin]
|
||||
- '{{.CLEAN}} bootRun -PbuildWithFrontend=true'
|
||||
|
||||
dev:saas:
|
||||
desc: "Start backend in SaaS flavor against Supabase"
|
||||
desc: "Start the backend in SaaS flavor"
|
||||
# `dotenv:` reads from the root Taskfile's directory (".") because this
|
||||
# subtaskfile is included with `dir: .`.
|
||||
dotenv: ['app/.env.saas.local', 'app/.env.saas']
|
||||
@@ -77,71 +78,104 @@ tasks:
|
||||
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
|
||||
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILES}}--args=\"--spring.profiles.include={{.PROFILES}}\"{{end}}"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILES}}--args='--spring.profiles.include={{.PROFILES}}'{{end}}
|
||||
platforms: [linux, darwin]
|
||||
- '{{.GRADLE}} :stirling-pdf:bootRun {{if .PROFILES}}--args=\"--spring.profiles.include={{.PROFILES}}\"{{end}}'
|
||||
|
||||
build:
|
||||
desc: "Full backend build"
|
||||
desc: "Build the backend"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat clean build"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew clean build
|
||||
platforms: [linux, darwin]
|
||||
- '{{.CLEAN}} build'
|
||||
|
||||
build:fast:
|
||||
desc: "Build without tests"
|
||||
desc: "Build the backend without running tests"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat clean build -x test"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew clean build -x test
|
||||
platforms: [linux, darwin]
|
||||
- '{{.CLEAN}} build -x test'
|
||||
|
||||
build:ci:
|
||||
desc: "Build for CI (formatting checked separately)"
|
||||
desc: "Build the backend for CI"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat build -PnoSpotless"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew build -PnoSpotless
|
||||
platforms: [linux, darwin]
|
||||
- '{{.GRADLE}} build -PnoSpotless'
|
||||
|
||||
prematrix:*:
|
||||
desc: "Run the backend test matrix"
|
||||
vars:
|
||||
MATRIXNAME: '{{index .MATCH 0}}'
|
||||
cmds:
|
||||
- for:
|
||||
matrix:
|
||||
FLAVOR: ["proprietary", "core", "saas"]
|
||||
LOGIN: ["true", "false"]
|
||||
SEC: ["true", "false"]
|
||||
task: '{{.MATRIXNAME}}:matrix'
|
||||
vars:
|
||||
STIRLING_FLAVOR: '{{.ITEM.FLAVOR}}'
|
||||
SECURITY_ENABLELOGIN: '{{.ITEM.LOGIN}}'
|
||||
DOCKER_ENABLE_SECURITY: '{{.ITEM.SEC}}'
|
||||
|
||||
test:
|
||||
desc: "Run backend tests"
|
||||
desc: "Run the backend test matrix"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat test"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew test
|
||||
platforms: [linux, darwin]
|
||||
- task: prematrix:test
|
||||
|
||||
test:matrix:
|
||||
internal: true
|
||||
env:
|
||||
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN}}'
|
||||
STIRLING_FLAVOR: '{{.STIRLING_FLAVOR}}'
|
||||
DOCKER_ENABLE_SECURITY: '{{.DOCKER_ENABLE_SECURITY}}'
|
||||
desc: "Run backend tests for one configuration"
|
||||
# Cover the backend with the main build/property combinations that change
|
||||
# Gradle behavior in this repo.
|
||||
cmds:
|
||||
- '{{.TEST}}'
|
||||
- '{{.TEST}} -PbuildWithFrontend=true'
|
||||
- '{{.TEST}} -PprototypesMode=true'
|
||||
- '{{.TEST}} -PbuildWithFrontend=true -PprototypesMode=true'
|
||||
|
||||
format:
|
||||
desc: "Auto-fix code formatting"
|
||||
desc: "Run the backend formatting matrix"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat spotlessApply"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew spotlessApply
|
||||
platforms: [linux, darwin]
|
||||
- task: prematrix:format
|
||||
|
||||
format:matrix:
|
||||
internal: true
|
||||
desc: "Apply backend formatting for one configuration"
|
||||
env:
|
||||
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN}}'
|
||||
STIRLING_FLAVOR: '{{.STIRLING_FLAVOR}}'
|
||||
DOCKER_ENABLE_SECURITY: '{{.DOCKER_ENABLE_SECURITY}}'
|
||||
cmds:
|
||||
- '{{.FORMAT}}'
|
||||
- '{{.FORMAT}} -PbuildWithFrontend=true'
|
||||
- '{{.FORMAT}} -PprototypesMode=true'
|
||||
|
||||
format:check:
|
||||
desc: "Check code formatting"
|
||||
desc: "Check the backend formatting matrix"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat spotlessCheck"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew spotlessCheck
|
||||
platforms: [linux, darwin]
|
||||
- task: prematrix:format:check
|
||||
|
||||
format:check:matrix:
|
||||
internal: true
|
||||
desc: "Check backend formatting for one configuration"
|
||||
env:
|
||||
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN}}'
|
||||
STIRLING_FLAVOR: '{{.STIRLING_FLAVOR}}'
|
||||
DOCKER_ENABLE_SECURITY: '{{.DOCKER_ENABLE_SECURITY}}'
|
||||
# Mirror the test matrix so formatting checks use the same backend
|
||||
# configuration variants.
|
||||
cmds:
|
||||
- '{{.FORMATCHECK}}'
|
||||
- '{{.FORMATCHECK}} -PbuildWithFrontend=true'
|
||||
- '{{.FORMATCHECK}} -PprototypesMode=true'
|
||||
|
||||
fix:
|
||||
desc: "Auto-fix backend"
|
||||
desc: "Apply backend fixes"
|
||||
cmds:
|
||||
- task: format
|
||||
|
||||
swagger:
|
||||
desc: "Generate OpenAPI docs"
|
||||
desc: "Generate the backend OpenAPI docs"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:copySwaggerDoc"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew :stirling-pdf:copySwaggerDoc
|
||||
platforms: [linux, darwin]
|
||||
- '{{.GRADLE}} :stirling-pdf:copySwaggerDoc'
|
||||
sources:
|
||||
- app/core/src/main/java/**/*.java
|
||||
- app/proprietary/src/main/java/**/*.java
|
||||
@@ -150,40 +184,43 @@ tasks:
|
||||
- SwaggerDoc.json
|
||||
|
||||
check:
|
||||
desc: "Backend quality gate"
|
||||
desc: "Run the backend quality gate"
|
||||
cmds:
|
||||
- task: format:check
|
||||
- task: test
|
||||
|
||||
version:
|
||||
desc: "Print project version"
|
||||
desc: "Print the backend version"
|
||||
silent: true
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat printVersion --quiet" | tail -1
|
||||
- cmd: pwsh -NoProfile -File scripts/backend-version.ps1
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew printVersion --quiet | tail -1
|
||||
platforms: [linux, darwin]
|
||||
|
||||
licenses:check:
|
||||
desc: "Check dependency licenses"
|
||||
desc: "Check backend dependency licenses"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat checkLicense --no-parallel"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew checkLicense --no-parallel
|
||||
platforms: [linux, darwin]
|
||||
- '{{.GRADLE}} checkLicense --no-parallel'
|
||||
|
||||
licenses:generate:
|
||||
desc: "Check and generate dependency license report"
|
||||
desc: "Generate the backend dependency license report"
|
||||
env:
|
||||
# Use the SaaS flavor so the license report includes all dependencies.
|
||||
STIRLING_FLAVOR: 'saas'
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat checkLicense generateLicenseReport --no-parallel"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew checkLicense generateLicenseReport --no-parallel
|
||||
- '{{.GRADLE}} checkLicense generateLicenseReport --no-parallel'
|
||||
|
||||
licenses:generate:copy:
|
||||
desc: "Generate and copy the backend dependency license report"
|
||||
deps: [licenses:generate]
|
||||
cmds:
|
||||
- cmd: cp build/reports/dependency-license/index.json app/core/src/main/resources/static/3rdPartyLicenses.json
|
||||
platforms: [linux, darwin]
|
||||
- cmd: powershell -NoProfile -Command "New-Item -ItemType Directory -Force -Path 'app/core/src/main/resources/static' | Out-Null; Copy-Item -Force 'build/reports/dependency-license/index.json' 'app/core/src/main/resources/static/3rdPartyLicenses.json'"
|
||||
platforms: [windows]
|
||||
|
||||
clean:
|
||||
desc: "Clean build artifacts"
|
||||
desc: "Clean backend build artifacts"
|
||||
cmds:
|
||||
- cmd: cmd /c ".\gradlew.bat clean"
|
||||
platforms: [windows]
|
||||
- cmd: ./gradlew clean
|
||||
platforms: [linux, darwin]
|
||||
- '{{.CLEAN}}'
|
||||
|
||||
+11
-11
@@ -6,7 +6,7 @@ spotless {
|
||||
java {
|
||||
target 'src/**/java/**/*.java'
|
||||
targetExclude 'src/main/java/org/apache/**'
|
||||
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
googleJavaFormat(rootProject.ext.googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
// google-java-format 1.28.0 bundles Guava 32.x which crashes Spotless lint on JDK 24/25
|
||||
suppressLintsFor { setStep('google-java-format') }
|
||||
|
||||
@@ -29,19 +29,19 @@ spotless {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
api "com.google.guava:guava:${guavaVersion}"
|
||||
api "com.google.guava:guava:${rootProject.ext.guavaVersion}"
|
||||
api 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||
api 'org.springframework.boot:spring-boot-starter-aspectj'
|
||||
api 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20260313.1'
|
||||
api 'com.fathzer:javaluator:3.0.6'
|
||||
api 'com.posthog.java:posthog:1.2.0'
|
||||
api "org.apache.commons:commons-lang3:${commonsLang3}"
|
||||
api "org.apache.commons:commons-lang3:${rootProject.ext.commonsLang3}"
|
||||
api 'com.drewnoakes:metadata-extractor:2.20.0' // Image metadata extractor
|
||||
api 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8'
|
||||
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
|
||||
api "org.apache.pdfbox:pdfbox-io:$pdfboxVersion"
|
||||
api "org.apache.pdfbox:xmpbox:$pdfboxVersion"
|
||||
api "org.apache.pdfbox:preflight:$pdfboxVersion"
|
||||
api "org.apache.pdfbox:pdfbox:${rootProject.ext.pdfboxVersion}"
|
||||
api "org.apache.pdfbox:pdfbox-io:${rootProject.ext.pdfboxVersion}"
|
||||
api "org.apache.pdfbox:xmpbox:${rootProject.ext.pdfboxVersion}"
|
||||
api "org.apache.pdfbox:preflight:${rootProject.ext.pdfboxVersion}"
|
||||
api 'com.github.junrar:junrar:7.5.10' // RAR archive support for CBR files
|
||||
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||
api 'org.snakeyaml:snakeyaml-engine:3.0.1'
|
||||
@@ -60,7 +60,7 @@ dependencies {
|
||||
exclude group: 'com.google.code.gson', module: 'gson'
|
||||
}
|
||||
|
||||
api "com.stirling:jpdfium:${jpdfiumVersion}"
|
||||
api "com.stirling:jpdfium:${rootProject.ext.jpdfiumVersion}"
|
||||
|
||||
// -PjpdfiumPlatforms=all|<csv of linux-x64,linux-arm64,darwin-x64,darwin-arm64,windows-x64>
|
||||
def jpdfiumPlatformsProp = (project.findProperty('jpdfiumPlatforms') ?: 'all').toString().trim()
|
||||
@@ -75,12 +75,12 @@ dependencies {
|
||||
}
|
||||
logger.lifecycle("JPDFium native platforms: ${jpdfiumPlatforms.join(', ')}")
|
||||
jpdfiumPlatforms.each { platform ->
|
||||
runtimeOnly "com.stirling:jpdfium-natives-${platform}:${jpdfiumVersion}"
|
||||
runtimeOnly "com.stirling:jpdfium-natives-${platform}:${rootProject.ext.jpdfiumVersion}"
|
||||
}
|
||||
|
||||
// Bucket4j (local in-process token bucket for RateLimitStore default impl)
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:${bucket4jVersion}"
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:${rootProject.ext.bucket4jVersion}"
|
||||
|
||||
// ArchUnit: enforces module dependency direction (see ArchitectureTest)
|
||||
testImplementation "com.tngtech.archunit:archunit-junit5:${archunitVersion}"
|
||||
testImplementation "com.tngtech.archunit:archunit-junit5:${rootProject.ext.archunitVersion}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# Auto-generated by MSW (`msw init`); regenerated verbatim, not hand-formatted.
|
||||
src/main/resources/static/mockServiceWorker.js
|
||||
+23
-20
@@ -13,7 +13,7 @@ spotless {
|
||||
java {
|
||||
target 'src/**/java/**/*.java'
|
||||
targetExclude 'src/main/resources/static/**', 'src/main/java/org/apache/**'
|
||||
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
googleJavaFormat(rootProject.ext.googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
// google-java-format 1.28.0 bundles Guava 32.x which crashes Spotless lint on JDK 24/25
|
||||
suppressLintsFor { setStep('google-java-format') }
|
||||
|
||||
@@ -67,21 +67,21 @@ dependencies {
|
||||
exclude group: 'com.fasterxml.jackson.jaxrs'
|
||||
exclude group: 'com.fasterxml.jackson.module', module: 'jackson-module-jaxb-annotations'
|
||||
}
|
||||
implementation "commons-io:commons-io:$commonsIoVersion"
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation "commons-io:commons-io:${rootProject.ext.commonsIoVersion}"
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:${rootProject.ext.bouncycastleVersion}"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:${rootProject.ext.bouncycastleVersion}"
|
||||
implementation 'io.micrometer:micrometer-core'
|
||||
implementation 'com.google.zxing:core:3.5.4'
|
||||
implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion"
|
||||
implementation "org.commonmark:commonmark:${rootProject.ext.commonmarkVersion}" // https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation "org.commonmark:commonmark-ext-gfm-tables:${rootProject.ext.commonmarkVersion}"
|
||||
|
||||
// General PDF dependencies
|
||||
implementation "org.apache.pdfbox:preflight:$pdfboxVersion"
|
||||
implementation "org.apache.pdfbox:xmpbox:$pdfboxVersion"
|
||||
implementation "org.apache.pdfbox:preflight:${rootProject.ext.pdfboxVersion}"
|
||||
implementation "org.apache.pdfbox:xmpbox:${rootProject.ext.pdfboxVersion}"
|
||||
|
||||
implementation 'org.verapdf:validation-model:1.28.2'
|
||||
// CVE-2025-66453: Explicit rhino 1.7.15 to override verapdf's 1.7.13
|
||||
implementation "org.mozilla:rhino:${rhinoVersion}"
|
||||
implementation "org.mozilla:rhino:${rootProject.ext.rhinoVersion}"
|
||||
|
||||
// veraPDF still uses javax.xml.bind, not the new jakarta namespace
|
||||
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||
@@ -89,33 +89,33 @@ dependencies {
|
||||
implementation 'com.sun.xml.bind:jaxb-core:4.0.7'
|
||||
|
||||
// CVE-2022-25647: Explicit gson to prevent unsafe deserialization (tabula would pull 2.8.7)
|
||||
implementation "com.google.code.gson:gson:${gsonVersion}"
|
||||
implementation "com.google.code.gson:gson:${rootProject.ext.gsonVersion}"
|
||||
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
|
||||
implementation 'com.opencsv:opencsv:5.12.0' // https://mvnrepository.com/artifact/com.opencsv/opencsv
|
||||
implementation 'org.apache.poi:poi-ooxml:5.5.1'
|
||||
|
||||
// Batik only bridge module needed (transitively pulls anim, gvt, util, css, dom, svg-dom)
|
||||
// Replaces batik-all which included unused codec, svggen, transcoder, script modules
|
||||
implementation "org.apache.xmlgraphics:batik-bridge:${batikVersion}"
|
||||
implementation "org.apache.xmlgraphics:batik-bridge:${rootProject.ext.batikVersion}"
|
||||
// Required by TwelveMonkeys imageio-batik SPI (SVGImageReaderSpi) during ImageIO init
|
||||
runtimeOnly "org.apache.xmlgraphics:batik-transcoder:${batikVersion}"
|
||||
runtimeOnly "org.apache.xmlgraphics:batik-transcoder:${rootProject.ext.batikVersion}"
|
||||
|
||||
// PDFBox Graphics2D bridge for Batik SVG to PDF conversion
|
||||
implementation 'de.rototor.pdfbox:graphics2d:3.0.5'
|
||||
|
||||
// TwelveMonkeys
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-bmp:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-jpeg:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-tiff:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:${rootProject.ext.imageioVersion}"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-bmp:${rootProject.ext.imageioVersion}"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-jpeg:${rootProject.ext.imageioVersion}"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-tiff:${rootProject.ext.imageioVersion}"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:${rootProject.ext.imageioVersion}"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-hdr:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-icns:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-iff:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
|
||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-psd:${rootProject.ext.imageioVersion}"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
|
||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
|
||||
@@ -135,7 +135,6 @@ sourceSets {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Disable regular jar
|
||||
jar {
|
||||
enabled = false
|
||||
@@ -292,7 +291,11 @@ tasks.register('npmBuild', Exec) {
|
||||
group = 'frontend'
|
||||
description = 'Build editor frontend application'
|
||||
workingDir file('../..')
|
||||
commandLine = ['task', frontendBuildTask]
|
||||
// Pin the repo-root Taskfile explicitly so the Exec task does not inherit
|
||||
// an unrelated TASKFILE override from the surrounding environment.
|
||||
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ?
|
||||
['cmd', '/c', 'task', '--taskfile', 'Taskfile.yml', frontendBuildTask] :
|
||||
['task', '--taskfile', 'Taskfile.yml', frontendBuildTask]
|
||||
inputs.dir(new File(frontendEditorDir, 'src'))
|
||||
inputs.dir(new File(frontendEditorDir, 'public'))
|
||||
inputs.file(new File(frontendDir, 'package.json'))
|
||||
|
||||
@@ -20,17 +20,18 @@
|
||||
|
||||
--cc-separator-border-color: #e0e0e0;
|
||||
|
||||
--cc-toggle-on-bg: #007bff;
|
||||
--cc-toggle-off-bg: #667481;
|
||||
--cc-toggle-on-knob-bg: #ffffff;
|
||||
--cc-toggle-off-knob-bg: #ffffff;
|
||||
/* Toggle colors mirror Mantine Switch (light scheme) */
|
||||
--cc-toggle-on-bg: var(--mantine-primary-color-filled, #007bff);
|
||||
--cc-toggle-off-bg: var(--mantine-color-gray-3, #dee2e6);
|
||||
--cc-toggle-on-knob-bg: var(--mantine-color-white, #ffffff);
|
||||
--cc-toggle-off-knob-bg: var(--mantine-color-white, #ffffff);
|
||||
|
||||
--cc-toggle-enabled-icon-color: #ffffff;
|
||||
--cc-toggle-disabled-icon-color: #ffffff;
|
||||
|
||||
--cc-toggle-readonly-bg: #f1f3f4;
|
||||
--cc-toggle-readonly-knob-bg: #79747e;
|
||||
--cc-toggle-readonly-knob-icon-color: #f1f3f4;
|
||||
--cc-toggle-readonly-bg: var(--mantine-color-disabled, #f1f3f4);
|
||||
--cc-toggle-readonly-knob-bg: var(--mantine-color-gray-0, #f8f9fa);
|
||||
--cc-toggle-readonly-knob-icon-color: transparent;
|
||||
|
||||
--cc-section-category-border: #e0e0e0;
|
||||
|
||||
@@ -69,17 +70,18 @@
|
||||
|
||||
--cc-separator-border-color: #555555;
|
||||
|
||||
--cc-toggle-on-bg: #4dabf7;
|
||||
--cc-toggle-off-bg: #667481;
|
||||
--cc-toggle-on-knob-bg: #2d2d2d;
|
||||
--cc-toggle-off-knob-bg: #2d2d2d;
|
||||
/* Toggle colors mirror Mantine Switch (dark scheme) */
|
||||
--cc-toggle-on-bg: var(--mantine-primary-color-filled, #4dabf7);
|
||||
--cc-toggle-off-bg: var(--mantine-color-dark-5, #555555);
|
||||
--cc-toggle-on-knob-bg: var(--mantine-color-white, #ffffff);
|
||||
--cc-toggle-off-knob-bg: var(--mantine-color-white, #ffffff);
|
||||
|
||||
--cc-toggle-enabled-icon-color: #2d2d2d;
|
||||
--cc-toggle-disabled-icon-color: #2d2d2d;
|
||||
|
||||
--cc-toggle-readonly-bg: #555555;
|
||||
--cc-toggle-readonly-knob-bg: #8e8e8e;
|
||||
--cc-toggle-readonly-knob-icon-color: #555555;
|
||||
--cc-toggle-readonly-bg: var(--mantine-color-disabled, #555555);
|
||||
--cc-toggle-readonly-knob-bg: var(--mantine-color-dark-3, #8e8e8e);
|
||||
--cc-toggle-readonly-knob-icon-color: transparent;
|
||||
|
||||
--cc-section-category-border: #555555;
|
||||
|
||||
@@ -176,9 +178,16 @@
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
|
||||
/* Lower z-index so cookie banner appears behind onboarding modals */
|
||||
/* Banner sits above the chat FAB but behind all modals and onboarding; value
|
||||
is Z_INDEX_COOKIE_CONSENT_BANNER, set as this variable by useCookieConsent */
|
||||
#cc-main {
|
||||
z-index: 100 !important;
|
||||
z-index: var(--z-index-cookie-consent) !important;
|
||||
}
|
||||
|
||||
/* Preferences dialog sits above the settings modal it opens from; value is
|
||||
Z_INDEX_COOKIE_PREFERENCES_MODAL, set as this variable by useCookieConsent */
|
||||
.show--preferences #cc-main {
|
||||
z-index: var(--z-index-cookie-preferences) !important;
|
||||
}
|
||||
|
||||
/* Ensure consent modal text is visible in both themes */
|
||||
@@ -203,3 +212,63 @@
|
||||
#cc-main .cm__link {
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
|
||||
/* ── Category toggles restyled to match Mantine Switch (size sm) ──────────
|
||||
Mantine sm metrics: 38×20 track, 14px plain thumb, 2.5px inline padding,
|
||||
150ms ease transitions, no icon inside the thumb. Colors come from the
|
||||
--cc-toggle-* variables above, which point at the Mantine palette. */
|
||||
#cc-main .section__toggle,
|
||||
#cc-main .section__toggle-wrapper,
|
||||
#cc-main .toggle__icon,
|
||||
#cc-main .toggle__label {
|
||||
width: 38px !important;
|
||||
height: 20px !important;
|
||||
border-radius: 1000px !important;
|
||||
}
|
||||
|
||||
/* Track: flat fill, no outline ring or border */
|
||||
#cc-main .toggle__icon {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
transition: background-color 150ms ease !important;
|
||||
}
|
||||
|
||||
#cc-main .section__toggle:checked ~ .toggle__icon {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Always-enabled categories = Mantine disabled switch (must out-prioritise
|
||||
the !important checked-track rule above) */
|
||||
#cc-main .section__toggle:checked:disabled ~ .toggle__icon {
|
||||
background: var(--cc-toggle-readonly-bg) !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#cc-main .section__toggle:disabled {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* Thumb: small plain circle, vertically centred, no drop shadow */
|
||||
#cc-main .toggle__icon-circle {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
top: 3px !important;
|
||||
left: 2.5px !important;
|
||||
box-shadow: none !important;
|
||||
transition:
|
||||
transform 150ms ease,
|
||||
background-color 150ms ease !important;
|
||||
}
|
||||
|
||||
/* Checked thumb travel: 38 − 14 − 2.5 = 21.5px end position */
|
||||
#cc-main .section__toggle:checked ~ .toggle__icon .toggle__icon-circle {
|
||||
transform: translateX(19px) !important;
|
||||
}
|
||||
|
||||
/* Mantine switches have no check/cross glyph inside the thumb */
|
||||
#cc-main .toggle__icon-on,
|
||||
#cc-main .toggle__icon-off {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker.
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
*/
|
||||
|
||||
const PACKAGE_VERSION = '2.14.6'
|
||||
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
|
||||
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||
const activeClientIds = new Set()
|
||||
|
||||
addEventListener('install', function () {
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
addEventListener('activate', function (event) {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
addEventListener('message', async function (event) {
|
||||
const clientId = Reflect.get(event.source || {}, 'id')
|
||||
|
||||
if (!clientId || !self.clients) {
|
||||
return
|
||||
}
|
||||
|
||||
const client = await self.clients.get(clientId)
|
||||
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
})
|
||||
|
||||
switch (event.data) {
|
||||
case 'KEEPALIVE_REQUEST': {
|
||||
sendToClient(client, {
|
||||
type: 'KEEPALIVE_RESPONSE',
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'INTEGRITY_CHECK_REQUEST': {
|
||||
sendToClient(client, {
|
||||
type: 'INTEGRITY_CHECK_RESPONSE',
|
||||
payload: {
|
||||
packageVersion: PACKAGE_VERSION,
|
||||
checksum: INTEGRITY_CHECKSUM,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'MOCK_ACTIVATE': {
|
||||
activeClientIds.add(clientId)
|
||||
|
||||
sendToClient(client, {
|
||||
type: 'MOCKING_ENABLED',
|
||||
payload: {
|
||||
client: {
|
||||
id: client.id,
|
||||
frameType: client.frameType,
|
||||
},
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case 'CLIENT_CLOSED': {
|
||||
activeClientIds.delete(clientId)
|
||||
|
||||
const remainingClients = allClients.filter((client) => {
|
||||
return client.id !== clientId
|
||||
})
|
||||
|
||||
// Unregister itself when there are no more clients
|
||||
if (remainingClients.length === 0) {
|
||||
self.registration.unregister()
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
addEventListener('fetch', function (event) {
|
||||
const requestInterceptedAt = Date.now()
|
||||
|
||||
// Bypass navigation requests.
|
||||
if (event.request.mode === 'navigate') {
|
||||
return
|
||||
}
|
||||
|
||||
// Opening the DevTools triggers the "only-if-cached" request
|
||||
// that cannot be handled by the worker. Bypass such requests.
|
||||
if (
|
||||
event.request.cache === 'only-if-cached' &&
|
||||
event.request.mode !== 'same-origin'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Bypass all requests when there are no active clients.
|
||||
// Prevents the self-unregistered worked from handling requests
|
||||
// after it's been terminated (still remains active until the next reload).
|
||||
if (activeClientIds.size === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const requestId = crypto.randomUUID()
|
||||
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {FetchEvent} event
|
||||
* @param {string} requestId
|
||||
* @param {number} requestInterceptedAt
|
||||
*/
|
||||
async function handleRequest(event, requestId, requestInterceptedAt) {
|
||||
const client = await resolveMainClient(event)
|
||||
const requestCloneForEvents = event.request.clone()
|
||||
const response = await getResponse(
|
||||
event,
|
||||
client,
|
||||
requestId,
|
||||
requestInterceptedAt,
|
||||
)
|
||||
|
||||
// Send back the response clone for the "response:*" life-cycle events.
|
||||
// Ensure MSW is active and ready to handle the message, otherwise
|
||||
// this message will pend indefinitely.
|
||||
if (client && activeClientIds.has(client.id)) {
|
||||
const serializedRequest = await serializeRequest(requestCloneForEvents)
|
||||
|
||||
// Clone the response so both the client and the library could consume it.
|
||||
const responseClone = response.clone()
|
||||
|
||||
sendToClient(
|
||||
client,
|
||||
{
|
||||
type: 'RESPONSE',
|
||||
payload: {
|
||||
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||
request: {
|
||||
id: requestId,
|
||||
...serializedRequest,
|
||||
},
|
||||
response: {
|
||||
type: responseClone.type,
|
||||
status: responseClone.status,
|
||||
statusText: responseClone.statusText,
|
||||
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||
body: responseClone.body,
|
||||
},
|
||||
},
|
||||
},
|
||||
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the main client for the given event.
|
||||
* Client that issues a request doesn't necessarily equal the client
|
||||
* that registered the worker. It's with the latter the worker should
|
||||
* communicate with during the response resolving phase.
|
||||
* @param {FetchEvent} event
|
||||
* @returns {Promise<Client | undefined>}
|
||||
*/
|
||||
async function resolveMainClient(event) {
|
||||
const client = await self.clients.get(event.clientId)
|
||||
|
||||
if (activeClientIds.has(event.clientId)) {
|
||||
return client
|
||||
}
|
||||
|
||||
if (client?.frameType === 'top-level') {
|
||||
return client
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
})
|
||||
|
||||
return allClients
|
||||
.filter((client) => {
|
||||
// Get only those clients that are currently visible.
|
||||
return client.visibilityState === 'visible'
|
||||
})
|
||||
.find((client) => {
|
||||
// Find the client ID that's recorded in the
|
||||
// set of clients that have registered the worker.
|
||||
return activeClientIds.has(client.id)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FetchEvent} event
|
||||
* @param {Client | undefined} client
|
||||
* @param {string} requestId
|
||||
* @param {number} requestInterceptedAt
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
async function getResponse(event, client, requestId, requestInterceptedAt) {
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const requestClone = event.request.clone()
|
||||
|
||||
function passthrough() {
|
||||
// Cast the request headers to a new Headers instance
|
||||
// so the headers can be manipulated with.
|
||||
const headers = new Headers(requestClone.headers)
|
||||
|
||||
// Remove the "accept" header value that marked this request as passthrough.
|
||||
// This prevents request alteration and also keeps it compliant with the
|
||||
// user-defined CORS policies.
|
||||
const acceptHeader = headers.get('accept')
|
||||
if (acceptHeader) {
|
||||
const values = acceptHeader.split(',').map((value) => value.trim())
|
||||
const filteredValues = values.filter(
|
||||
(value) => value !== 'msw/passthrough',
|
||||
)
|
||||
|
||||
if (filteredValues.length > 0) {
|
||||
headers.set('accept', filteredValues.join(', '))
|
||||
} else {
|
||||
headers.delete('accept')
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(requestClone, { headers })
|
||||
}
|
||||
|
||||
// Bypass mocking when the client is not active.
|
||||
if (!client) {
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
// Bypass initial page load requests (i.e. static assets).
|
||||
// The absence of the immediate/parent client in the map of the active clients
|
||||
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||
// and is not ready to handle requests.
|
||||
if (!activeClientIds.has(client.id)) {
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
// Notify the client that a request has been intercepted.
|
||||
const serializedRequest = await serializeRequest(event.request)
|
||||
const clientMessage = await sendToClient(
|
||||
client,
|
||||
{
|
||||
type: 'REQUEST',
|
||||
payload: {
|
||||
id: requestId,
|
||||
interceptedAt: requestInterceptedAt,
|
||||
...serializedRequest,
|
||||
},
|
||||
},
|
||||
[serializedRequest.body],
|
||||
)
|
||||
|
||||
switch (clientMessage.type) {
|
||||
case 'MOCK_RESPONSE': {
|
||||
return respondWithMock(clientMessage.data)
|
||||
}
|
||||
|
||||
case 'PASSTHROUGH': {
|
||||
return passthrough()
|
||||
}
|
||||
}
|
||||
|
||||
return passthrough()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {any} message
|
||||
* @param {Array<Transferable>} transferrables
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
function sendToClient(client, message, transferrables = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel()
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
if (event.data && event.data.error) {
|
||||
return reject(event.data.error)
|
||||
}
|
||||
|
||||
resolve(event.data)
|
||||
}
|
||||
|
||||
client.postMessage(message, [
|
||||
channel.port2,
|
||||
...transferrables.filter(Boolean),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
* @returns {Response}
|
||||
*/
|
||||
function respondWithMock(response) {
|
||||
// Setting response status code to 0 is a no-op.
|
||||
// However, when responding with a "Response.error()", the produced Response
|
||||
// instance will have status code set to 0. Since it's not possible to create
|
||||
// a Response instance with status code 0, handle that use-case separately.
|
||||
if (response.status === 0) {
|
||||
return Response.error()
|
||||
}
|
||||
|
||||
const mockedResponse = new Response(response.body, response)
|
||||
|
||||
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||
value: true,
|
||||
enumerable: true,
|
||||
})
|
||||
|
||||
return mockedResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Request} request
|
||||
*/
|
||||
async function serializeRequest(request) {
|
||||
return {
|
||||
url: request.url,
|
||||
mode: request.mode,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: await request.arrayBuffer(),
|
||||
keepalive: request.keepalive,
|
||||
}
|
||||
}
|
||||
@@ -147,11 +147,17 @@ class SvgToPdfTest {
|
||||
ImageIO.write(red, "png", external.toFile());
|
||||
|
||||
try {
|
||||
String rootRelativeExternal =
|
||||
"/"
|
||||
+ external.toAbsolutePath()
|
||||
.toString()
|
||||
.replace('\\', '/')
|
||||
.replaceFirst("^/+", "");
|
||||
String svg =
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" "
|
||||
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"100\" height=\"100\">"
|
||||
+ "<image x=\"0\" y=\"0\" width=\"100\" height=\"100\" xlink:href=\""
|
||||
+ external.toUri()
|
||||
+ rootRelativeExternal
|
||||
+ "\"/></svg>";
|
||||
|
||||
byte[] pdf;
|
||||
|
||||
@@ -11,7 +11,7 @@ spotless {
|
||||
java {
|
||||
target 'src/**/java/**/*.java'
|
||||
targetExclude 'src/main/java/org/apache/**'
|
||||
googleJavaFormat(googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
googleJavaFormat(rootProject.ext.googleJavaFormatVersion).aosp().reorderImports(false)
|
||||
// google-java-format 1.28.0 bundles Guava 32.x which crashes Spotless lint on JDK 24/25
|
||||
suppressLintsFor { setStep('google-java-format') }
|
||||
|
||||
@@ -35,13 +35,13 @@ spotless {
|
||||
}
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
api "com.google.guava:guava:${guavaVersion}"
|
||||
api "com.google.guava:guava:${rootProject.ext.guavaVersion}"
|
||||
|
||||
api 'org.springframework:spring-jdbc'
|
||||
api 'org.springframework:spring-webmvc'
|
||||
api 'org.springframework.session:spring-session-core'
|
||||
api "org.springframework.security:spring-security-core:$springSecuritySamlVersion"
|
||||
api "org.springframework.security:spring-security-saml2-service-provider:$springSecuritySamlVersion"
|
||||
api "org.springframework.security:spring-security-core:${rootProject.ext.springSecuritySamlVersion}"
|
||||
api "org.springframework.security:spring-security-saml2-service-provider:${rootProject.ext.springSecuritySamlVersion}"
|
||||
api 'org.springframework.boot:spring-boot-starter-jetty'
|
||||
api 'org.springframework.boot:spring-boot-starter-security'
|
||||
api 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
@@ -55,16 +55,16 @@ dependencies {
|
||||
api 'com.github.ben-manes.caffeine:caffeine'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.46'
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:${bucket4jVersion}"
|
||||
implementation "com.bucket4j:bucket4j_jdk17-core:${rootProject.ext.bucket4jVersion}"
|
||||
// Lettuce-backed Bucket4j ProxyManager used by ValkeyRateLimitStore for cluster-wide
|
||||
// token-bucket rate limiting (parity with in-process Bucket4j semantics; no fixed-window
|
||||
// boundary doubling).
|
||||
implementation "com.bucket4j:bucket4j_jdk17-lettuce:${bucket4jVersion}"
|
||||
implementation "com.bucket4j:bucket4j_jdk17-lettuce:${rootProject.ext.bucket4jVersion}"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:${rootProject.ext.bouncycastleVersion}"
|
||||
|
||||
implementation "com.google.code.gson:gson:${gsonVersion}"
|
||||
implementation "com.google.code.gson:gson:${rootProject.ext.gsonVersion}"
|
||||
|
||||
// jinjava/jjwt transitively request older Jackson 2 versions; declare the current
|
||||
// version directly so it is selected consistently (root build.gradle pins are the fallback).
|
||||
@@ -82,17 +82,17 @@ dependencies {
|
||||
|
||||
api 'io.micrometer:micrometer-registry-prometheus'
|
||||
|
||||
api "io.jsonwebtoken:jjwt-api:${jwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:${jwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${jwtVersion}"
|
||||
api "io.jsonwebtoken:jjwt-api:${rootProject.ext.jwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-impl:${rootProject.ext.jwtVersion}"
|
||||
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${rootProject.ext.jwtVersion}"
|
||||
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database - file format incompatible with 2.4.x, would break existing user databases
|
||||
runtimeOnly 'org.postgresql:postgresql:42.7.11'
|
||||
implementation('com.coveo:saml-client:5.0.0') {
|
||||
exclude group: 'org.opensaml', module: 'opensaml-core'
|
||||
}
|
||||
|
||||
implementation "software.amazon.awssdk:s3:${awsSdkVersion}"
|
||||
implementation "software.amazon.awssdk:url-connection-client:${awsSdkVersion}"
|
||||
implementation "software.amazon.awssdk:s3:${rootProject.ext.awsSdkVersion}"
|
||||
implementation "software.amazon.awssdk:url-connection-client:${rootProject.ext.awsSdkVersion}"
|
||||
|
||||
// @DataJpaTest slice (Boot 4 ships test slices as separate starters, like webmvc-test at the
|
||||
// root) so policy.source repositories can be exercised against embedded H2.
|
||||
@@ -100,11 +100,10 @@ dependencies {
|
||||
|
||||
// Testcontainers: real MinIO/LocalStack (S3) and Valkey for integration tests in CI without
|
||||
// manually-started instances. Tests skip cleanly when Docker is unavailable.
|
||||
testImplementation "org.testcontainers:testcontainers:${testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:minio:${testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:localstack:${testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:postgresql:${testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:junit-jupiter:${testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:testcontainers:${rootProject.ext.testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:minio:${rootProject.ext.testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:localstack:${rootProject.ext.testcontainersMinioVersion}"
|
||||
testImplementation "org.testcontainers:junit-jupiter:${rootProject.ext.testcontainersMinioVersion}"
|
||||
}
|
||||
|
||||
tasks.register('prepareKotlinBuildScriptModel') {}
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import stirling.software.common.model.ApplicationProperties;
|
||||
* the full production bean method {@code valkeyConnectionFactory()} so the parse, credential
|
||||
* wiring, and eager-handshake all run exactly as at boot.
|
||||
*/
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@EnabledIf("isDockerAvailable")
|
||||
class LiveValkeyAuthIntegrationTest {
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import stirling.software.common.model.ApplicationProperties;
|
||||
* to reproduce a partition rather than {@code stop} (which would fail fast with
|
||||
* connection-refused).
|
||||
*/
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@EnabledIf("isDockerAvailable")
|
||||
class LiveValkeyChaosTest {
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ import stirling.software.common.model.ApplicationProperties;
|
||||
* unavailable - without that guard, {@code @Testcontainers} would throw {@code initializationError}
|
||||
* (test FAILURE, not skip) on CI runners without Docker.
|
||||
*/
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
@EnabledIf("isDockerAvailable")
|
||||
class LiveValkeyIntegrationTest {
|
||||
|
||||
|
||||
+6
-3
@@ -235,6 +235,7 @@ subprojects {
|
||||
resolutionStrategy.force "org.bouncycastle:bcprov-jdk18on:${bouncycastleVersion}"
|
||||
resolutionStrategy.force "org.bouncycastle:bcpkix-jdk18on:${bouncycastleVersion}"
|
||||
resolutionStrategy.force "org.bouncycastle:bcutil-jdk18on:${bouncycastleVersion}"
|
||||
resolutionStrategy.force "org.apache.santuario:xmlsec:3.0.5"
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
@@ -285,6 +286,7 @@ subprojects {
|
||||
def jacocoReport = tasks.named("jacocoTestReport")
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
jvmArgs "--enable-native-access=ALL-UNNAMED"
|
||||
useJUnitPlatform()
|
||||
finalizedBy(jacocoReport)
|
||||
}
|
||||
@@ -297,7 +299,8 @@ subprojects {
|
||||
html.required.set(true)
|
||||
}
|
||||
doLast {
|
||||
def xmlReport = reports.xml.outputLocation.get().asFile
|
||||
def reportBaseDir = layout.buildDirectory.dir("reports/jacoco/test").get().asFile
|
||||
def xmlReport = new File(reportBaseDir, "jacocoTestReport.xml")
|
||||
if (!xmlReport.exists()) {
|
||||
logger.lifecycle("Jacoco coverage report not found at ${xmlReport}")
|
||||
return
|
||||
@@ -376,7 +379,7 @@ subprojects {
|
||||
}
|
||||
logger.lifecycle(separator)
|
||||
|
||||
def htmlReport = reports.html.outputLocation.get().asFile
|
||||
def htmlReport = new File(reportBaseDir, "html")
|
||||
logger.lifecycle("Detailed HTML report available at: ${htmlReport}")
|
||||
if (rows.any { it[3] == "FAIL" }) {
|
||||
logger.lifecycle("Some coverage targets were missed. Please review the detailed report above.")
|
||||
@@ -666,7 +669,7 @@ tasks.register('compileRestartHelper', JavaCompile) {
|
||||
source = fileTree(dir: 'scripts', include: 'RestartHelper.java')
|
||||
classpath = files()
|
||||
destinationDirectory = layout.buildDirectory.dir("restart-helper-classes")
|
||||
def restartMajorVersion = project.ext.modernJavaVersion
|
||||
def restartMajorVersion = rootProject.ext.modernJavaVersion
|
||||
def restartCompatibility = JavaVersion.toVersion(restartMajorVersion.toString())
|
||||
sourceCompatibility = restartCompatibility
|
||||
targetCompatibility = restartCompatibility
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$output = & "$PSScriptRoot/../gradlew.bat" printVersion --quiet 2>&1
|
||||
$lines = @($output | Where-Object { $_.ToString().Trim() })
|
||||
if ($lines.Count -eq 0) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Output $lines[-1]
|
||||
Reference in New Issue
Block a user