diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4c577566 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Dependencies +node_modules +**/node_modules + +# Build outputs +dist +build +*.log + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage +.nyc_output + +# Cypress +cypress/videos +cypress/screenshots + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp +temp +*.tmp diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c87e188f..faf194d5 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -133,3 +133,141 @@ jobs: if: always() run: | pkill -f node || true + + cypress-tests: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Volta + uses: volta-cli/action@v4 + + - name: Install Node.js 22 + run: | + volta install node@22 + node --version + + - name: Clean up memory (kill any existing node processes) + run: | + pkill -f node || true + + # - name: Install root dependencies + # run: npm ci + + - name: Install renderer dependencies + working-directory: src/renderer + run: npm ci + + - name: Make Environment (.env.local) + working-directory: src/renderer + run: | + echo "VITE_DOMAIN=${{ env.DOMAIN }}" > .env.local + echo "VITE_CLIENTID=${{ env.CLIENT_ID }}" >> .env.local + echo "VITE_ENDPOINT=${{ env.APP }}" >> .env.local + echo "VITE_CALLBACK=${{ env.CALLBACK }}" >> .env.local + echo "VITE_HOST=${{ env.HOST }}" >> .env.local + echo "VITE_HELP=${{ env.HELP }}" >> .env.local + echo "VITE_COMMUNITY=${{ env.COMMUNITY }}" >> .env.local + echo "VITE_OPENNOTES=${{ env.OPEN_NOTES }}" >> .env.local + echo "VITE_RESOURCES=${{ env.RESOURCES }}" >> .env.local + echo "VITE_OPENCONTENT=${{ env.OPEN_CONTENT }}" >> .env.local + echo "VITE_COURSE=${{ env.COURSE }}" >> .env.local + echo "VITE_VIDEO_TRAINING=${{ env.VIDEOS }}" >> .env.local + echo "VITE_WALK_THRU=${{ env.WALK_THRU }}" >> .env.local + echo "VITE_AKUO=${{ env.AKUO }}" >> .env.local + echo "VITE_FLAT=${{ env.FLAT }}" >> .env.local + echo "VITE_HIERARCHICAL=${{ env.HIERARCHICAL }}" >> .env.local + echo "VITE_GEN_FLAT=${{ env.GEN_FLAT }}" >> .env.local + echo "VITE_GEN_HIERARCHICAL=${{ env.GEN_HIERARCHICAL }}" >> .env.local + echo "VITE_GOOGLE_SAMPLES=${{ env.GOOGLE_SAMPLES }}" >> .env.local + echo "VITE_SNAGID=${{ env.SNAG_ID }}" >> .env.local + echo "VITE_SIZELIMIT=${{ env.SIZELIMIT }}" >> .env.local + echo "VITE_SITE_TITLE=${{ env.NAME }}" >> .env.local + + - name: Create auth0-variables.json + working-directory: src/renderer + run: | + echo '{"apiIdentifier":"${{ env.API_ID }}","auth0Domain":"${{ env.DOMAIN }}","webClientId":"${{ env.CLIENT_ID }}"}' > src/auth/auth0-variables.json + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install docker-compose + run: | + sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + docker-compose --version + + - name: Clean up disk space before tests + run: | + # Clean up system packages + sudo apt-get clean || true + sudo rm -rf /var/lib/apt/lists/* || true + + # Clean up Docker aggressively + docker system prune -af --volumes || true + docker builder prune -af || true + + # Remove cache directories to free up space + rm -rf node_modules/.cache || true + rm -rf src/renderer/node_modules/.cache || true + + - name: Check disk space + run: | + df -h + docker system df + + - name: Build and run Cypress tests in Docker + working-directory: src/renderer + env: + DOCKER_BUILDKIT: 1 + COMPOSE_DOCKER_CLI_BUILD: 1 + run: npm run cy:docker:build + + - name: Clean up Docker containers + if: always() + working-directory: src/renderer + run: npm run cy:docker:down + + - name: Clean up Docker system after tests + if: always() + run: | + docker system prune -af --volumes || true + docker builder prune -af || true + + - name: Upload Cypress screenshots on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-screenshots-${{ github.run_number }} + path: src/renderer/cypress/screenshots + retention-days: 7 + if-no-files-found: ignore + + - name: Upload Cypress videos on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-videos-${{ github.run_number }} + path: src/renderer/cypress/videos + retention-days: 7 + if-no-files-found: ignore + + - name: Upload Cypress test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: cypress-results-${{ github.run_number }} + path: | + src/renderer/cypress/reports + src/renderer/cypress/results + retention-days: 30 + if-no-files-found: ignore + + - name: Clean up processes + if: always() + run: | + pkill -f node || true diff --git a/src/renderer/.dockerignore b/src/renderer/.dockerignore new file mode 100644 index 00000000..4c577566 --- /dev/null +++ b/src/renderer/.dockerignore @@ -0,0 +1,40 @@ +# Dependencies +node_modules +**/node_modules + +# Build outputs +dist +build +*.log + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Test coverage +coverage +.nyc_output + +# Cypress +cypress/videos +cypress/screenshots + +# Environment files +.env +.env.local +.env.*.local + +# Temporary files +tmp +temp +*.tmp diff --git a/src/renderer/Dockerfile b/src/renderer/Dockerfile new file mode 100644 index 00000000..0332304d --- /dev/null +++ b/src/renderer/Dockerfile @@ -0,0 +1,39 @@ +FROM node:22.19.0 + +# Install wget for healthcheck +RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy root package files first +COPY package*.json ./ + +# Install root dependencies (use ci for clean install, ignore cache) +RUN npm ci + +# These lines appear to cause the container build to hang +# # Create a non-root user for running the application +# RUN groupadd --gid 1010 apm \ +# && useradd --uid 1010 --gid apm --shell /bin/bash --create-home apm +# # Change ownership of the app directory to the apm user +# RUN chown -R apm:apm /app +# # Switch to the non-root user +# USER apm + +# Copy renderer package files +COPY src/renderer/package*.json ./src/renderer/ + +# Install renderer dependencies (use ci for clean install, ignore cache) +RUN cd src/renderer && npm ci + +# Copy necessary config files +COPY env-config ./env-config +COPY tsconfig*.json ./ +COPY electron.vite.config.ts ./ +COPY src/renderer ./src/renderer + +# Expose Vite dev server port +EXPOSE 3000 + +# The command will be overridden by docker-compose +CMD ["npm", "start"] diff --git a/src/renderer/Dockerfile.cypress b/src/renderer/Dockerfile.cypress new file mode 100644 index 00000000..c77db0bb --- /dev/null +++ b/src/renderer/Dockerfile.cypress @@ -0,0 +1,14 @@ +FROM cypress/included:15.7.1 + +# Install curl for warming up Vite +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +WORKDIR /e2e + +# Copy package files +COPY package*.json ./ + +# Install project dependencies (including vite-dev-server, etc.) +RUN npm ci + +# The rest will be mounted via volumes diff --git a/src/renderer/docker-compose.yml b/src/renderer/docker-compose.yml new file mode 100644 index 00000000..a9782877 --- /dev/null +++ b/src/renderer/docker-compose.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + # Vite development server + app: + build: + context: ../.. + dockerfile: src/renderer/Dockerfile + ports: + - '3000:3000' + volumes: + - ../..:/app + - /app/node_modules + - /app/src/renderer/node_modules + environment: + - NODE_ENV=development + command: sh -c "set -e; cd /app && npm run stamp && npm run devs && cd /app/src/renderer && npm start" + healthcheck: + test: + [ + 'CMD', + 'wget', + '--no-verbose', + '--tries=1', + '--spider', + 'http://localhost:3000', + ] + interval: 3s + timeout: 5s + retries: 20 + start_period: 40s + networks: + - cypress-net + + # Cypress tests + cypress: + build: + context: . + dockerfile: Dockerfile.cypress + depends_on: + app: + condition: service_healthy + environment: + - CYPRESS_baseUrl=http://app:3000 + working_dir: /e2e + volumes: + - .:/e2e + - /e2e/node_modules + command: > + docker run + --component + --config-file cypress/config/local.config.ts + networks: + - cypress-net + +networks: + cypress-net: + driver: bridge diff --git a/src/renderer/package-lock.json b/src/renderer/package-lock.json index 43fc6ef1..45235ea4 100644 --- a/src/renderer/package-lock.json +++ b/src/renderer/package-lock.json @@ -120,7 +120,8 @@ "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.9.3", - "vite": "^7.0.0" + "vite": "^7.0.0", + "write": "^2.0.0" } }, "node_modules/@adobe/css-tools": { @@ -5628,6 +5629,19 @@ "node": ">=0.4.0" } }, + "node_modules/add-filename-increment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-filename-increment/-/add-filename-increment-1.0.0.tgz", + "integrity": "sha512-pFV8VZX8jxuVMIycKvGZkWF/ihnUubu9lbQVnOnZWp7noVxbKQTNj7zG2y9fXdPcuZ6lAN3Drr517HaivGCjdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-filename-increment": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -17452,6 +17466,16 @@ "node": ">=8" } }, + "node_modules/strip-filename-increment": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-filename-increment/-/strip-filename-increment-2.0.1.tgz", + "integrity": "sha512-+v5xsiTTsdYqkPj7qz1zlngIsjZedhHDi3xp/9bMurV8kXe9DAr732gNVqtt4X8sI3hOqS3nlFfps5gyVcux6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -19421,6 +19445,19 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/write": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/write/-/write-2.0.0.tgz", + "integrity": "sha512-yam9TAqN8sAZokECAejo9HpT2j2s39OgK8i8yxadrFBVo+iSWLfnipRVFulfAw1d2dz5vSuGmlMHYRKG4fysOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "add-filename-increment": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", diff --git a/src/renderer/package.json b/src/renderer/package.json index 2f747c8d..6c626ed3 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -18,6 +18,9 @@ "cy:open-ct": "cypress open --component --browser firefox --config-file cypress/config/local.config.ts", "cy:run-ct": "cypress run --component --config-file cypress/config/local.config.ts", "cy:run-ct-fast": "npm run cy:run-ct --config video=false screenshot=false", + "cy:docker": "docker-compose up --abort-on-container-exit --exit-code-from cypress", + "cy:docker:build": "docker-compose up --build --abort-on-container-exit --exit-code-from cypress", + "cy:docker:down": "docker-compose down", "devs": "cd ../.. && node env-config/changeEnv.cjs dev && cd src/renderer", "qas": "cd ../.. && node env-config/changeEnv.cjs qa && cd src/renderer", "prods": "cd ../.. && node env-config/changeEnv.cjs prod && cd src/renderer" @@ -174,7 +177,8 @@ "ts-jest": "29.2.5", "ts-node": "10.9.2", "typescript": "5.9.3", - "vite": "^7.0.0" + "vite": "^7.0.0", + "write": "^2.0.0" }, "author": { "name": "SIL Global", diff --git a/src/renderer/vite.config.ts b/src/renderer/vite.config.ts index 9537c8e7..395a7ae1 100644 --- a/src/renderer/vite.config.ts +++ b/src/renderer/vite.config.ts @@ -7,6 +7,10 @@ export default defineConfig({ port: 3000, host: true, }, + optimizeDeps: { + // Removed force: true to allow Vite to cache optimizations + // This prevents re-optimization during test runs + }, plugins: [react()], build: { rollupOptions: {