From 3fd52ed564c654cdf7f804588fa8150e9e61d95d Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Wed, 16 Jul 2025 23:09:26 +0100 Subject: [PATCH 1/7] testing and docker replacements --- .github/workflows/build.yml | 44 +++++- docker/backend/Dockerfile | 34 ++++- docker/backend/Dockerfile.fat | 3 +- docker/backend/Dockerfile.ultra-lite | 29 +++- docker/compose/docker-compose.fat.yml | 6 +- docker/compose/docker-compose.monolith.yml | 2 - docker/compose/docker-compose.ultra-lite.yml | 6 +- docker/compose/docker-compose.yml | 6 +- docker/frontend/Dockerfile | 8 +- docker/frontend/nginx.conf | 18 +++ ...-compose-latest-fat-endpoints-disabled.yml | 36 ----- ...r-compose-latest-fat-security-postgres.yml | 64 --------- .../docker-compose-latest-fat-security.yml | 34 ----- ...ocker-compose-latest-security-with-sso.yml | 42 ------ .../docker-compose-latest-security.yml | 34 ----- ...ker-compose-latest-ultra-lite-security.yml | 31 ----- exampleYmlFiles/docker-compose-latest.yml | 31 ----- exampleYmlFiles/test_cicd.yml | 34 ----- .../src/components/pageEditor/PageEditor.tsx | 2 +- scripts/init-without-ocr.sh | 5 +- .../docker-compose-security-with-login.yml | 63 +++++++++ testing/compose/docker-compose-security.yml | 59 +++++++++ .../compose/docker-compose-ultra-lite.yml | 40 +++++- testing/test.sh | 125 ++++++++---------- testing/test2.sh | 34 ++--- 25 files changed, 363 insertions(+), 427 deletions(-) delete mode 100644 exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml delete mode 100644 exampleYmlFiles/docker-compose-latest-fat-security-postgres.yml delete mode 100644 exampleYmlFiles/docker-compose-latest-fat-security.yml delete mode 100644 exampleYmlFiles/docker-compose-latest-security-with-sso.yml delete mode 100644 exampleYmlFiles/docker-compose-latest-security.yml delete mode 100644 exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml delete mode 100644 exampleYmlFiles/docker-compose-latest.yml delete mode 100644 exampleYmlFiles/test_cicd.yml create mode 100644 testing/compose/docker-compose-security-with-login.yml create mode 100644 testing/compose/docker-compose-security.yml rename exampleYmlFiles/docker-compose-latest-ultra-lite.yml => testing/compose/docker-compose-ultra-lite.yml (50%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa98d2a1e..3c8a85782 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: Build repo on: push: - branches: ["main"] + branches: ["main", "V2", "V2-gha"] pull_request: - branches: ["main"] + branches: ["main", "V2", "V2-gha"] permissions: contents: read @@ -114,6 +114,46 @@ jobs: name: openapi-docs path: ./SwaggerDoc.json + frontend-validation: + runs-on: ubuntu-latest + 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: Set up Node.js + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # 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 (if available) + run: | + cd frontend + npm test --passWithNoTests --watchAll=false || true + + - name: Upload frontend build artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: frontend-build + path: frontend/dist/ + retention-days: 3 + check-licence: runs-on: ubuntu-latest steps: diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 68dba6108..d3e64a843 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,11 +1,36 @@ -# Backend Dockerfile - Java Spring Boot with all dependencies +# Backend Dockerfile - Java Spring Boot with all dependencies and build stage +# Build the application +FROM gradle:8.14-jdk21 AS build + +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set the working directory +WORKDIR /app + +# Copy the entire project to the working directory +COPY . . + +# Build the application with DISABLE_ADDITIONAL_FEATURES=false +RUN DISABLE_ADDITIONAL_FEATURES=false \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Main stage FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 # Copy necessary files COPY scripts /scripts COPY pipeline /pipeline COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ -COPY app/core/build/libs/*.jar app.jar +# first /app directory is for the build stage, second is for the final image +COPY --from=build /app/app/core/build/libs/*.jar app.jar ARG VERSION_TAG @@ -22,7 +47,7 @@ LABEL org.opencontainers.image.version="${VERSION_TAG}" LABEL org.opencontainers.image.keywords="PDF, manipulation, backend, API, Spring Boot" # Set Environment Variables -ENV DISABLE_ADDITIONAL_FEATURES=true \ +ENV DISABLE_ADDITIONAL_FEATURES=false \ VERSION_TAG=$VERSION_TAG \ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ JAVA_CUSTOM_OPTS="" \ @@ -50,7 +75,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a tini \ bash \ curl \ - qpdf \ shadow \ su-exec \ openssl \ @@ -63,11 +87,13 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a # pdftohtml poppler-utils \ # OCR MY PDF (unpaper for descew and other advanced features) + unpaper \ tesseract-ocr-data-eng \ tesseract-ocr-data-chi_sim \ tesseract-ocr-data-deu \ tesseract-ocr-data-fra \ tesseract-ocr-data-por \ + ocrmypdf \ # CV py3-opencv \ python3 \ diff --git a/docker/backend/Dockerfile.fat b/docker/backend/Dockerfile.fat index 5d4b73385..0fc2acc77 100644 --- a/docker/backend/Dockerfile.fat +++ b/docker/backend/Dockerfile.fat @@ -77,12 +77,13 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a # pdftohtml poppler-utils \ # OCR MY PDF (unpaper for descew and other advanced featues) - qpdf \ + unpaper \ tesseract-ocr-data-eng \ tesseract-ocr-data-chi_sim \ tesseract-ocr-data-deu \ tesseract-ocr-data-fra \ tesseract-ocr-data-por \ + ocrmypdf \ font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \ # CV py3-opencv \ diff --git a/docker/backend/Dockerfile.ultra-lite b/docker/backend/Dockerfile.ultra-lite index 6291736e2..8c5e41cc5 100644 --- a/docker/backend/Dockerfile.ultra-lite +++ b/docker/backend/Dockerfile.ultra-lite @@ -1,4 +1,28 @@ -# Backend ultra-lite Dockerfile - Java Spring Boot with minimal dependencies +# Backend ultra-lite Dockerfile - Java Spring Boot with minimal dependencies and build stage +# Build the application +FROM gradle:8.14-jdk21 AS build + +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set the working directory +WORKDIR /app + +# Copy the entire project to the working directory +COPY . . + +# Build the application with DISABLE_ADDITIONAL_FEATURES=true +RUN DISABLE_ADDITIONAL_FEATURES=true \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + +# Main stage FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 ARG VERSION_TAG @@ -18,11 +42,10 @@ ENV DISABLE_ADDITIONAL_FEATURES=true \ TMP=/tmp/stirling-pdf # Copy necessary files -COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh COPY scripts/installFonts.sh /scripts/installFonts.sh COPY pipeline /pipeline -COPY app/core/build/libs/*.jar app.jar +COPY --from=build /app/app/core/build/libs/*.jar app.jar # Set up necessary directories and permissions RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ diff --git a/docker/compose/docker-compose.fat.yml b/docker/compose/docker-compose.fat.yml index 764c9604c..8399c4080 100644 --- a/docker/compose/docker-compose.fat.yml +++ b/docker/compose/docker-compose.fat.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: backend: build: @@ -43,8 +41,8 @@ services: frontend: build: - context: ../../frontend - dockerfile: ../docker/frontend/Dockerfile + context: ../.. + dockerfile: docker/frontend/Dockerfile container_name: stirling-pdf-frontend-fat restart: on-failure:5 ports: diff --git a/docker/compose/docker-compose.monolith.yml b/docker/compose/docker-compose.monolith.yml index 06064cdf7..82d40f2bd 100644 --- a/docker/compose/docker-compose.monolith.yml +++ b/docker/compose/docker-compose.monolith.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: stirling-pdf-monolith: build: diff --git a/docker/compose/docker-compose.ultra-lite.yml b/docker/compose/docker-compose.ultra-lite.yml index a16484262..bfbf55861 100644 --- a/docker/compose/docker-compose.ultra-lite.yml +++ b/docker/compose/docker-compose.ultra-lite.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: backend: build: @@ -38,8 +36,8 @@ services: frontend: build: - context: ../../frontend - dockerfile: ../docker/frontend/Dockerfile + context: ../.. + dockerfile: docker/frontend/Dockerfile container_name: stirling-pdf-frontend-ultra-lite restart: on-failure:5 ports: diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml index 3bcdca423..4defef872 100644 --- a/docker/compose/docker-compose.yml +++ b/docker/compose/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: backend: build: @@ -41,8 +39,8 @@ services: frontend: build: - context: ../../frontend - dockerfile: ../docker/frontend/Dockerfile + context: ../.. + dockerfile: docker/frontend/Dockerfile container_name: stirling-pdf-frontend restart: on-failure:5 ports: diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index af570b6bb..7a5dc6025 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -4,13 +4,13 @@ FROM node:20-alpine AS build WORKDIR /app # Copy package files -COPY package*.json ./ +COPY frontend/package*.json ./ # Install dependencies RUN npm ci # Copy source code -COPY . . +COPY frontend . # Build the application RUN npm run build @@ -22,8 +22,8 @@ FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html # Copy nginx configuration and entrypoint -COPY nginx.conf /etc/nginx/nginx.conf -COPY entrypoint.sh /entrypoint.sh +COPY docker/frontend/nginx.conf /etc/nginx/nginx.conf +COPY docker/frontend/entrypoint.sh /entrypoint.sh # Make entrypoint executable RUN chmod +x /entrypoint.sh diff --git a/docker/frontend/nginx.conf b/docker/frontend/nginx.conf index f45f5784b..400ad65f1 100644 --- a/docker/frontend/nginx.conf +++ b/docker/frontend/nginx.conf @@ -17,6 +17,9 @@ http { server_name _; root /usr/share/nginx/html; index index.html index.htm; + + # Global settings for file uploads + client_max_body_size 100m; # Handle client-side routing - support subpaths location / { @@ -32,6 +35,21 @@ http { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; + + # Additional headers for proper API proxying + proxy_set_header Connection ''; + proxy_http_version 1.1; + proxy_buffering off; + proxy_cache off; + + # Timeout settings for large file uploads + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # Request size limits for file uploads + client_max_body_size 100m; + proxy_request_buffering off; } # Cache static assets diff --git a/exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml b/exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml deleted file mode 100644 index 827de1e19..000000000 --- a/exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml +++ /dev/null @@ -1,36 +0,0 @@ - -services: - stirling-pdf: - container_name: Stirling-PDF-Fat-Disable-Endpoints - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - 8080:8080 - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - - ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "false" - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with all Endpoints Disabled - UI_APPNAMENAVBAR: Stirling-PDF Latest-fat - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/docker-compose-latest-fat-security-postgres.yml b/exampleYmlFiles/docker-compose-latest-fat-security-postgres.yml deleted file mode 100644 index bbf8a2115..000000000 --- a/exampleYmlFiles/docker-compose-latest-fat-security-postgres.yml +++ /dev/null @@ -1,64 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Security-Fat-Postgres - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat-postgres - deploy: - resources: - limits: - memory: 4G - depends_on: - - db - healthcheck: - test: [ "CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'" ] - interval: 5s - timeout: 10s - retries: 16 - ports: - - 8080:8080 - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "false" - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security and PostgreSQL - UI_APPNAMENAVBAR: Stirling-PDF Latest-fat-PostgreSQL - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "true" - SYSTEM_DATASOURCE_CUSTOMDATABASEURL: "jdbc:postgresql://db:5432/stirling_pdf" - SYSTEM_DATASOURCE_USERNAME: "admin" - SYSTEM_DATASOURCE_PASSWORD: "stirling" - SHOW_SURVEY: "true" - restart: on-failure:5 - - db: - image: 'postgres:17.2-alpine' - restart: on-failure:5 - container_name: db - ports: - - "5432:5432" - environment: - POSTGRES_DB: "stirling_pdf" - POSTGRES_USER: "admin" - POSTGRES_PASSWORD: "stirling" - shm_size: "512mb" - deploy: - resources: - limits: - memory: 512m - cpus: "0.5" - healthcheck: - test: [ "CMD-SHELL", "pg_isready -U admin stirling_pdf" ] - interval: 1s - timeout: 5s - retries: 10 - volumes: - - ./stirling/latest/data:/pgdata diff --git a/exampleYmlFiles/docker-compose-latest-fat-security.yml b/exampleYmlFiles/docker-compose-latest-fat-security.yml deleted file mode 100644 index 5b07420ff..000000000 --- a/exampleYmlFiles/docker-compose-latest-fat-security.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Security-Fat - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - 8080:8080 - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "false" - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security - UI_APPNAMENAVBAR: Stirling-PDF Latest-fat - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml b/exampleYmlFiles/docker-compose-latest-security-with-sso.yml deleted file mode 100644 index 55ea0893d..000000000 --- a/exampleYmlFiles/docker-compose-latest-security-with-sso.yml +++ /dev/null @@ -1,42 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Security - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - "8080:8080" - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "true" - SECURITY_OAUTH2_ENABLED: "true" - SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF - SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point - SECURITY_OAUTH2_CLIENTID: ".apps.googleusercontent.com" # Client ID from your provider - SECURITY_OAUTH2_CLIENTSECRET: "" # Client Secret from your provider - SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope - SECURITY_OAUTH2_USEASUSERNAME: "email" # Default is 'email'; custom fields can be used as the username - SECURITY_OAUTH2_PROVIDER: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak' - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security - UI_APPNAMENAVBAR: Stirling-PDF Latest - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/docker-compose-latest-security.yml b/exampleYmlFiles/docker-compose-latest-security.yml deleted file mode 100644 index c6589ab9c..000000000 --- a/exampleYmlFiles/docker-compose-latest-security.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Security - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - "8080:8080" - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "true" - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security - UI_APPNAMENAVBAR: Stirling-PDF Latest - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml b/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml deleted file mode 100644 index fe839d941..000000000 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml +++ /dev/null @@ -1,31 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Ultra-Lite-Security - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite - deploy: - resources: - limits: - memory: 1G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - "8080:8080" - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "true" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF-Lite - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security - UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/docker-compose-latest.yml b/exampleYmlFiles/docker-compose-latest.yml deleted file mode 100644 index a68da538a..000000000 --- a/exampleYmlFiles/docker-compose-latest.yml +++ /dev/null @@ -1,31 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - "8080:8080" - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - SECURITY_ENABLELOGIN: "false" - LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest - UI_APPNAMENAVBAR: Stirling-PDF Latest - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - restart: on-failure:5 diff --git a/exampleYmlFiles/test_cicd.yml b/exampleYmlFiles/test_cicd.yml deleted file mode 100644 index 31e24da48..000000000 --- a/exampleYmlFiles/test_cicd.yml +++ /dev/null @@ -1,34 +0,0 @@ -services: - stirling-pdf: - container_name: Stirling-PDF-Security-Fat-with-login - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"] - interval: 5s - timeout: 10s - retries: 16 - ports: - - 8080:8080 - volumes: - - ./stirling/latest/data:/usr/share/tessdata:rw - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "false" - SECURITY_ENABLELOGIN: "true" - PUID: 1002 - PGID: 1002 - UMASK: "022" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security - UI_APPNAMENAVBAR: Stirling-PDF Latest-fat - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SECURITY_CUSTOMGLOBALAPIKEY: "123456789" - restart: on-failure:5 diff --git a/frontend/src/components/pageEditor/PageEditor.tsx b/frontend/src/components/pageEditor/PageEditor.tsx index f1a5dfd3c..4ba56a291 100644 --- a/frontend/src/components/pageEditor/PageEditor.tsx +++ b/frontend/src/components/pageEditor/PageEditor.tsx @@ -21,7 +21,7 @@ import { pdfExportService } from "../../services/pdfExportService"; import { useThumbnailGeneration } from "../../hooks/useThumbnailGeneration"; import { calculateScaleFromFileSize } from "../../utils/thumbnailUtils"; import { fileStorage } from "../../services/fileStorage"; -import './pageEditor.module.css'; +import './PageEditor.module.css'; import PageThumbnail from './PageThumbnail'; import BulkSelectionPanel from './BulkSelectionPanel'; import DragDropGrid from './DragDropGrid'; diff --git a/scripts/init-without-ocr.sh b/scripts/init-without-ocr.sh index 73d9feb4a..bbc1a32ec 100644 --- a/scripts/init-without-ocr.sh +++ b/scripts/init-without-ocr.sh @@ -19,9 +19,8 @@ if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" && "$FAT_DOCKER" != "true" #apk add --no-cache calibre@testing fi -if [[ "$FAT_DOCKER" != "true" ]]; then - /scripts/download-security-jar.sh -fi +# Security jar is now built into the application jar during Docker build +# No need to download it separately if [[ -n "$LANGS" ]]; then /scripts/installFonts.sh $LANGS diff --git a/testing/compose/docker-compose-security-with-login.yml b/testing/compose/docker-compose-security-with-login.yml new file mode 100644 index 000000000..feb91b080 --- /dev/null +++ b/testing/compose/docker-compose-security-with-login.yml @@ -0,0 +1,63 @@ +services: + backend: + build: + context: ../.. + dockerfile: docker/backend/Dockerfile + container_name: Stirling-PDF-Security-with-login + restart: on-failure:5 + deploy: + resources: + limits: + memory: 4G + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"] + interval: 5s + timeout: 10s + retries: 16 + ports: + - "8080:8080" + volumes: + - ../../stirling/latest/data:/usr/share/tessdata:rw + - ../../stirling/latest/config:/configs:rw + - ../../stirling/latest/logs:/logs:rw + environment: + DISABLE_ADDITIONAL_FEATURES: "false" + DOCKER_ENABLE_SECURITY: "true" + SECURITY_ENABLELOGIN: "true" + SECURITY_INITIALLOGIN_USERNAME: "admin" + SECURITY_INITIALLOGIN_PASSWORD: "stirling" + SYSTEM_DEFAULTLOCALE: en-US + UI_APPNAME: Stirling-PDF + UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security and Login + UI_APPNAMENAVBAR: Stirling-PDF Latest + SYSTEM_MAXFILESIZE: "100" + METRICS_ENABLED: "true" + SYSTEM_GOOGLEVISIBILITY: "true" + SECURITY_CUSTOMGLOBALAPIKEY: "123456789" + SHOW_SURVEY: "true" + networks: + - stirling-network + + frontend: + build: + context: ../.. + dockerfile: docker/frontend/Dockerfile + container_name: stirling-pdf-frontend-security-login + restart: on-failure:5 + ports: + - "3000:80" + environment: + BACKEND_URL: http://backend:8080 + depends_on: + - backend + networks: + - stirling-network + +networks: + stirling-network: + driver: bridge + +volumes: + stirling-data: + stirling-config: + stirling-logs: \ No newline at end of file diff --git a/testing/compose/docker-compose-security.yml b/testing/compose/docker-compose-security.yml new file mode 100644 index 000000000..14aedb697 --- /dev/null +++ b/testing/compose/docker-compose-security.yml @@ -0,0 +1,59 @@ +services: + backend: + build: + context: ../.. + dockerfile: docker/backend/Dockerfile + container_name: Stirling-PDF-Security + restart: on-failure:5 + deploy: + resources: + limits: + memory: 4G + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"] + interval: 5s + timeout: 10s + retries: 16 + ports: + - "8080:8080" + volumes: + - ../../stirling/latest/data:/usr/share/tessdata:rw + - ../../stirling/latest/config:/configs:rw + - ../../stirling/latest/logs:/logs:rw + environment: + DISABLE_ADDITIONAL_FEATURES: "false" + SECURITY_ENABLELOGIN: "false" + SYSTEM_DEFAULTLOCALE: en-US + UI_APPNAME: Stirling-PDF + UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security + UI_APPNAMENAVBAR: Stirling-PDF Latest + SYSTEM_MAXFILESIZE: "100" + METRICS_ENABLED: "true" + SYSTEM_GOOGLEVISIBILITY: "true" + SHOW_SURVEY: "true" + networks: + - stirling-network + + frontend: + build: + context: ../.. + dockerfile: docker/frontend/Dockerfile + container_name: stirling-pdf-frontend-security + restart: on-failure:5 + ports: + - "3000:80" + environment: + BACKEND_URL: http://backend:8080 + depends_on: + - backend + networks: + - stirling-network + +networks: + stirling-network: + driver: bridge + +volumes: + stirling-data: + stirling-config: + stirling-logs: \ No newline at end of file diff --git a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml b/testing/compose/docker-compose-ultra-lite.yml similarity index 50% rename from exampleYmlFiles/docker-compose-latest-ultra-lite.yml rename to testing/compose/docker-compose-ultra-lite.yml index a3710ad82..2ea9464a6 100644 --- a/exampleYmlFiles/docker-compose-latest-ultra-lite.yml +++ b/testing/compose/docker-compose-ultra-lite.yml @@ -1,11 +1,14 @@ services: - stirling-pdf: + backend: + build: + context: ../.. + dockerfile: docker/backend/Dockerfile.ultra-lite container_name: Stirling-PDF-Ultra-Lite - image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite + restart: on-failure:5 deploy: resources: limits: - memory: 1G + memory: 2G healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"] interval: 5s @@ -14,10 +17,12 @@ services: ports: - "8080:8080" volumes: - - ./stirling/latest/config:/configs:rw - - ./stirling/latest/logs:/logs:rw + - ../../stirling/latest/config:/configs:rw + - ../../stirling/latest/logs:/logs:rw environment: + DISABLE_ADDITIONAL_FEATURES: "true" SECURITY_ENABLELOGIN: "false" + ENDPOINTS_GROUPS_TO_REMOVE: "CLI" SYSTEM_DEFAULTLOCALE: en-US UI_APPNAME: Stirling-PDF-Ultra-lite UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Ultra-lite Latest @@ -26,4 +31,29 @@ services: METRICS_ENABLED: "true" SYSTEM_GOOGLEVISIBILITY: "true" SHOW_SURVEY: "true" + networks: + - stirling-network + + frontend: + build: + context: ../.. + dockerfile: docker/frontend/Dockerfile + container_name: stirling-pdf-frontend-ultra-lite restart: on-failure:5 + ports: + - "3000:80" + environment: + BACKEND_URL: http://backend:8080 + depends_on: + - backend + networks: + - stirling-network + +networks: + stirling-network: + driver: bridge + +volumes: + stirling-data: + stirling-config: + stirling-logs: \ No newline at end of file diff --git a/testing/test.sh b/testing/test.sh index d4adce375..0876893de 100644 --- a/testing/test.sh +++ b/testing/test.sh @@ -225,8 +225,8 @@ test_compose() { echo "Testing $compose_file configuration..." - # Start up the Docker Compose service - docker-compose -f "$compose_file" up -d + # Start up the Docker Compose service with forced rebuild + docker-compose -f "$compose_file" up -d --build # Wait for the service to become healthy if check_health "$service_name" "$compose_file"; then @@ -276,22 +276,27 @@ main() { EXPECTED_VERSION=$(get_expected_version) echo "Expected version: $EXPECTED_VERSION" - # Building Docker images - # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . - docker build --build-arg VERSION_TAG=alpha -t docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite . - # Test each configuration - run_tests "Stirling-PDF-Ultra-Lite" "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" + run_tests "Stirling-PDF-Ultra-Lite" "./testing/compose/docker-compose-ultra-lite.yml" - echo "Testing webpage accessibility..." - cd "testing" - if ./test_webpages.sh -f webpage_urls.txt -b http://localhost:8080; then - passed_tests+=("Webpage-Accessibility-lite") + echo "Testing basic frontend homepage accessibility..." + if curl -f http://localhost:3000 > /dev/null 2>&1; then + passed_tests+=("Frontend-Homepage-Accessibility-lite") + echo "Frontend homepage accessibility check passed" else - failed_tests+=("Webpage-Accessibility-lite") - echo "Webpage accessibility lite tests failed" + failed_tests+=("Frontend-Homepage-Accessibility-lite") + echo "Frontend homepage accessibility check failed" fi - cd "$PROJECT_ROOT" + + # echo "Testing webpage accessibility..." + # cd "testing" + # if ./test_webpages.sh -f webpage_urls.txt -b http://localhost:8080; then + # passed_tests+=("Webpage-Accessibility-lite") + # else + # failed_tests+=("Webpage-Accessibility-lite") + # echo "Webpage accessibility lite tests failed" + # fi + # cd "$PROJECT_ROOT" echo "Testing version verification..." if verify_app_version "Stirling-PDF-Ultra-Lite" "http://localhost:8080"; then @@ -302,10 +307,11 @@ main() { echo "Version verification failed for Stirling-PDF-Ultra-Lite" fi - docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite.yml" down - - # run_tests "Stirling-PDF" "./exampleYmlFiles/docker-compose-latest.yml" - # docker-compose -f "./exampleYmlFiles/docker-compose-latest.yml" down + docker-compose -f "./testing/compose/docker-compose-ultra-lite.yml" down + + # Clean up any generated config files + echo "Cleaning up generated config files..." + rm -rf "$PROJECT_ROOT/stirling/" 2>/dev/null || true export DISABLE_ADDITIONAL_FEATURES=false # Run the gradlew build command and check if it fails @@ -319,43 +325,44 @@ main() { EXPECTED_VERSION=$(get_expected_version) echo "Expected version with security enabled: $EXPECTED_VERSION" - # Building Docker images with security enabled - # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile . - # docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite . - docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat . - - # Test each configuration with security - # run_tests "Stirling-PDF-Ultra-Lite-Security" "./exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml" - # docker-compose -f "./exampleYmlFiles/docker-compose-latest-ultra-lite-security.yml" down - # run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml" - # docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down + run_tests "Stirling-PDF-Security" "./testing/compose/docker-compose-security.yml" - - run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/docker-compose-latest-fat-security.yml" - - echo "Testing webpage accessibility..." - cd "testing" - if ./test_webpages.sh -f webpage_urls_full.txt -b http://localhost:8080; then - passed_tests+=("Webpage-Accessibility-full") + echo "Testing basic frontend homepage accessibility..." + if curl -f http://localhost:3000 > /dev/null 2>&1; then + passed_tests+=("Frontend-Homepage-Accessibility-full") + echo "Frontend homepage accessibility check passed" else - failed_tests+=("Webpage-Accessibility-full") - echo "Webpage accessibility full tests failed" + failed_tests+=("Frontend-Homepage-Accessibility-full") + echo "Frontend homepage accessibility check failed" fi - cd "$PROJECT_ROOT" + + # echo "Testing webpage accessibility..." + # cd "testing" + # if ./test_webpages.sh -f webpage_urls_full.txt -b http://localhost:8080; then + # passed_tests+=("Webpage-Accessibility-full") + # else + # failed_tests+=("Webpage-Accessibility-full") + # echo "Webpage accessibility full tests failed" + # fi + # cd "$PROJECT_ROOT" echo "Testing version verification..." - if verify_app_version "Stirling-PDF-Security-Fat" "http://localhost:8080"; then - passed_tests+=("Stirling-PDF-Security-Fat-Version-Check") - echo "Version verification passed for Stirling-PDF-Security-Fat" + if verify_app_version "Stirling-PDF-Security" "http://localhost:8080"; then + passed_tests+=("Stirling-PDF-Security-Version-Check") + echo "Version verification passed for Stirling-PDF-Security" else - failed_tests+=("Stirling-PDF-Security-Fat-Version-Check") - echo "Version verification failed for Stirling-PDF-Security-Fat" + failed_tests+=("Stirling-PDF-Security-Version-Check") + echo "Version verification failed for Stirling-PDF-Security" fi - docker-compose -f "./exampleYmlFiles/docker-compose-latest-fat-security.yml" down + docker-compose -f "./testing/compose/docker-compose-security.yml" down + + # Clean up any generated config files + echo "Cleaning up generated config files..." + rm -rf "$PROJECT_ROOT/stirling/" 2>/dev/null || true - run_tests "Stirling-PDF-Security-Fat-with-login" "./exampleYmlFiles/test_cicd.yml" + run_tests "Stirling-PDF-Security-with-login" "./testing/compose/docker-compose-security-with-login.yml" if [ $? -eq 0 ]; then # Create directory for file snapshots if it doesn't exist @@ -368,7 +375,7 @@ main() { DIFF_FILE="$SNAPSHOT_DIR/files_diff.txt" # Define container name variable for consistency - CONTAINER_NAME="Stirling-PDF-Security-Fat-with-login" + CONTAINER_NAME="Stirling-PDF-Security-with-login" capture_file_list "$CONTAINER_NAME" "$BEFORE_FILE" @@ -409,28 +416,12 @@ main() { fi fi - docker-compose -f "./exampleYmlFiles/test_cicd.yml" down + docker-compose -f "./testing/compose/docker-compose-security-with-login.yml" down + + # Clean up any generated config files + echo "Cleaning up generated config files..." + rm -rf "$PROJECT_ROOT/stirling/" 2>/dev/null || true - run_tests "Stirling-PDF-Fat-Disable-Endpoints" "./exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml" - - echo "Testing disabled endpoints..." - if ./testing/test_disabledEndpoints.sh -f ./testing/endpoints.txt -b http://localhost:8080; then - passed_tests+=("Disabled-Endpoints") - else - failed_tests+=("Disabled-Endpoints") - echo "Disabled Endpoints tests failed" - fi - - echo "Testing version verification..." - if verify_app_version "Stirling-PDF-Fat-Disable-Endpoints" "http://localhost:8080"; then - passed_tests+=("Stirling-PDF-Fat-Disable-Endpoints-Version-Check") - echo "Version verification passed for Stirling-PDF-Fat-Disable-Endpoints" - else - failed_tests+=("Stirling-PDF-Fat-Disable-Endpoints-Version-Check") - echo "Version verification failed for Stirling-PDF-Fat-Disable-Endpoints" - fi - - docker-compose -f "./exampleYmlFiles/docker-compose-latest-fat-endpoints-disabled.yml" down # Report results echo "All tests completed in $SECONDS seconds." diff --git a/testing/test2.sh b/testing/test2.sh index b33d2df8c..c8376d288 100644 --- a/testing/test2.sh +++ b/testing/test2.sh @@ -51,29 +51,33 @@ build_and_test() { local dockerfile_name="./Dockerfile" local image_base="stirlingtools/stirling-pdf" local security_suffix="" - local docker_compose_base="./exampleYmlFiles/docker-compose-latest" + local docker_compose_base="./testing/compose/docker-compose" local compose_suffix=".yml" local service_name_base="Stirling-PDF" - if [ "$enable_security" == "true" ]; then - security_suffix="-Security" - docker_compose_base+="-security" # Append to base name for Docker Compose files with security - fi - case "$build_type" in full) - dockerfile_name="./Dockerfile" + dockerfile_name="./docker/backend/Dockerfile" + if [ "$enable_security" == "true" ]; then + compose_file="${docker_compose_base}-fat-security${compose_suffix}" + service_name="Stirling-PDF-Security-Fat" + else + compose_file="${docker_compose_base}-fat${compose_suffix}" + service_name="stirling-pdf-backend-fat" + fi ;; ultra-lite) - dockerfile_name="./Dockerfile.ultra-lite" + dockerfile_name="./docker/backend/Dockerfile.ultra-lite" + if [ "$enable_security" == "true" ]; then + compose_file="${docker_compose_base}-ultra-lite-security${compose_suffix}" + service_name="stirling-pdf-backend-ultra-lite-security" + else + compose_file="${docker_compose_base}-ultra-lite${compose_suffix}" + service_name="Stirling-PDF-Ultra-Lite" + fi ;; esac - # Dynamic image tag and service name based on build type and security - local image_tag="${image_base}:latest${build_type}${security_suffix}" - local service_name="${service_name_base}${build_type^}${security_suffix}" - local compose_file="${docker_compose_base}${build_type}${compose_suffix}" - # Gradle build with or without security echo "Running ./gradlew clean build with security=$enable_security..." ./gradlew clean build @@ -83,10 +87,6 @@ build_and_test() { exit 1 fi - # Building Docker image - echo "Building Docker image $image_tag with Dockerfile $dockerfile_name..." - docker build --build-arg VERSION_TAG=$version_tag -t $image_tag -f $dockerfile_name . - if [ "$run_compose" == "true" ]; then echo "Running Docker Compose for $build_type with security=$enable_security..." docker-compose -f "$compose_file" up -d From a35956d92763c959e993841af3b4986e944f94c3 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Wed, 16 Jul 2025 23:42:38 +0100 Subject: [PATCH 2/7] V2 --- .github/workflows/PR-Auto-Deploy-V2.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index d8df89585..fc5a0ae61 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -57,10 +57,11 @@ jobs: fi done - # If PR is targeting feature/react-overhaul and user is authorized, deploy unconditionally + # If PR is targeting V2 and user is authorized, deploy unconditionally PR_BASE_BRANCH="${{ github.event.pull_request.base.ref }}" - if [[ "$PR_BASE_BRANCH" == "feature/react-overhaul" && "$is_authorized" == "true" ]]; then - echo "✅ Deployment forced: PR targets feature/react-overhaul and author is authorized." + 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 From 33549c2d673a7307c45c6a5dadff7750c8b95849 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Thu, 17 Jul 2025 00:34:08 +0100 Subject: [PATCH 3/7] fix for deploy --- .github/workflows/PR-Auto-Deploy-V2.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index fc5a0ae61..9d0ada9ad 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -59,8 +59,7 @@ jobs: # 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 + 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 From 117d906be3e9e91f611d54f789796b44271f3794 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Thu, 17 Jul 2025 00:40:30 +0100 Subject: [PATCH 4/7] test new doc --- docker/monolith/Dockerfile | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docker/monolith/Dockerfile b/docker/monolith/Dockerfile index 1957fb8f5..f8eef9fc5 100644 --- a/docker/monolith/Dockerfile +++ b/docker/monolith/Dockerfile @@ -1,4 +1,27 @@ # Monolith Dockerfile - Frontend + Backend in same container +# Build the application +FROM gradle:8.14-jdk21 AS build + +COPY build.gradle . +COPY settings.gradle . +COPY gradlew . +COPY gradle gradle/ +COPY app/core/build.gradle core/. +COPY app/common/build.gradle common/. +COPY app/proprietary/build.gradle proprietary/. +RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 + +# Set the working directory +WORKDIR /app + +# Copy the entire project to the working directory +COPY . . + +# Build the application with DISABLE_ADDITIONAL_FEATURES=true +RUN DISABLE_ADDITIONAL_FEATURES=true \ + STIRLING_PDF_DESKTOP_UI=false \ + ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube + # Build frontend FROM node:20-alpine AS frontend-build @@ -22,8 +45,9 @@ FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be02 # Copy necessary files COPY scripts /scripts COPY pipeline /pipeline -COPY stirling-pdf/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ -COPY stirling-pdf/build/libs/*.jar app.jar +COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ +# first /app directory is for the build stage, second is for the final image +COPY --from=build /app/app/core/build/libs/*.jar app.jar # Copy built frontend files COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html @@ -71,7 +95,6 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a tini \ bash \ curl \ - qpdf \ shadow \ su-exec \ openssl \ @@ -85,11 +108,13 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a # pdftohtml poppler-utils \ # OCR MY PDF (unpaper for descew and other advanced features) + unpaper \ tesseract-ocr-data-eng \ tesseract-ocr-data-chi_sim \ tesseract-ocr-data-deu \ tesseract-ocr-data-fra \ tesseract-ocr-data-por \ + ocrmypdf \ # CV py3-opencv \ python3 \ From 0742364a03777180b2613c05cfe150cecfe85973 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:50:40 +0100 Subject: [PATCH 5/7] V2 frontend license checker (#3944) # Added scripts for checking the licenses of dependencies similar to the backend app --- .../workflows/frontend-licenses-update.yml | 217 +++++++++ frontend/package-lock.json | 422 +++++++++++++++++- frontend/package.json | 5 +- frontend/scripts/generate-licenses.js | 415 +++++++++++++++++ 4 files changed, 1042 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/frontend-licenses-update.yml create mode 100644 frontend/scripts/generate-licenses.js diff --git a/.github/workflows/frontend-licenses-update.yml b/.github/workflows/frontend-licenses-update.yml new file mode 100644 index 000000000..b6f37ce3a --- /dev/null +++ b/.github/workflows/frontend-licenses-update.yml @@ -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<> $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 }} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6c19c7632..60c2af7e4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "frontend", "version": "0.1.0", + "license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", @@ -39,6 +40,7 @@ "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", + "license-checker": "^25.0.1", "postcss": "^8.5.3", "postcss-cli": "^11.0.1", "postcss-preset-mantine": "^1.17.0", @@ -2241,8 +2243,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/agent-base": { "version": "6.0.2", @@ -2326,6 +2328,21 @@ "dequal": "^2.0.3" } }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2408,8 +2425,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -2428,8 +2445,8 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2665,8 +2682,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -2770,6 +2787,16 @@ } } }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -2833,6 +2860,16 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -3154,8 +3191,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -3274,8 +3311,8 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3388,6 +3425,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -3491,8 +3534,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3683,6 +3726,141 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/license-checker": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", + "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker": "bin/license-checker" + } + }, + "node_modules/license-checker/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/license-checker/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/license-checker/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/license-checker/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/license-checker/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -4077,8 +4255,8 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4086,6 +4264,15 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4239,6 +4426,27 @@ "node": ">=6" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4258,6 +4466,12 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -4285,12 +4499,41 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "wrappy": "1" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -4330,8 +4573,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -5078,6 +5321,46 @@ "pify": "^2.3.0" } }, + "node_modules/read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5093,6 +5376,19 @@ "node": ">= 6" } }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5348,6 +5644,15 @@ "simple-concat": "^1.0.0" } }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5366,6 +5671,66 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true + }, + "node_modules/spdx-satisfies": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", + "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", + "dev": true, + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5604,6 +5969,15 @@ "node": ">=8.0" } }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5770,6 +6144,22 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -5919,8 +6309,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", diff --git a/frontend/package.json b/frontend/package.json index 38dfbf56e..aa5251545 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,6 +2,7 @@ "name": "frontend", "version": "0.1.0", "private": true, + "license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE", "proxy": "http://localhost:8080", "dependencies": { "@emotion/react": "^11.14.0", @@ -34,7 +35,8 @@ "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "generate-licenses": "node scripts/generate-licenses.js" }, "eslintConfig": { "extends": [ @@ -58,6 +60,7 @@ "@types/react": "^19.1.4", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", + "license-checker": "^25.0.1", "postcss": "^8.5.3", "postcss-cli": "^11.0.1", "postcss-preset-mantine": "^1.17.0", diff --git a/frontend/scripts/generate-licenses.js b/frontend/scripts/generate-licenses.js new file mode 100644 index 000000000..cfd5f675a --- /dev/null +++ b/frontend/scripts/generate-licenses.js @@ -0,0 +1,415 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +/** + * Generate 3rd party licenses for frontend dependencies + * This script creates a JSON file similar to the Java backend's 3rdPartyLicenses.json + */ + +const OUTPUT_FILE = path.join(__dirname, '..', 'src', 'assets', '3rdPartyLicenses.json'); +const PACKAGE_JSON = path.join(__dirname, '..', 'package.json'); + +// Ensure the output directory exists +const outputDir = path.dirname(OUTPUT_FILE); +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +console.log('🔍 Generating frontend license report...'); + +try { + // Install license-checker if not present + try { + require.resolve('license-checker'); + } catch (e) { + console.log('📦 Installing license-checker...'); + execSync('npm install --save-dev license-checker', { stdio: 'inherit' }); + } + + // Generate license report using license-checker (more reliable) + const licenseReport = execSync('npx license-checker --production --json', { + encoding: 'utf8', + cwd: path.dirname(PACKAGE_JSON) + }); + + let licenseData; + try { + licenseData = JSON.parse(licenseReport); + } catch (parseError) { + console.error('❌ Failed to parse license data:', parseError.message); + console.error('Raw output:', licenseReport.substring(0, 500) + '...'); + process.exit(1); + } + + if (!licenseData || typeof licenseData !== 'object') { + console.error('❌ Invalid license data structure'); + process.exit(1); + } + + // Convert license-checker format to array + const licenseArray = Object.entries(licenseData).map(([key, value]) => { + let name, version; + + // Handle scoped packages like @mantine/core@1.0.0 + if (key.startsWith('@')) { + const parts = key.split('@'); + name = `@${parts[1]}`; + version = parts[2]; + } else { + // Handle regular packages like react@18.0.0 + const lastAtIndex = key.lastIndexOf('@'); + name = key.substring(0, lastAtIndex); + version = key.substring(lastAtIndex + 1); + } + + // Normalize license types for edge cases + let licenseType = value.licenses; + + // Handle missing or null licenses + if (!licenseType || licenseType === null || licenseType === undefined) { + licenseType = 'Unknown'; + } + + // Handle empty string licenses + if (licenseType === '') { + licenseType = 'Unknown'; + } + + // Handle array licenses (rare but possible) + if (Array.isArray(licenseType)) { + licenseType = licenseType.join(' AND '); + } + + // Handle object licenses (fallback) + if (typeof licenseType === 'object' && licenseType !== null) { + licenseType = 'Unknown'; + } + + return { + name: name, + version: version || value.version || 'unknown', + licenseType: licenseType, + repository: value.repository, + url: value.url, + link: value.licenseUrl + }; + }); + + // Transform to match Java backend format + const transformedData = { + dependencies: licenseArray.map(dep => { + const licenseType = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown'); + const licenseUrl = dep.link || getLicenseUrl(licenseType); + + return { + moduleName: dep.name, + moduleUrl: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`, + moduleVersion: dep.version, + moduleLicense: licenseType, + moduleLicenseUrl: licenseUrl + }; + }) + }; + + // Log summary of license types found + const licenseSummary = licenseArray.reduce((acc, dep) => { + const license = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown'); + acc[license] = (acc[license] || 0) + 1; + return acc; + }, {}); + + console.log('📊 License types found:'); + Object.entries(licenseSummary).forEach(([license, count]) => { + console.log(` ${license}: ${count} packages`); + }); + + // Log any complex or unusual license formats for debugging + const complexLicenses = licenseArray.filter(dep => + dep.licenseType && ( + dep.licenseType.includes('AND') || + dep.licenseType.includes('OR') || + dep.licenseType === 'Unknown' || + dep.licenseType.includes('SEE LICENSE') + ) + ); + + if (complexLicenses.length > 0) { + console.log('\n🔍 Complex/Edge case licenses detected:'); + complexLicenses.forEach(dep => { + console.log(` ${dep.name}@${dep.version}: "${dep.licenseType}"`); + }); + } + + // Check for potentially problematic licenses + const problematicLicenses = checkLicenseCompatibility(licenseSummary, licenseArray); + if (problematicLicenses.length > 0) { + console.log('\n⚠️ License compatibility warnings:'); + problematicLicenses.forEach(warning => { + console.log(` ${warning.message}`); + }); + + // Write license warnings to a separate file for CI/CD + const warningsFile = path.join(__dirname, '..', 'src', 'assets', 'license-warnings.json'); + fs.writeFileSync(warningsFile, JSON.stringify({ + warnings: problematicLicenses, + generated: new Date().toISOString() + }, null, 2)); + console.log(`⚠️ License warnings saved to: ${warningsFile}`); + } else { + console.log('\n✅ All licenses appear to be corporate-friendly'); + } + + // Write to file + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(transformedData, null, 4)); + + console.log(`✅ License report generated successfully!`); + console.log(`📄 Found ${transformedData.dependencies.length} dependencies`); + console.log(`💾 Saved to: ${OUTPUT_FILE}`); + +} catch (error) { + console.error('❌ Error generating license report:', error.message); + process.exit(1); +} + +/** + * Get standard license URLs for common licenses + */ +function getLicenseUrl(licenseType) { + if (!licenseType || licenseType === 'Unknown') return ''; + + const licenseUrls = { + 'MIT': 'https://opensource.org/licenses/MIT', + 'Apache-2.0': 'https://www.apache.org/licenses/LICENSE-2.0', + 'Apache License 2.0': 'https://www.apache.org/licenses/LICENSE-2.0', + 'BSD-3-Clause': 'https://opensource.org/licenses/BSD-3-Clause', + 'BSD-2-Clause': 'https://opensource.org/licenses/BSD-2-Clause', + 'BSD': 'https://opensource.org/licenses/BSD-3-Clause', + 'GPL-3.0': 'https://www.gnu.org/licenses/gpl-3.0.html', + 'GPL-2.0': 'https://www.gnu.org/licenses/gpl-2.0.html', + 'LGPL-2.1': 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html', + 'LGPL-3.0': 'https://www.gnu.org/licenses/lgpl-3.0.html', + 'ISC': 'https://opensource.org/licenses/ISC', + 'CC0-1.0': 'https://creativecommons.org/publicdomain/zero/1.0/', + 'Unlicense': 'https://unlicense.org/', + 'MPL-2.0': 'https://www.mozilla.org/en-US/MPL/2.0/', + 'WTFPL': 'http://www.wtfpl.net/', + 'Zlib': 'https://opensource.org/licenses/Zlib', + 'Artistic-2.0': 'https://opensource.org/licenses/Artistic-2.0', + 'EPL-1.0': 'https://www.eclipse.org/legal/epl-v10.html', + 'EPL-2.0': 'https://www.eclipse.org/legal/epl-2.0/', + 'CDDL-1.0': 'https://opensource.org/licenses/CDDL-1.0', + 'Ruby': 'https://www.ruby-lang.org/en/about/license.txt', + 'Python-2.0': 'https://www.python.org/download/releases/2.0/license/', + 'Public Domain': 'https://creativecommons.org/publicdomain/zero/1.0/', + 'UNLICENSED': '' + }; + + // Try exact match first + if (licenseUrls[licenseType]) { + return licenseUrls[licenseType]; + } + + // Try case-insensitive match + const lowerType = licenseType.toLowerCase(); + for (const [key, url] of Object.entries(licenseUrls)) { + if (key.toLowerCase() === lowerType) { + return url; + } + } + + // Handle complex SPDX expressions like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)" + if (licenseType.includes('AND') || licenseType.includes('OR')) { + // Extract the first license from compound expressions for URL + const match = licenseType.match(/\(?\s*([A-Za-z0-9\-\.]+)/); + if (match && licenseUrls[match[1]]) { + return licenseUrls[match[1]]; + } + } + + // For non-standard licenses, return empty string (will use package link if available) + return ''; +} + +/** + * Check for potentially problematic licenses that may not be MIT/corporate compatible + */ +function checkLicenseCompatibility(licenseSummary, licenseArray) { + const warnings = []; + + // Define problematic license patterns + const problematicLicenses = { + // Copyleft licenses + 'GPL-2.0': 'Strong copyleft license - requires derivative works to be GPL', + 'GPL-3.0': 'Strong copyleft license - requires derivative works to be GPL', + 'LGPL-2.1': 'Weak copyleft license - may require source disclosure for modifications', + 'LGPL-3.0': 'Weak copyleft license - may require source disclosure for modifications', + 'AGPL-3.0': 'Network copyleft license - requires source disclosure for network use', + 'AGPL-1.0': 'Network copyleft license - requires source disclosure for network use', + + // Other potentially problematic licenses + 'WTFPL': 'Potentially problematic license - legal uncertainty', + 'CC-BY-SA-4.0': 'ShareAlike license - requires derivative works to use same license', + 'CC-BY-SA-3.0': 'ShareAlike license - requires derivative works to use same license', + 'CC-BY-NC-4.0': 'Non-commercial license - prohibits commercial use', + 'CC-BY-NC-3.0': 'Non-commercial license - prohibits commercial use', + 'OSL-3.0': 'Copyleft license - requires derivative works to be OSL', + 'EPL-1.0': 'Weak copyleft license - may require source disclosure', + 'EPL-2.0': 'Weak copyleft license - may require source disclosure', + 'CDDL-1.0': 'Weak copyleft license - may require source disclosure', + 'CDDL-1.1': 'Weak copyleft license - may require source disclosure', + 'CPL-1.0': 'Weak copyleft license - may require source disclosure', + 'MPL-1.1': 'Weak copyleft license - may require source disclosure', + 'EUPL-1.1': 'Copyleft license - requires derivative works to be EUPL', + 'EUPL-1.2': 'Copyleft license - requires derivative works to be EUPL', + 'UNLICENSED': 'No license specified - usage rights unclear', + 'Unknown': 'License not detected - manual review required' + }; + + // Known good licenses (no warnings needed) + const goodLicenses = new Set([ + 'MIT', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD', + 'ISC', 'CC0-1.0', 'Public Domain', 'Unlicense', '0BSD', 'BlueOak-1.0.0', + 'Zlib', 'Artistic-2.0', 'Python-2.0', 'Ruby', 'MPL-2.0', 'CC-BY-4.0', + 'SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE' + ]); + + // Helper function to normalize license names for comparison + function normalizeLicense(license) { + return license + .replace(/-or-later$/, '') // Remove -or-later suffix + .replace(/\+$/, '') // Remove + suffix + .trim(); + } + + // Check each license type + Object.entries(licenseSummary).forEach(([license, count]) => { + // Skip known good licenses + if (goodLicenses.has(license)) { + return; + } + + // Check if this license only affects our own packages + const affectedPackages = licenseArray.filter(dep => { + const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType; + return depLicense === license; + }); + + const isOnlyOurPackages = affectedPackages.every(dep => + dep.name === 'frontend' || + dep.name.toLowerCase().includes('stirling-pdf') || + dep.name.toLowerCase().includes('stirling_pdf') || + dep.name.toLowerCase().includes('stirlingpdf') + ); + + if (isOnlyOurPackages && (license === 'UNLICENSED' || license.startsWith('SEE LICENSE IN'))) { + return; // Skip warnings for our own Stirling-PDF packages + } + + // Check for compound licenses like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)" + if (license.includes('AND') || license.includes('OR')) { + // For OR licenses, check if there's at least one acceptable license option + if (license.includes('OR')) { + // Extract license components from OR expression + const orComponents = license + .replace(/[()]/g, '') // Remove parentheses + .split(' OR ') + .map(component => component.trim()); + + // Check if any component is in the goodLicenses set (with normalization) + const hasGoodLicense = orComponents.some(component => { + const normalized = normalizeLicense(component); + return goodLicenses.has(component) || goodLicenses.has(normalized); + }); + + if (hasGoodLicense) { + return; // Skip warning - can use the good license option + } + } + + // For AND licenses or OR licenses with no good options, check for problematic components + const hasProblematicComponent = Object.keys(problematicLicenses).some(problematic => + license.includes(problematic) + ); + + if (hasProblematicComponent) { + const affectedPackages = licenseArray + .filter(dep => { + const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType; + return depLicense === license; + }) + .map(dep => ({ + name: dep.name, + version: dep.version, + url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}` + })); + + const licenseType = license.includes('AND') ? 'AND' : 'OR'; + const reason = licenseType === 'AND' + ? 'Compound license with AND requirement - all components must be compatible' + : 'Compound license with potentially problematic components and no good fallback options'; + + warnings.push({ + message: `📋 This PR contains ${count} package${count > 1 ? 's' : ''} with compound license "${license}" - manual review recommended`, + licenseType: license, + licenseUrl: '', + reason: reason, + packageCount: count, + affectedDependencies: affectedPackages + }); + } + return; + } + + // Check for exact matches with problematic licenses + if (problematicLicenses[license]) { + const affectedPackages = licenseArray + .filter(dep => { + const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType; + return depLicense === license; + }) + .map(dep => ({ + name: dep.name, + version: dep.version, + url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}` + })); + + const packageList = affectedPackages.map(pkg => pkg.name).slice(0, 5).join(', ') + (affectedPackages.length > 5 ? `, and ${affectedPackages.length - 5} more` : ''); + const licenseUrl = getLicenseUrl(license) || 'https://opensource.org/licenses'; + + warnings.push({ + message: `⚠️ This PR contains ${count} package${count > 1 ? 's' : ''} with license type [${license}](${licenseUrl}) - ${problematicLicenses[license]}. Affected packages: ${packageList}`, + licenseType: license, + licenseUrl: licenseUrl, + reason: problematicLicenses[license], + packageCount: count, + affectedDependencies: affectedPackages + }); + } else { + // Unknown license type - flag for manual review + const affectedPackages = licenseArray + .filter(dep => { + const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType; + return depLicense === license; + }) + .map(dep => ({ + name: dep.name, + version: dep.version, + url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}` + })); + + warnings.push({ + message: `❓ This PR contains ${count} package${count > 1 ? 's' : ''} with unknown license type "${license}" - manual review required`, + licenseType: license, + licenseUrl: '', + reason: 'Unknown license type', + packageCount: count, + affectedDependencies: affectedPackages + }); + } + }); + + return warnings; +} \ No newline at end of file From 0549c5b191ab3f54eb77b293887899e3547aada7 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:19:36 +0100 Subject: [PATCH 6/7] testing and docker replacements (#3968) This PR restructures testing scripts and Docker configurations to use centralized compose files, introduces new Docker Compose variants with integrated frontend services, and updates related CI workflows. Migrate test scripts to reference testing/compose files and streamline test flows with forced rebuilds and direct curl checks. Add ultra-lite, security, and security-with-login compose files under testing/compose, each defining both backend and frontend services. Rename and adjust frontend imports and update CI workflows to build and validate the frontend separately. --- .github/workflows/PR-Auto-Deploy-V2.yml | 214 +++++++++++++++++++-- docker/README.md | 15 +- docker/compose/docker-compose.monolith.yml | 40 ---- docker/frontend/Dockerfile | 2 +- docker/frontend/entrypoint.sh | 4 +- docker/frontend/nginx.conf | 2 +- docker/monolith/Dockerfile | 153 --------------- docker/monolith/nginx-monolith.conf | 49 ----- docker/monolith/start-monolith.sh | 20 -- frontend/README.md | 4 + 10 files changed, 203 insertions(+), 300 deletions(-) delete mode 100644 docker/compose/docker-compose.monolith.yml delete mode 100644 docker/monolith/Dockerfile delete mode 100644 docker/monolith/nginx-monolith.conf delete mode 100644 docker/monolith/start-monolith.sh diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index 9d0ada9ad..288d82aed 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -2,7 +2,7 @@ name: Auto PR V2 Deployment on: pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, closed] permissions: @@ -12,6 +12,7 @@ permissions: jobs: check-pr: + if: github.event.action != 'closed' runs-on: ubuntu-latest outputs: should_deploy: ${{ steps.check-conditions.outputs.should_deploy }} @@ -183,19 +184,8 @@ jobs: 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 JDK - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - java-version: "17" - distribution: "temurin" - - - name: Build backend - run: | - export DISABLE_ADDITIONAL_FEATURES=true - ./gradlew clean build - env: - STIRLING_PDF_DESKTOP_UI: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -212,13 +202,81 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_API }} - - name: Build and push V2 monolith image + - 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/monolith/Dockerfile + file: ./docker/frontend/Dockerfile push: true - tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-pr-${{ needs.check-pr.outputs.pr_number }} + 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 @@ -233,16 +291,17 @@ jobs: 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 monolith + # Create docker-compose for V2 with separate frontend and backend cat > docker-compose.yml << EOF version: '3.3' services: - stirling-pdf-v2: - container_name: stirling-pdf-v2-pr-${{ needs.check-pr.outputs.pr_number }} - image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-pr-${{ needs.check-pr.outputs.pr_number }} + 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: - - "${V2_PORT}:80" # Frontend port (same as regular PRs) + - "${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 @@ -258,6 +317,17 @@ jobs: 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 @@ -280,6 +350,9 @@ jobs: # 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 @@ -325,3 +398,102 @@ jobs: 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 + diff --git a/docker/README.md b/docker/README.md index e73a82072..df07e6b9e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -14,15 +14,10 @@ docker/ │ ├── Dockerfile # React/Vite frontend with nginx │ ├── nginx.conf # Nginx configuration │ └── entrypoint.sh # Dynamic backend URL setup -├── monolith/ # Single container setup -│ ├── Dockerfile # Combined frontend + backend -│ ├── nginx-monolith.conf # Nginx config for monolith -│ └── start-monolith.sh # Startup script └── compose/ # Docker Compose files ├── docker-compose.yml # Standard setup ├── docker-compose.ultra-lite.yml # Ultra-lite setup - ├── docker-compose.fat.yml # Full-featured setup - └── docker-compose.monolith.yml # Single container setup + └── docker-compose.fat.yml # Full-featured setup ``` ## Usage @@ -42,12 +37,6 @@ docker-compose -f docker/compose/docker-compose.ultra-lite.yml up --build docker-compose -f docker/compose/docker-compose.fat.yml up --build ``` -### Single Container (Monolith) - -```bash -# Single container with both frontend and backend -docker-compose -f docker/compose/docker-compose.monolith.yml up --build -``` ## Access Points @@ -57,7 +46,7 @@ docker-compose -f docker/compose/docker-compose.monolith.yml up --build ## Configuration -- **Backend URL**: Set `BACKEND_URL` environment variable for custom backend locations +- **Backend URL**: Set `VITE_API_BASE_URL` environment variable for custom backend locations - **Custom Ports**: Modify port mappings in docker-compose files - **Memory Limits**: Adjust memory limits per variant (2G ultra-lite, 4G standard, 6G fat) diff --git a/docker/compose/docker-compose.monolith.yml b/docker/compose/docker-compose.monolith.yml deleted file mode 100644 index 82d40f2bd..000000000 --- a/docker/compose/docker-compose.monolith.yml +++ /dev/null @@ -1,40 +0,0 @@ -services: - stirling-pdf-monolith: - build: - context: ../.. - dockerfile: docker/monolith/Dockerfile - container_name: stirling-pdf-monolith - restart: on-failure:5 - deploy: - resources: - limits: - memory: 4G - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:80/ && curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"] - interval: 10s - timeout: 15s - retries: 16 - ports: - - "3000:80" # Frontend access - - "8080:8080" # Direct backend access (for debugging) - volumes: - - ../../stirling/latest/data:/usr/share/tessdata:rw - - ../../stirling/latest/config:/configs:rw - - ../../stirling/latest/logs:/logs:rw - environment: - DISABLE_ADDITIONAL_FEATURES: "true" - SECURITY_ENABLELOGIN: "false" - LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID" - SYSTEM_DEFAULTLOCALE: en-US - UI_APPNAME: Stirling-PDF - UI_HOMEDESCRIPTION: Single container Stirling-PDF - UI_APPNAMENAVBAR: Stirling-PDF Monolith - SYSTEM_MAXFILESIZE: "100" - METRICS_ENABLED: "true" - SYSTEM_GOOGLEVISIBILITY: "true" - SHOW_SURVEY: "true" - -volumes: - stirling-data: - stirling-config: - stirling-logs: \ No newline at end of file diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index 7a5dc6025..a220782b0 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -32,7 +32,7 @@ RUN chmod +x /entrypoint.sh EXPOSE 80 # Environment variables for flexibility -ENV BACKEND_URL=http://backend:8080 +ENV VITE_API_BASE_URL=http://backend:8080 # Use custom entrypoint ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker/frontend/entrypoint.sh b/docker/frontend/entrypoint.sh index ca1d6cba7..a81272969 100644 --- a/docker/frontend/entrypoint.sh +++ b/docker/frontend/entrypoint.sh @@ -1,10 +1,10 @@ #!/bin/sh # Set default backend URL if not provided -BACKEND_URL=${BACKEND_URL:-"http://backend:8080"} +VITE_API_BASE_URL=${VITE_API_BASE_URL:-"http://backend:8080"} # Replace the placeholder in nginx.conf with the actual backend URL -sed -i "s|\${BACKEND_URL}|${BACKEND_URL}|g" /etc/nginx/nginx.conf +sed -i "s|\${VITE_API_BASE_URL}|${VITE_API_BASE_URL}|g" /etc/nginx/nginx.conf # Start nginx exec nginx -g "daemon off;" \ No newline at end of file diff --git a/docker/frontend/nginx.conf b/docker/frontend/nginx.conf index 400ad65f1..456d70140 100644 --- a/docker/frontend/nginx.conf +++ b/docker/frontend/nginx.conf @@ -28,7 +28,7 @@ http { # Proxy API calls to backend location /api/ { - proxy_pass ${BACKEND_URL}/api/; + proxy_pass ${VITE_API_BASE_URL}/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/docker/monolith/Dockerfile b/docker/monolith/Dockerfile deleted file mode 100644 index f8eef9fc5..000000000 --- a/docker/monolith/Dockerfile +++ /dev/null @@ -1,153 +0,0 @@ -# Monolith Dockerfile - Frontend + Backend in same container -# Build the application -FROM gradle:8.14-jdk21 AS build - -COPY build.gradle . -COPY settings.gradle . -COPY gradlew . -COPY gradle gradle/ -COPY app/core/build.gradle core/. -COPY app/common/build.gradle common/. -COPY app/proprietary/build.gradle proprietary/. -RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0 - -# Set the working directory -WORKDIR /app - -# Copy the entire project to the working directory -COPY . . - -# Build the application with DISABLE_ADDITIONAL_FEATURES=true -RUN DISABLE_ADDITIONAL_FEATURES=true \ - STIRLING_PDF_DESKTOP_UI=false \ - ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube - -# Build frontend -FROM node:20-alpine AS frontend-build - -WORKDIR /app/frontend - -# Copy frontend package files -COPY frontend/package*.json ./ - -# Install frontend dependencies -RUN npm ci - -# Copy frontend source -COPY frontend/ ./ - -# Build frontend -RUN npm run build - -# Main stage - Backend with frontend files -FROM alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 - -# Copy necessary files -COPY scripts /scripts -COPY pipeline /pipeline -COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ -# first /app directory is for the build stage, second is for the final image -COPY --from=build /app/app/core/build/libs/*.jar app.jar - -# Copy built frontend files -COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html - -ARG VERSION_TAG - -LABEL org.opencontainers.image.title="Stirling-PDF Monolith" -LABEL org.opencontainers.image.description="Single container with both frontend and backend for Stirling-PDF" -LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF" -LABEL org.opencontainers.image.licenses="MIT" -LABEL org.opencontainers.image.vendor="Stirling-Tools" -LABEL org.opencontainers.image.url="https://www.stirlingpdf.com" -LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com" -LABEL maintainer="Stirling-Tools" -LABEL org.opencontainers.image.authors="Stirling-Tools" -LABEL org.opencontainers.image.version="${VERSION_TAG}" -LABEL org.opencontainers.image.keywords="PDF, manipulation, monolith, single-container" - -# Set Environment Variables -ENV DISABLE_ADDITIONAL_FEATURES=true \ - VERSION_TAG=$VERSION_TAG \ - JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \ - JAVA_CUSTOM_OPTS="" \ - HOME=/home/stirlingpdfuser \ - PUID=1000 \ - PGID=1000 \ - UMASK=022 \ - PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ - UNO_PATH=/usr/lib/libreoffice/program \ - URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \ - PATH=$PATH:/opt/venv/bin \ - STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \ - TMPDIR=/tmp/stirling-pdf \ - TEMP=/tmp/stirling-pdf \ - TMP=/tmp/stirling-pdf - -# Install nginx and all dependencies -RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ - echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ - echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ - apk upgrade --no-cache -a && \ - apk add --no-cache \ - ca-certificates \ - tzdata \ - tini \ - bash \ - curl \ - shadow \ - su-exec \ - openssl \ - openssl-dev \ - openjdk21-jre \ - nginx \ - # Doc conversion - gcompat \ - libc6-compat \ - libreoffice \ - # pdftohtml - poppler-utils \ - # OCR MY PDF (unpaper for descew and other advanced features) - unpaper \ - tesseract-ocr-data-eng \ - tesseract-ocr-data-chi_sim \ - tesseract-ocr-data-deu \ - tesseract-ocr-data-fra \ - tesseract-ocr-data-por \ - ocrmypdf \ - # CV - py3-opencv \ - python3 \ - py3-pip \ - py3-pillow@testing \ - py3-pdf2image@testing && \ - python3 -m venv /opt/venv && \ - /opt/venv/bin/pip install --upgrade pip setuptools && \ - /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \ - ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ - ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ - ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ - mv /usr/share/tessdata /usr/share/tessdata-original && \ - mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \ - fc-cache -f -v && \ - chmod +x /scripts/* && \ - chmod +x /scripts/init.sh && \ - # User permissions - addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ - chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \ - chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ - chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/nginx/html - -# Copy nginx configuration for monolith -COPY docker/monolith/nginx-monolith.conf /etc/nginx/nginx.conf - -# Copy startup script -COPY docker/monolith/start-monolith.sh /start-monolith.sh -RUN chmod +x /start-monolith.sh - -# Expose both ports -EXPOSE 80 8080 - -# Set user and run command -ENTRYPOINT ["tini", "--"] -CMD ["/start-monolith.sh"] \ No newline at end of file diff --git a/docker/monolith/nginx-monolith.conf b/docker/monolith/nginx-monolith.conf deleted file mode 100644 index 05fa72b7d..000000000 --- a/docker/monolith/nginx-monolith.conf +++ /dev/null @@ -1,49 +0,0 @@ -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; - - server { - listen 80; - server_name _; - root /usr/share/nginx/html; - index index.html index.htm; - - # Handle client-side routing - support subpaths - location / { - try_files $uri $uri/ /index.html; - } - - # Proxy API calls to backend running on same container - location /api/ { - proxy_pass http://localhost:8080/api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - } -} \ No newline at end of file diff --git a/docker/monolith/start-monolith.sh b/docker/monolith/start-monolith.sh deleted file mode 100644 index b75f15d2d..000000000 --- a/docker/monolith/start-monolith.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# Start the Java backend in the background -echo "Starting Java backend..." -su-exec stirlingpdfuser:stirlingpdfgroup bash -c " - cd /home/stirlingpdfuser && \ - java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & - /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1 & -" - -# Wait for backend to start -echo "Waiting for backend to start..." -until curl -f http://localhost:8080/api/v1/info/status >/dev/null 2>&1; do - sleep 2 -done - -echo "Backend started, starting nginx..." - -# Start nginx in the foreground -nginx -g "daemon off;" \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 58beeaccd..115fcca84 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -2,6 +2,10 @@ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +## Docker Setup + +For Docker deployments and configuration, see the [Docker README](../docker/README.md). + ## Available Scripts In the project directory, you can run: From b8d582a1e3c109a742680655e3d40cc67a9b369b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:45:43 +0100 Subject: [PATCH 7/7] Update PR-Auto to include ethan --- .github/workflows/PR-Auto-Deploy-V2.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index 288d82aed..a8f971d53 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -47,6 +47,7 @@ jobs: "reecebrowne" "DarioGii" "ConnorYoh" + "EthanHealy01" ) # Check if author is in the authorized list