mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Add Playwright/bootRun/test.sh tasks (#6244)
## Description Consolidates Playwright running under cohesive Task namespaces, isolates Playwright state from the developer's local working tree, and swaps CI's frontend webserver from `vite` dev to `vite preview` against a pre-built `dist/`. ### `e2e:*` namespace Renames `.taskfiles/testing.yml` to `.taskfiles/e2e.yml` and consolidates everything Playwright-related under one `e2e:` namespace: - `e2e:stubbed` / `e2e:live` / `e2e:enterprise` / `e2e:cross-browser`: project-specific runners - `e2e:check` (no-Docker subset) and `e2e:check:all` (full) - `e2e:oauth:up` / `:down`, `e2e:saml:up` / `:down`: symmetric lifecycle for the keycloak compose stacks - `e2e:install`: Playwright browser install - `docker:test`: full Docker integration suite The redundant `frontend:test:e2e:*` project shortcuts are removed. CI workflows (`e2e-stubbed.yml`, `e2e-live.yml`, `build-enterprise.yml`, `nightly.yml`) are updated to call the new task names. ### Isolated Playwright state New `STIRLING_BASE_PATH` (and `-Dstirling.base-path=`) override in `InstallationPathConfig` redirects the entire state tree (configs, backups, customFiles, pipeline, logs) at startup. `task e2e:live` points it at `.test-state/playwright/` (purged on every invocation) so the suite never touches the developer's local DB, settings.yml or backups. `task e2e:live` auto-spawns gradle, waits for `/api/v1/info/status` to come up, runs Playwright, then tears down the whole backend process tree. ### CI runs Playwright against `vite preview` Builds the frontend up-front with `VITE_BUILD_FOR_PREVIEW=1` (forces absolute base so deep SPA routes resolve `/assets/...`) and the playwright `webServer` now uses `vite preview --port 5173 --strictPort` in CI. Avoids the per-page on-demand transform cost that was blowing the 30s navigation timeout under `--workers=3` on `all-tool-pages-load.spec.ts`. Local dev keeps `vite` dev for HMR. ### OAuth/SAML compose helpers `start-oauth-test.sh` and `start-saml-test.sh` gain a `--license-key <KEY>` (`-k`) flag so CI and scripted runs can skip the interactive license prompt. `start-oauth-test.sh` also moves from `for arg in "$@"` to a `while`-with-`shift` arg loop to support multi-arg flags consistently with the SAML script. ### Backend gradlew unification Drops the per-platform `cmd /c gradlew.bat` branches from `backend.yml` and routes every gradle invocation through `bash gradlew`. Works uniformly on Linux/macOS and Windows-with-Git-Bash. ### Compare.tsx flake fix (re-land of [#6316](https://github.com/Stirling-Tools/Stirling-PDF/pull/6316)) Piggybacks Anthony's never-merged fix from #6316. Without it, `e2e:stubbed` continues to flake under `--workers=3` on `compare.spec.ts`'s second-upload case via a React "Maximum update depth exceeded" infinite loop in the Compare auto-fill effect. CI traces from recent failed runs match exactly; 10 local runs of `compare.spec.ts` with `CI=1 --workers=3` pass cleanly with the fix applied. --------- Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
co-authored by
James Brunton
parent
c059e13423
commit
f60a075443
@@ -63,7 +63,15 @@ jobs:
|
|||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
||||||
- name: Install Playwright (chromium only)
|
- name: Install Playwright (chromium only)
|
||||||
run: task frontend:test:e2e:install -- chromium
|
run: task e2e:install -- chromium
|
||||||
|
- name: Build frontend (needed for playwright's vite preview webServer)
|
||||||
|
# Enterprise tests target :8080 (Spring Boot's bundled frontend), but
|
||||||
|
# playwright's webServer config still launches `vite preview --port 5173`
|
||||||
|
# before any test run, which needs dist/ to exist. VITE_BUILD_FOR_PREVIEW
|
||||||
|
# forces absolute asset paths so vite preview can serve deep SPA routes.
|
||||||
|
env:
|
||||||
|
VITE_BUILD_FOR_PREVIEW: "1"
|
||||||
|
run: task frontend:build
|
||||||
|
|
||||||
- name: Resolve kubernetes.docker.internal to localhost
|
- name: Resolve kubernetes.docker.internal to localhost
|
||||||
# The compose stacks set KC_HOSTNAME=kubernetes.docker.internal so
|
# The compose stacks set KC_HOSTNAME=kubernetes.docker.internal so
|
||||||
@@ -140,7 +148,7 @@ jobs:
|
|||||||
wait_for_backend
|
wait_for_backend
|
||||||
- name: Run enterprise OAuth Playwright tests
|
- name: Run enterprise OAuth Playwright tests
|
||||||
id: oauth-tests
|
id: oauth-tests
|
||||||
run: task frontend:test:e2e -- --project=enterprise --grep "OAuth"
|
run: task e2e:enterprise -- --grep "OAuth"
|
||||||
- name: Stop backend + tear down OAuth Keycloak
|
- name: Stop backend + tear down OAuth Keycloak
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
@@ -213,7 +221,7 @@ jobs:
|
|||||||
wait_for_backend
|
wait_for_backend
|
||||||
- name: Run enterprise SAML Playwright tests
|
- name: Run enterprise SAML Playwright tests
|
||||||
id: saml-tests
|
id: saml-tests
|
||||||
run: task frontend:test:e2e -- --project=enterprise --grep "SAML"
|
run: task e2e:enterprise -- --grep "SAML"
|
||||||
- name: Stop backend + tear down SAML Keycloak
|
- name: Stop backend + tear down SAML Keycloak
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
@@ -243,7 +251,7 @@ jobs:
|
|||||||
wait_for_backend
|
wait_for_backend
|
||||||
- name: Run enterprise feature Playwright tests
|
- name: Run enterprise feature Playwright tests
|
||||||
id: feature-tests
|
id: feature-tests
|
||||||
run: task frontend:test:e2e -- --project=enterprise --grep "Enterprise license"
|
run: task e2e:enterprise -- --grep "Enterprise license"
|
||||||
- name: Print backend log on failure
|
- name: Print backend log on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -34,58 +34,26 @@ jobs:
|
|||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
||||||
- name: Install Playwright (chromium only)
|
- name: Install Playwright (chromium only)
|
||||||
run: task frontend:test:e2e:install -- chromium
|
run: task e2e:install -- chromium
|
||||||
- name: Start Spring Boot backend (background)
|
- name: Build frontend (production bundle for vite preview)
|
||||||
env:
|
env:
|
||||||
# Suppress the analytics opt-in modal that fires on first admin login when
|
VITE_BUILD_FOR_PREVIEW: "1"
|
||||||
# enableAnalytics is null (see Onboarding.tsx). The modal renders a Mantine
|
run: task frontend:build
|
||||||
# overlay that intercepts pointer events on every tool page until dismissed,
|
|
||||||
# which causes every "click run button" assertion in the live suite to fail.
|
|
||||||
SYSTEM_ENABLEANALYTICS: "false"
|
|
||||||
# NOTE: SECURITY_INITIALLOGIN_USERNAME/PASSWORD are intentionally NOT set.
|
|
||||||
# The live-setup project's bootstrap spec performs the real first-login
|
|
||||||
# flow against the backend's default admin/stirling user, exercising the
|
|
||||||
# forced-password-change UI and leaving the DB at admin/adminadmin for
|
|
||||||
# the rest of the live suite. This is both real coverage of the first-
|
|
||||||
# login flow and a stronger seed than env-var-driven user creation.
|
|
||||||
run: |
|
|
||||||
nohup ./gradlew :stirling-pdf:bootRun > /tmp/backend.log 2>&1 &
|
|
||||||
echo $! > /tmp/backend.pid
|
|
||||||
- name: Wait for backend to become ready
|
|
||||||
run: |
|
|
||||||
start=$SECONDS
|
|
||||||
# 300 iterations × 2s = 10 minute ceiling
|
|
||||||
for i in $(seq 1 300); do
|
|
||||||
if curl -fsS http://localhost:8080/api/v1/info/status >/dev/null 2>&1; then
|
|
||||||
echo "Backend up after $((SECONDS - start))s"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "Backend did not become ready in $((SECONDS - start))s"
|
|
||||||
tail -200 /tmp/backend.log || true
|
|
||||||
exit 1
|
|
||||||
- name: Run live E2E tests (chromium)
|
- name: Run live E2E tests (chromium)
|
||||||
id: live-tests
|
id: live-tests
|
||||||
run: task frontend:test:e2e -- --project=live
|
run: task e2e:live
|
||||||
- name: Print backend log on failure
|
- name: Print backend log on failure
|
||||||
if: failure() && steps.live-tests.conclusion == 'failure'
|
if: failure() && steps.live-tests.conclusion == 'failure'
|
||||||
run: |
|
run: |
|
||||||
echo "::group::Spring Boot backend log (last 500 lines)"
|
echo "::group::Spring Boot backend log (last 500 lines)"
|
||||||
tail -500 /tmp/backend.log || echo "no backend log found"
|
tail -500 .test-state/playwright/backend.log || echo "no backend log found"
|
||||||
echo "::endgroup::"
|
echo "::endgroup::"
|
||||||
- name: Stop backend
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ -f /tmp/backend.pid ]; then
|
|
||||||
kill "$(cat /tmp/backend.pid)" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
- name: Upload backend log
|
- name: Upload backend log
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: backend-log-live-${{ github.run_id }}
|
name: backend-log-live-${{ github.run_id }}
|
||||||
path: /tmp/backend.log
|
path: .test-state/playwright/backend.log
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Upload Playwright report
|
- name: Upload Playwright report
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ jobs:
|
|||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
||||||
- name: Install Playwright (chromium only)
|
- name: Install Playwright (chromium only)
|
||||||
run: task frontend:test:e2e:install -- chromium
|
run: task e2e:install -- chromium
|
||||||
|
- name: Build frontend (production bundle for vite preview)
|
||||||
|
env:
|
||||||
|
VITE_BUILD_FOR_PREVIEW: "1"
|
||||||
|
run: task frontend:build
|
||||||
- name: Run stubbed E2E tests (chromium)
|
- name: Run stubbed E2E tests (chromium)
|
||||||
run: task frontend:test:e2e -- --project=stubbed --workers=3
|
run: task e2e:stubbed -- --workers=3
|
||||||
- name: Upload Playwright report
|
- name: Upload Playwright report
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ jobs:
|
|||||||
- name: Install Task
|
- name: Install Task
|
||||||
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
|
||||||
- name: Install all Playwright browsers
|
- name: Install all Playwright browsers
|
||||||
run: task frontend:test:e2e:install
|
run: task e2e:install
|
||||||
|
|
||||||
- name: Run E2E tests (all browsers)
|
- name: Run E2E tests (all browsers)
|
||||||
run: task frontend:test:e2e
|
run: task e2e:cross-browser
|
||||||
|
|
||||||
- name: Upload Playwright report
|
- name: Upload Playwright report
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ exampleYmlFiles/stirling/
|
|||||||
/testing/cucumber/junit/
|
/testing/cucumber/junit/
|
||||||
/testing/cucumber/report.html
|
/testing/cucumber/report.html
|
||||||
/testing/.failed_tests
|
/testing/.failed_tests
|
||||||
|
/.test-state/
|
||||||
SwaggerDoc.json
|
SwaggerDoc.json
|
||||||
|
|
||||||
# Runtime storage for uploaded files and user data (not Java source code)
|
# Runtime storage for uploaded files and user data (not Java source code)
|
||||||
|
|||||||
+21
-48
@@ -1,5 +1,8 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
|
# All gradle invocations go through `bash gradlew` (the sh wrapper) so they work
|
||||||
|
# uniformly on Linux/macOS (native bash) and Windows-with-Git-Bash.
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
dev:
|
dev:
|
||||||
desc: "Start backend dev server"
|
desc: "Start backend dev server"
|
||||||
@@ -10,58 +13,43 @@ tasks:
|
|||||||
env:
|
env:
|
||||||
SERVER_PORT: '{{.PORT}}'
|
SERVER_PORT: '{{.PORT}}'
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: '{{if .AIENGINE_URL}}set "AIENGINE_URL={{.AIENGINE_URL}}" && set "AIENGINE_ENABLED=true" && {{end}}cmd /c gradlew.bat :stirling-pdf:bootRun'
|
- '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}bash gradlew :stirling-pdf:bootRun'
|
||||||
platforms: [windows]
|
|
||||||
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
|
dev:bundled:
|
||||||
platforms: [linux, darwin]
|
desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)"
|
||||||
|
ignore_error: true
|
||||||
|
cmds:
|
||||||
|
- bash gradlew clean bootRun -PbuildWithFrontend=true
|
||||||
|
|
||||||
build:
|
build:
|
||||||
desc: "Full backend build"
|
desc: "Full backend build"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat clean build
|
- bash gradlew clean build
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew clean build
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
build:fast:
|
build:fast:
|
||||||
desc: "Build without tests"
|
desc: "Build without tests"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat clean build -x test
|
- bash gradlew clean build -x test
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew clean build -x test
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
build:ci:
|
build:ci:
|
||||||
desc: "Build for CI (formatting checked separately)"
|
desc: "Build for CI (formatting checked separately)"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat build -PnoSpotless
|
- bash gradlew build -PnoSpotless
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew build -PnoSpotless
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
desc: "Run backend tests"
|
desc: "Run backend tests"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat test
|
- bash gradlew test
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew test
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
format:
|
format:
|
||||||
desc: "Auto-fix code formatting"
|
desc: "Auto-fix code formatting"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat spotlessApply
|
- bash gradlew spotlessApply
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew spotlessApply
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
format:check:
|
format:check:
|
||||||
desc: "Check code formatting"
|
desc: "Check code formatting"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat spotlessCheck
|
- bash gradlew spotlessCheck
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew spotlessCheck
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
fix:
|
fix:
|
||||||
desc: "Auto-fix backend"
|
desc: "Auto-fix backend"
|
||||||
@@ -71,10 +59,7 @@ tasks:
|
|||||||
swagger:
|
swagger:
|
||||||
desc: "Generate OpenAPI docs"
|
desc: "Generate OpenAPI docs"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat :stirling-pdf:copySwaggerDoc
|
- bash gradlew :stirling-pdf:copySwaggerDoc
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew :stirling-pdf:copySwaggerDoc
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
sources:
|
sources:
|
||||||
- app/core/src/main/java/**/*.java
|
- app/core/src/main/java/**/*.java
|
||||||
- app/proprietary/src/main/java/**/*.java
|
- app/proprietary/src/main/java/**/*.java
|
||||||
@@ -92,31 +77,19 @@ tasks:
|
|||||||
desc: "Print project version"
|
desc: "Print project version"
|
||||||
silent: true
|
silent: true
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat printVersion --quiet | tail -1
|
- bash gradlew printVersion --quiet | tail -1
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew printVersion --quiet | tail -1
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
licenses:check:
|
licenses:check:
|
||||||
desc: "Check dependency licenses"
|
desc: "Check dependency licenses"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat checkLicense --no-parallel
|
- bash gradlew checkLicense --no-parallel
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew checkLicense --no-parallel
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
licenses:generate:
|
licenses:generate:
|
||||||
desc: "Check and generate dependency license report"
|
desc: "Check and generate dependency license report"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat checkLicense generateLicenseReport --no-parallel
|
- bash gradlew checkLicense generateLicenseReport --no-parallel
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew checkLicense generateLicenseReport --no-parallel
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
desc: "Clean build artifacts"
|
desc: "Clean build artifacts"
|
||||||
cmds:
|
cmds:
|
||||||
- cmd: cmd /c gradlew.bat clean
|
- bash gradlew clean
|
||||||
platforms: [windows]
|
|
||||||
- cmd: ./gradlew clean
|
|
||||||
platforms: [linux, darwin]
|
|
||||||
|
|||||||
@@ -55,3 +55,9 @@ tasks:
|
|||||||
desc: "Tail docker compose logs"
|
desc: "Tail docker compose logs"
|
||||||
cmds:
|
cmds:
|
||||||
- docker compose -f {{.COMPOSE_DIR}}/docker-compose.yml logs -f
|
- docker compose -f {{.COMPOSE_DIR}}/docker-compose.yml logs -f
|
||||||
|
|
||||||
|
test:
|
||||||
|
desc: "Run full Docker integration test suite (builds all variants and tests them)"
|
||||||
|
ignore_error: true
|
||||||
|
cmds:
|
||||||
|
- bash testing/test.sh {{.CLI_ARGS}}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
install:
|
||||||
|
desc: "Install Playwright browsers"
|
||||||
|
dir: frontend
|
||||||
|
deps: [ ':frontend:install' ]
|
||||||
|
cmds:
|
||||||
|
- npx playwright install {{.CLI_ARGS}} --with-deps
|
||||||
|
|
||||||
|
stubbed:
|
||||||
|
desc: "Run stubbed E2E tests"
|
||||||
|
dir: frontend
|
||||||
|
deps: [ ':frontend:prepare' ]
|
||||||
|
cmds:
|
||||||
|
- npx playwright test --project=stubbed {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
live:
|
||||||
|
desc: "Run live E2E tests"
|
||||||
|
summary: |
|
||||||
|
Auto-spawns a Spring Boot backend with isolated state under
|
||||||
|
.test-state/playwright/ (purged on every run) so the suite doesn't
|
||||||
|
touch your local DB, settings.yml, backups or customFiles. The
|
||||||
|
backend is stopped automatically when the runner exits.
|
||||||
|
|
||||||
|
Pass extra Playwright flags via -- :
|
||||||
|
task e2e:live -- --headed --grep merge
|
||||||
|
deps:
|
||||||
|
- live:backend
|
||||||
|
- live:runner
|
||||||
|
|
||||||
|
live:backend:
|
||||||
|
internal: true
|
||||||
|
ignore_error: true
|
||||||
|
vars:
|
||||||
|
BASE_DIR: '{{.ROOT_DIR}}/.test-state/playwright'
|
||||||
|
env:
|
||||||
|
STIRLING_BASE_PATH: '{{.BASE_DIR}}'
|
||||||
|
# Suppress the analytics opt-in modal that fires on first admin login.
|
||||||
|
# It renders a Mantine overlay that intercepts pointer events and blocks
|
||||||
|
# FirstLoginSlide from rendering, so the bootstrap spec times out waiting
|
||||||
|
# for the password-change prompt.
|
||||||
|
SYSTEM_ENABLEANALYTICS: "false"
|
||||||
|
# NOTE: SECURITY_INITIALLOGIN_USERNAME/PASSWORD are intentionally NOT set.
|
||||||
|
# The live-setup project's bootstrap spec performs the real first-login
|
||||||
|
# flow against the backend's default admin/stirling user, exercising the
|
||||||
|
# forced-password-change UI and leaving the DB at admin/adminadmin for
|
||||||
|
# the rest of the live suite. This is both real coverage of the first-
|
||||||
|
# login flow and a stronger seed than env-var-driven user creation.
|
||||||
|
cmds:
|
||||||
|
# Wrapped in `bash -c` because Task's embedded shell (mvdan/sh) doesn't
|
||||||
|
# support `&` + `$!` + `wait` reliably (gradle was never actually
|
||||||
|
# backgrounded, so `$!` recorded a stale PID and the runner saw an
|
||||||
|
# immediate "backend died"). Real bash backgrounds gradle properly and
|
||||||
|
# gives us a stable PID to clean up.
|
||||||
|
- |
|
||||||
|
bash -c '
|
||||||
|
set -e
|
||||||
|
rm -rf "{{.BASE_DIR}}"
|
||||||
|
mkdir -p "{{.BASE_DIR}}"
|
||||||
|
# Background gradle and record its PID so the runner can clean up
|
||||||
|
# the exact process tree (wrapper + forked Spring Boot JVM) without
|
||||||
|
# resorting to fuzzy `pkill -f` patterns. `wait` keeps this script
|
||||||
|
# alive for the lifetime of gradle so Task'"'"'s parallel deps stay
|
||||||
|
# synchronised.
|
||||||
|
bash gradlew :stirling-pdf:bootRun > "{{.BASE_DIR}}/backend.log" 2>&1 &
|
||||||
|
GRADLE_PID=$!
|
||||||
|
echo $GRADLE_PID > "{{.BASE_DIR}}/backend.pid"
|
||||||
|
wait $GRADLE_PID
|
||||||
|
'
|
||||||
|
|
||||||
|
live:runner:
|
||||||
|
internal: true
|
||||||
|
deps: [ ':frontend:prepare' ]
|
||||||
|
dir: frontend
|
||||||
|
vars:
|
||||||
|
BASE_DIR: '{{.ROOT_DIR}}/.test-state/playwright'
|
||||||
|
cmds:
|
||||||
|
# Wrapped in `bash -c` because Task's embedded shell (mvdan/sh) only
|
||||||
|
# supports `EXIT`/`ERR` in `trap`, not `INT`/`TERM`. CLI_ARGS are
|
||||||
|
# passed positionally so quoted args like --grep "foo bar" survive.
|
||||||
|
- |
|
||||||
|
bash -c '
|
||||||
|
set +e
|
||||||
|
# bootRun forks a separate Spring Boot JVM as a child of the gradle
|
||||||
|
# wrapper; killing only the wrapper leaves that JVM orphaned holding
|
||||||
|
# :8080. Walk the recorded PID'"'"'s process tree via pgrep so we kill
|
||||||
|
# every descendant, then lsof as a last-resort backstop.
|
||||||
|
kill_tree() {
|
||||||
|
local pid=$1
|
||||||
|
for child in $(pgrep -P "$pid" 2>/dev/null); do
|
||||||
|
kill_tree "$child"
|
||||||
|
done
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
cleanup() {
|
||||||
|
if [ -f "{{.BASE_DIR}}/backend.pid" ]; then
|
||||||
|
echo "Stopping backend..."
|
||||||
|
kill_tree "$(cat "{{.BASE_DIR}}/backend.pid")"
|
||||||
|
sleep 1
|
||||||
|
lsof -ti:8080 2>/dev/null | xargs kill 2>/dev/null || true
|
||||||
|
echo "Backend stopped"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# Trap so cleanup runs on every exit path: tests pass, tests fail,
|
||||||
|
# backend never starts, user Ctrl-Cs. Without this the gradle JVM
|
||||||
|
# is left orphaned holding :8080 whenever the wait loop times out.
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
echo "Waiting for backend on :8080 (up to 10min; first compile can be slow)..."
|
||||||
|
WAITED=0
|
||||||
|
while [ $WAITED -lt 600 ]; do
|
||||||
|
if curl -sf http://localhost:8080/api/v1/info/status 2>/dev/null | grep -q UP; then
|
||||||
|
echo "Backend ready, starting Playwright"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# Bail early if the backend already died: no point waiting 10min.
|
||||||
|
if [ -f "{{.BASE_DIR}}/backend.pid" ]; then
|
||||||
|
BACKEND_PID=$(cat "{{.BASE_DIR}}/backend.pid")
|
||||||
|
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
|
||||||
|
echo "Backend process exited before becoming ready; last 100 log lines:"
|
||||||
|
tail -100 "{{.BASE_DIR}}/backend.log" 2>/dev/null || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
WAITED=$((WAITED + 2))
|
||||||
|
done
|
||||||
|
if [ $WAITED -ge 600 ]; then
|
||||||
|
echo "Backend did not become ready within 10min, aborting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
npx playwright test --project=live "$@"
|
||||||
|
' bash {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
enterprise:
|
||||||
|
desc: "Run enterprise E2E tests"
|
||||||
|
summary: |
|
||||||
|
Requires an already-running keycloak compose stack on :8080. Bring one
|
||||||
|
up first with:
|
||||||
|
task e2e:oauth:up
|
||||||
|
task e2e:saml:up
|
||||||
|
dir: frontend
|
||||||
|
deps: [ ':frontend:prepare' ]
|
||||||
|
cmds:
|
||||||
|
- npx playwright test --project=enterprise {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
cross-browser:
|
||||||
|
desc: "Run stubbed E2E tests on Chromium, Firefox, and WebKit"
|
||||||
|
dir: frontend
|
||||||
|
deps: [ ':frontend:prepare' ]
|
||||||
|
cmds:
|
||||||
|
- npx playwright test --project=stubbed --project=stubbed-firefox --project=stubbed-webkit {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
check:
|
||||||
|
desc: "Run all E2E tests not requiring Docker"
|
||||||
|
cmds:
|
||||||
|
- task: stubbed
|
||||||
|
- task: live
|
||||||
|
|
||||||
|
check:all:
|
||||||
|
desc: "Run all E2E tests"
|
||||||
|
summary: |
|
||||||
|
Includes the enterprise project, which requires a keycloak compose
|
||||||
|
stack on :8080. Bring one up first with:
|
||||||
|
task e2e:oauth:up
|
||||||
|
task e2e:saml:up
|
||||||
|
cmds:
|
||||||
|
- task: stubbed
|
||||||
|
- task: live
|
||||||
|
- task: enterprise
|
||||||
|
|
||||||
|
oauth:up:
|
||||||
|
desc: "Start the OAuth keycloak test environment"
|
||||||
|
summary: |
|
||||||
|
Set LICENSE_KEY=<KEY> to skip the interactive license prompt:
|
||||||
|
task e2e:oauth:up LICENSE_KEY=abc123
|
||||||
|
Pass extra flags via -- :
|
||||||
|
task e2e:oauth:up -- --auto --nobuild
|
||||||
|
ignore_error: true
|
||||||
|
cmds:
|
||||||
|
- bash testing/compose/start-oauth-test.sh {{if .LICENSE_KEY}}--license-key "{{.LICENSE_KEY}}"{{end}} {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
oauth:down:
|
||||||
|
desc: "Stop the OAuth keycloak test environment"
|
||||||
|
cmds:
|
||||||
|
- docker compose -f testing/compose/docker-compose-keycloak-oauth.yml down -v
|
||||||
|
|
||||||
|
saml:up:
|
||||||
|
desc: "Start the SAML keycloak test environment"
|
||||||
|
summary: |
|
||||||
|
Set LICENSE_KEY=<KEY> to skip the interactive license prompt:
|
||||||
|
task e2e:saml:up LICENSE_KEY=abc123
|
||||||
|
Pass extra flags via -- :
|
||||||
|
task e2e:saml:up -- --auto --with-storage --nobuild --language sv-SE
|
||||||
|
ignore_error: true
|
||||||
|
cmds:
|
||||||
|
- bash testing/compose/start-saml-test.sh {{if .LICENSE_KEY}}--license-key "{{.LICENSE_KEY}}"{{end}} {{.CLI_ARGS}}
|
||||||
|
|
||||||
|
saml:down:
|
||||||
|
desc: "Stop the SAML keycloak test environment"
|
||||||
|
cmds:
|
||||||
|
- docker compose -f testing/compose/docker-compose-keycloak-saml.yml down -v
|
||||||
@@ -284,18 +284,6 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- npx vitest --coverage
|
- npx vitest --coverage
|
||||||
|
|
||||||
test:e2e:
|
|
||||||
desc: "Run E2E tests"
|
|
||||||
deps: [prepare]
|
|
||||||
cmds:
|
|
||||||
- npx playwright test {{.CLI_ARGS}}
|
|
||||||
|
|
||||||
test:e2e:install:
|
|
||||||
desc: "Install E2E test browsers"
|
|
||||||
deps: [install]
|
|
||||||
cmds:
|
|
||||||
- npx playwright install {{.CLI_ARGS}} --with-deps
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Code Generation
|
# Code Generation
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ includes:
|
|||||||
desktop:
|
desktop:
|
||||||
taskfile: .taskfiles/desktop.yml
|
taskfile: .taskfiles/desktop.yml
|
||||||
dir: frontend
|
dir: frontend
|
||||||
|
e2e:
|
||||||
|
taskfile: .taskfiles/e2e.yml
|
||||||
|
dir: .
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
+12
@@ -59,6 +59,18 @@ public class InstallationPathConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String initializeBasePath() {
|
private static String initializeBasePath() {
|
||||||
|
// Allow tests / harnesses to redirect the entire state tree (configs,
|
||||||
|
// backups, customFiles, pipeline, logs) to an isolated location via
|
||||||
|
// -Dstirling.base-path=... or STIRLING_BASE_PATH=... so a Playwright
|
||||||
|
// run never touches a developer's working state.
|
||||||
|
String override = System.getProperty("stirling.base-path");
|
||||||
|
if (override == null || override.isBlank()) {
|
||||||
|
override = System.getenv("STIRLING_BASE_PATH");
|
||||||
|
}
|
||||||
|
if (override != null && !override.isBlank()) {
|
||||||
|
boolean hasTrailingSeparator = override.endsWith("/") || override.endsWith("\\");
|
||||||
|
return hasTrailingSeparator ? override : override + File.separator;
|
||||||
|
}
|
||||||
return "." + File.separator;
|
return "." + File.separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,13 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: "npx vite",
|
// In CI, serve a pre-built `dist/` via `vite preview` so the heavy tool
|
||||||
|
// pages don't pay vite's on-demand transform cost on first hit (which
|
||||||
|
// blew the 30s navigationTimeout under --workers=3 — see
|
||||||
|
// all-tool-pages-load.spec.ts). Locally, keep `vite` dev for HMR.
|
||||||
|
command: process.env.CI
|
||||||
|
? "npx vite preview --port 5173 --strictPort"
|
||||||
|
: "npx vite",
|
||||||
url: "http://localhost:5173",
|
url: "http://localhost:5173",
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
timeout: 120_000,
|
timeout: 120_000,
|
||||||
|
|||||||
@@ -157,33 +157,39 @@ const Compare = (props: BaseToolProps) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Track previous file count to detect the transition to exactly 2 files.
|
// Keep slot params in sync with the current workbench/selection state.
|
||||||
const prevAllIdsLengthRef = useRef<number | null>(null);
|
//
|
||||||
|
// The effect must be idempotent: it can fire repeatedly during an upload
|
||||||
// Auto-fill slots when the file count first reaches 2; respect manual picker changes after that.
|
// burst (ADD_FILES, SET_SELECTED_FILES, and downstream dispatches all land
|
||||||
|
// in quick succession), and any "did the count transition?" tracking via
|
||||||
|
// a ref races with React's batched re-renders under load. With workers=3
|
||||||
|
// in CI, that race surfaced as a "Maximum update depth exceeded" crash
|
||||||
|
// when the second slot was filled. So the implementation derives the
|
||||||
|
// target params purely from current state and bails when there's nothing
|
||||||
|
// to change.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedIds = fileState.ui.selectedFileIds as FileId[];
|
const selectedIds = fileState.ui.selectedFileIds as FileId[];
|
||||||
const allIds = fileState.files.ids as FileId[];
|
const allIds = fileState.files.ids as FileId[];
|
||||||
const prevLength = prevAllIdsLengthRef.current;
|
|
||||||
prevAllIdsLengthRef.current = allIds.length;
|
|
||||||
|
|
||||||
if (allIds.length === 2 && prevLength !== 2) {
|
|
||||||
// Transitioned to exactly 2 files — auto-fill both slots.
|
|
||||||
const [firstId, secondId] = allIds as [FileId, FileId];
|
|
||||||
fileActions.setSelectedFiles([firstId, secondId]);
|
|
||||||
base.params.setParameters((prev) => {
|
|
||||||
if (prev.baseFileId === firstId && prev.comparisonFileId === secondId)
|
|
||||||
return prev;
|
|
||||||
return { ...prev, baseFileId: firstId, comparisonFileId: secondId };
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedIds.length > 2) {
|
if (selectedIds.length > 2) {
|
||||||
fileActions.setSelectedFiles(selectedIds.slice(0, 2) as FileId[]);
|
fileActions.setSelectedFiles(selectedIds.slice(0, 2) as FileId[]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When exactly 2 files are loaded but the selection doesn't cover both,
|
||||||
|
// promote the selection so Compare can run. Skips when the selection is
|
||||||
|
// already correct so we don't dispatch a fresh array reference and
|
||||||
|
// retrigger this effect. Covers the upload window where ADD_FILES has
|
||||||
|
// landed but SET_SELECTED_FILES is still in flight.
|
||||||
|
if (allIds.length === 2) {
|
||||||
|
const selectionMatchesAllIds =
|
||||||
|
selectedIds.length === 2 &&
|
||||||
|
allIds.every((id) => selectedIds.includes(id));
|
||||||
|
if (!selectionMatchesAllIds) {
|
||||||
|
fileActions.setSelectedFiles([...allIds] as FileId[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sticky slot mapping: preserve the current slot if its file is still selected,
|
// Sticky slot mapping: preserve the current slot if its file is still selected,
|
||||||
// otherwise fill empty slots from the selection in order. This lets users add/remove
|
// otherwise fill empty slots from the selection in order. This lets users add/remove
|
||||||
// files from the selection without the other slot being clobbered.
|
// files from the selection without the other slot being clobbered.
|
||||||
|
|||||||
+34
-13
@@ -49,6 +49,21 @@ export default defineConfig(async ({ mode }) => {
|
|||||||
xfwd: true,
|
xfwd: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Shared between `vite` (dev) and `vite preview` (production-build serve, used
|
||||||
|
// in CI/E2E) so the live test suite still resolves /api → :8080.
|
||||||
|
const backendProxyConfig =
|
||||||
|
effectiveMode === "desktop"
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
"/api": backendProxy,
|
||||||
|
"/oauth2": backendProxy,
|
||||||
|
"/saml2": backendProxy,
|
||||||
|
"/login/oauth2": backendProxy,
|
||||||
|
"/login/saml2": backendProxy,
|
||||||
|
"/swagger-ui": backendProxy,
|
||||||
|
"/v1/api-docs": backendProxy,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
@@ -107,19 +122,25 @@ export default defineConfig(async ({ mode }) => {
|
|||||||
ignored: ["**/src-tauri/**"],
|
ignored: ["**/src-tauri/**"],
|
||||||
},
|
},
|
||||||
// Only use proxy in web mode - Tauri handles backend connections directly
|
// Only use proxy in web mode - Tauri handles backend connections directly
|
||||||
proxy:
|
proxy: backendProxyConfig,
|
||||||
effectiveMode === "desktop"
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
"/api": backendProxy,
|
|
||||||
"/oauth2": backendProxy,
|
|
||||||
"/saml2": backendProxy,
|
|
||||||
"/login/oauth2": backendProxy,
|
|
||||||
"/login/saml2": backendProxy,
|
|
||||||
"/swagger-ui": backendProxy,
|
|
||||||
"/v1/api-docs": backendProxy,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
base: env.RUN_SUBPATH ? `/${env.RUN_SUBPATH}` : "./",
|
preview: {
|
||||||
|
host: true,
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
proxy: backendProxyConfig,
|
||||||
|
},
|
||||||
|
// base: "./" produces relative asset URLs which work when dist/ is served
|
||||||
|
// at any path (e.g. Spring Boot bundling the frontend at /). But under
|
||||||
|
// `vite preview` for deep SPA routes (e.g. /workflow/sign/<token>), the
|
||||||
|
// browser resolves ./assets/X.js relative to the current path → 404, then
|
||||||
|
// SPA fallback returns index.html as text/html and React never mounts.
|
||||||
|
// VITE_BUILD_FOR_PREVIEW=1 (set by the CI playwright steps) overrides to
|
||||||
|
// an absolute base so deep-route asset paths resolve to /assets/...
|
||||||
|
base: env.RUN_SUBPATH
|
||||||
|
? `/${env.RUN_SUBPATH}`
|
||||||
|
: process.env.VITE_BUILD_FOR_PREVIEW === "1"
|
||||||
|
? "/"
|
||||||
|
: "./",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,27 +15,52 @@ echo ""
|
|||||||
AUTO_LOGIN=false
|
AUTO_LOGIN=false
|
||||||
FORCE_ALL_LOGIN=false
|
FORCE_ALL_LOGIN=false
|
||||||
COMPOSE_UP_ARGS=(-d --build)
|
COMPOSE_UP_ARGS=(-d --build)
|
||||||
for arg in "$@"; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$arg" in
|
case "$1" in
|
||||||
--auto)
|
--auto)
|
||||||
AUTO_LOGIN=true
|
AUTO_LOGIN=true
|
||||||
|
shift
|
||||||
;;
|
;;
|
||||||
--all)
|
--all)
|
||||||
FORCE_ALL_LOGIN=true
|
FORCE_ALL_LOGIN=true
|
||||||
|
shift
|
||||||
;;
|
;;
|
||||||
--nobuild)
|
--nobuild)
|
||||||
COMPOSE_UP_ARGS=(-d)
|
COMPOSE_UP_ARGS=(-d)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--license-key)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for --license-key${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export PREMIUM_KEY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--license-key=*)
|
||||||
|
export PREMIUM_KEY="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-k)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for -k${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export PREMIUM_KEY="$2"
|
||||||
|
shift 2
|
||||||
;;
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: $0 [--auto] [--nobuild]"
|
echo "Usage: $0 [--auto] [--all] [--nobuild] [--license-key <KEY>]"
|
||||||
echo ""
|
echo ""
|
||||||
echo " --auto Enable SSO auto-login and force OAuth-only login method"
|
echo " --auto Enable SSO auto-login and force OAuth-only login method"
|
||||||
echo " --all Force login method to allow all providers (overrides --auto)"
|
echo " --all Force login method to allow all providers (overrides --auto)"
|
||||||
echo " --nobuild Skip building images (use existing images)"
|
echo " --nobuild Skip building images (use existing images)"
|
||||||
|
echo " --license-key <KEY> Premium license key (skips the interactive prompt)"
|
||||||
|
echo " Equivalent to setting PREMIUM_KEY in the environment."
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Unknown option: $arg${NC}"
|
echo -e "${RED}Unknown option: $1${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -55,14 +55,36 @@ while [[ $# -gt 0 ]]; do
|
|||||||
DEFAULT_LANGUAGE="$2"
|
DEFAULT_LANGUAGE="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--license-key)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for --license-key${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export PREMIUM_KEY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--license-key=*)
|
||||||
|
export PREMIUM_KEY="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-k)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo -e "${RED}Missing value for -k${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export PREMIUM_KEY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "Usage: $0 [--auto] [--with-storage] [--nobuild] [--language <locale>]"
|
echo "Usage: $0 [--auto] [--with-storage] [--nobuild] [--language <locale>] [--license-key <KEY>]"
|
||||||
echo ""
|
echo ""
|
||||||
echo " --auto Enable SSO auto-login and force SAML-only login method"
|
echo " --auto Enable SSO auto-login and force SAML-only login method"
|
||||||
echo " --with-storage Enable the file storage + link-sharing feature"
|
echo " --with-storage Enable the file storage + link-sharing feature"
|
||||||
echo " (required to test /share/<token> flows)"
|
echo " (required to test /share/<token> flows)"
|
||||||
echo " --nobuild Skip building images (use existing images)"
|
echo " --nobuild Skip building images (use existing images)"
|
||||||
echo " --language Set system default locale (e.g. de-DE, sv-SE)"
|
echo " --language Set system default locale (e.g. de-DE, sv-SE)"
|
||||||
|
echo " --license-key <KEY> Premium license key (skips the interactive prompt)"
|
||||||
|
echo " Equivalent to setting PREMIUM_KEY in the environment."
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
Reference in New Issue
Block a user