From 37cb1825af5ee1691c4755fc6afd1e0b119aa300 Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Wed, 10 Dec 2025 13:14:34 -0800 Subject: [PATCH] deploy changes to test and production environments --- .gitea/workflows/deploy-old.yml | 488 ++++++++++++++++++ .gitea/workflows/deploy-to-prod.yml | 142 +++++ .../{deploy.yml => deploy-to-test.yml} | 123 +++-- ecosystem.config.cjs | 45 +- 4 files changed, 724 insertions(+), 74 deletions(-) create mode 100644 .gitea/workflows/deploy-old.yml create mode 100644 .gitea/workflows/deploy-to-prod.yml rename .gitea/workflows/{deploy.yml => deploy-to-test.yml} (79%) diff --git a/.gitea/workflows/deploy-old.yml b/.gitea/workflows/deploy-old.yml new file mode 100644 index 00000000..c47d4c76 --- /dev/null +++ b/.gitea/workflows/deploy-old.yml @@ -0,0 +1,488 @@ +# .gitea/workflows/deploy.yml +# +# deploy to production which is an ubuntu co-lo server with nginx + postgres +# +# note to AI - the order in this file matters - also, minor changes to this file can have big impacts and is easy to break +name: Deploy to Web Server flyer-crawler.projectium.com + +on: + push: + branches: + - main # This pipeline runs only on a push to the 'main' branch. + +jobs: + deploy-to-test: + runs-on: projectium.com # This job runs on your self-hosted Gitea runner. + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + # Fetch all history for all tags and branches + with: { fetch-depth: 0 } + + # Add this NEW STEP FOR DEBUGGING + - name: Show Git REF + run: | + echo "Gitea ref: ${{ gitea.ref }}" + echo "Gitea ref_name: ${{ gitea.ref_name }}" # often more useful (e.g., 'main' or 'my-feature-branch') + echo "Gitea ref_type: ${{ gitea.ref_type }}" # 'branch' or 'tag' + echo "Gitea SHA: ${{ gitea.sha }}" + echo "Triggering actor: ${{ gitea.actor }}" + echo "Repository: ${{ gitea.repository }}" + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' # Re-enable the cache. If this fails, we will remove it again. + cache-dependency-path: '**/package-lock.json' + + # The setup-node action with caching handles installation correctly. + # If dependencies are not found in cache, it will run 'npm ci' automatically. + # If they are found, it restores them. This is the standard, reliable way. + - name: Install Dependencies + run: npm ci # 'ci' is faster and safer for CI/CD than 'install'. + +# ----------------------------------------------------------------------- + + # --- NEW DEBUGGING STEPS --- + - name: Verify Project Structure + run: | + echo "--- Current Working Directory ---" + pwd + echo "--- Listing Root Directory ---" + ls -alF + echo "--- Listing SRC Directory ---" + ls -alF src + + - name: Lint TypeScript Code + run: npm run lint # Run the linter to check for code quality issues. + continue-on-error: true # Allows the workflow to proceed even if linting fails. + + - name: Stop Test Server Before Tests + # This is a critical step to ensure a clean test environment. + # It stops the currently running pm2 process, freeing up port 3001 so that the + # integration test suite can launch its own, fresh server instance. + # '|| true' ensures the workflow doesn't fail if the process isn't running. + run: | + pm2 stop flyer-crawler-api-test || true + pm2 stop flyer-crawler-worker-test || true + pm2 delete flyer-crawler-api-test || true + pm2 delete flyer-crawler-worker-test || true + + - name: Run All Tests and Generate Merged Coverage Report + # This single step runs both unit and integration tests, then merges their + # coverage data into a single report. It combines the environment variables + # needed for both test suites. + env: + # --- Database credentials for the test suite --- + # These are injected from Gitea secrets into the runner's environment. + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: "flyer-crawler-test" # Explicitly set for tests + + # --- Redis credentials for the test suite --- + REDIS_URL: "redis://localhost:6379" + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_TEST }} + + # --- Integration test specific variables --- + FRONTEND_URL: "http://localhost:3000" + VITE_API_BASE_URL: "http://localhost:3001/api" + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} + + # --- JWT Secret for Passport authentication in tests --- + JWT_SECRET: ${{ secrets.JWT_SECRET }} + + # --- Increase Node.js memory limit to prevent heap out of memory errors --- + # This is crucial for memory-intensive tasks like running tests and coverage. + NODE_OPTIONS: "--max-old-space-size=8192" + + run: | + # Fail-fast check to ensure secrets are configured in Gitea for testing. + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ] || [ -z "$GEMINI_API_KEY" ] || [ -z "$REDIS_PASSWORD" ] || [ -z "$JWT_SECRET" ]; then + echo "ERROR: One or more test secrets (DB_*, GEMINI_API_KEY, REDIS_PASSWORD_TEST) are not set in Gitea repository secrets." + exit 1 + fi + + # Temporarily disable secret masking to prevent the runner from garbling test output numbers. + echo "::stop-commands secret-masking::" + + # Run unit and integration tests as separate steps. + # The `|| true` ensures the workflow continues even if tests fail, allowing coverage to run. + echo "--- Running Unit Tests ---" + npm run test:unit -- --coverage --reporter=verbose --includeTaskLocation --testTimeout=20000 || true + + echo "--- Running Integration Tests ---" + npm run test:integration -- --coverage --reporter=verbose --includeTaskLocation --testTimeout=20000 || true + + # Re-enable secret masking for subsequent steps. + echo "::secret-masking::" + + continue-on-error: true # Allows the workflow to proceed even if tests fail. + + - name: Merge Coverage and Display Summary + if: always() # This step runs even if the previous test step failed. + run: | + echo "--- Merging Coverage Reports and Displaying Text Summary ---" + + # Add logging to verify that the source coverage files exist before merging. + echo "Checking for source coverage files..." + ls -l .coverage/unit/coverage-final.json + ls -l .coverage/integration/coverage-final.json + + # --- V8 Coverage Processing for Backend Server --- + # The integration tests start the server, which generates raw V8 coverage data. + # This step uses the `c8` tool to convert that raw data into a standard + # Istanbul coverage report (`coverage-final.json`) that can be merged. + echo "Processing V8 coverage data from the integration test server..." + # Create a dedicated output directory for the server's coverage report. + mkdir -p .coverage/integration-server || echo "Directory .coverage/integration-server already exists." + mkdir -p .coverage/tmp/integration-server || echo "Directory .coverage/tmp/integration-server already exists." + # Run c8: read raw files from the temp dir, and output an Istanbul JSON report. + # We only generate the 'json' report here because it's all nyc needs for merging. + echo "Server coverage report about to be generated..." + npx c8 report --reporter=json --temp-directory .coverage/tmp/integration-server --reports-dir .coverage/integration-server + echo "Server coverage report generated. Verifying existence:" + ls -l .coverage/integration-server/coverage-final.json + + # Now we have three coverage reports: + # nyc's `report` command can merge multiple coverage files automatically. + # The standard way to do this is to place all `coverage-final.json` files + # into a single directory and point `nyc report` to it. + + # Step 1: Define a directory for nyc to use as its source for merging. + # We use a path relative to the workspace to avoid issues with the runner's CWD. + NYC_SOURCE_DIR=".coverage/nyc-source-for-report" + mkdir -p "$NYC_SOURCE_DIR" || echo "Directory $NYC_SOURCE_DIR already exists." + echo "Created temporary directory for nyc reporting source: $NYC_SOURCE_DIR" + + # Step 2: Copy the individual coverage reports into the source directory. + # We give them unique names to be safe, though it's not strictly necessary. + cp .coverage/unit/coverage-final.json "$NYC_SOURCE_DIR/unit-coverage.json" + cp .coverage/integration/coverage-final.json "$NYC_SOURCE_DIR/integration-coverage.json" + # This file might not exist if integration tests fail early, so we add `|| true` + cp .coverage/integration-server/coverage-final.json "$NYC_SOURCE_DIR/integration-server-coverage.json" || echo "Server coverage file not found, skipping." + echo "Copied coverage files to source directory. Contents:" + ls -l "$NYC_SOURCE_DIR" + + # Step 3: Generate the reports directly from the source directory. + # We explicitly tell nyc where to find the source coverage files (`--temp-dir`) + # and where to output the final reports (`--report-dir`). + # This avoids the ENOENT error by preventing `nyc` from looking in a default + # cache location (`.nyc_output`) which was causing the failure. + echo "Generating reports from coverage data..." + + # Temporarily disable secret masking to prevent the runner from garbling test output numbers. + echo "::stop-commands secret-masking::" + + npx nyc report \ + --reporter=text \ + --reporter=html \ + --report-dir .coverage/ \ + --temp-dir "$NYC_SOURCE_DIR" + + # Re-enable secret masking for subsequent steps. + echo "::secret-masking::" + + echo "✅ Coverage reports generated successfully." + + continue-on-error: true # Allows the workflow to proceed even if coverage merge fails. + + - name: Clean Up Test Artifacts + if: always() # This step runs even if the previous test or coverage steps failed. + run: echo "Skipping test artifact cleanup on runner; this is handled on the server." + + - name: Deploy Coverage Report to Public URL + if: always() + run: | + TARGET_DIR="/var/www/flyer-crawler-test.projectium.com/coverage" + echo "Deploying HTML coverage report to $TARGET_DIR..." + mkdir -p "$TARGET_DIR" + rm -rf "$TARGET_DIR"/* + cp -r .coverage/* "$TARGET_DIR/" + echo "✅ Coverage report deployed to https://flyer-crawler-test.projectium.com/coverage" + + - name: Archive Code Coverage Report + # This action saves the generated HTML coverage report as a downloadable artifact. + uses: actions/upload-artifact@v3 + with: + name: code-coverage-report + path: .coverage/ + + continue-on-error: true # Allows the workflow to proceed even if tests fail. + + - name: Check for Test Database Schema Changes + env: + # Use test database credentials for this check. + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # This is used by psql + DB_NAME: ${{ secrets.DB_DATABASE_TEST }} # This is used by the application + run: | + # Fail-fast check to ensure secrets are configured in Gitea. + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more test database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_TEST) are not set in Gitea repository settings." + exit 1 + fi + + echo "--- Checking for test schema changes ---" + # Calculate the hash of the current schema file in the repository. + # We normalize line endings to ensure the hash is consistent across different OS environments. + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + echo "Current Git Schema Hash: $CURRENT_HASH" + + # Query the production database to get the hash of the deployed schema. + # The `psql` command requires PGPASSWORD to be set. + # `\t` sets tuples-only mode and `\A` unaligns output to get just the raw value. + # The `|| echo "none"` ensures the command doesn't fail if the table or row doesn't exist yet. + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A || echo "none") + echo "Deployed DB Schema Hash: $DEPLOYED_HASH" + + # Check if the hash is "none" (command failed) OR if it's an empty string (table exists but is empty). + if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then + echo "WARNING: No schema hash found in the test database." + echo "This is expected for a first-time deployment. The hash will be set after a successful deployment." + # We allow the deployment to continue, but a manual schema update is required. + # You could choose to fail here by adding `exit 1`. + elif [ "$CURRENT_HASH" != "$DEPLOYED_HASH" ]; then + echo "ERROR: Database schema mismatch detected!" + echo "The schema file in the repository has changed. A manual database migration is required." + exit 1 # Fail the deployment pipeline. + else + echo "✅ Schema is up to date. No changes detected." + fi + + # --- Frontend Deployment --- + - name: Build React Application + # We set the environment variable directly in the command line for this step. + # This maps the Gitea secret to the environment variable the application expects. + # We also generate and inject the application version, commit URL, and commit message. + run: | + # Fail-fast check for the build-time secret. + if [ -z "${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}" ]; then + echo "ERROR: The VITE_GOOGLE_GENAI_API_KEY secret is not set." + exit 1 + fi + + GITEA_SERVER_URL="https://gitea.projectium.com" # Your Gitea instance URL + COMMIT_MESSAGE=$(git log -1 --pretty=%s) + VITE_APP_VERSION="$(date +'%Y%m%d-%H%M'):$(git rev-parse --short HEAD)" \ + VITE_APP_COMMIT_URL="$GITEA_SERVER_URL/${{ gitea.repository }}/commit/${{ gitea.sha }}" \ + VITE_APP_COMMIT_MESSAGE="$COMMIT_MESSAGE" \ + VITE_API_BASE_URL="https://flyer-crawler-test.projectium.com/api" VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY_TEST }} npm run build + + - name: Deploy Application to Test Server + run: | + echo "Deploying application files to /var/www/flyer-crawler-test.projectium.com..." + APP_PATH="/var/www/flyer-crawler-test.projectium.com" + + # Ensure the destination directory exists + mkdir -p "$APP_PATH" + mkdir -p "$APP_PATH/flyer-images/icons" "$APP_PATH/flyer-images/archive" # Ensure all required subdirectories exist + + # 1. Copy the backend source code and project files first. + # CRITICAL: We exclude 'node_modules', '.git', and 'dist'. + rsync -avz --delete --exclude 'node_modules' --exclude '.git' --exclude 'dist' --exclude 'flyer-images' ./ "$APP_PATH/" + + # 2. Copy the built frontend assets into the same directory. + # This will correctly place index.html and the assets/ folder in the webroot. + rsync -avz dist/ "$APP_PATH" + echo "Application deployment complete." + + - name: Install Backend Dependencies and Restart Test Server + env: + # --- Test Secrets Injection --- + # These secrets are injected into the environment for the PM2 process. + # Your Node.js application will read these directly from `process.env`. + + # Database Credentials + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_TEST }} + + # Redis Credentials + REDIS_URL: "redis://localhost:6379" + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_TEST }} + + # Application Secrets + FRONTEND_URL: "https://flyer-crawler-test.projectium.com" + JWT_SECRET: ${{ secrets.JWT_SECRET_TEST }} + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY_TEST }} + GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + + # SMTP (email) + SMTP_HOST: "localhost" + SMTP_PORT: "1025" + SMTP_SECURE: "false" + SMTP_USER: "" # Using MailHog, no auth needed + SMTP_PASS: "" # Using MailHog, no auth needed + SMTP_FROM_EMAIL: "noreply@flyer-crawler-test.projectium.com" + + + run: | + # Fail-fast check to ensure secrets are configured in Gitea. + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more test database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_TEST) are not set in Gitea repository settings." + exit 1 + fi + + echo "Installing production dependencies and restarting test server..." + cd /var/www/flyer-crawler-test.projectium.com + npm install --omit=dev # Install only production dependencies + # Use `startOrReload` with the ecosystem file. This is the standard, idempotent way to deploy. + # It will START the process if it's not running, or RELOAD it if it is. + # We also add `&& pm2 save` to persist the process list across server reboots. + pm2 startOrReload ecosystem.config.cjs --env test && pm2 save + echo "Test backend server reloaded successfully." + + # After a successful deployment, update the schema hash in the database. + # This ensures the next deployment will compare against this new state. + echo "Updating schema hash in test database..." + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c \ + "INSERT INTO public.schema_info (id, schema_hash, deployed_at) VALUES (1, '$CURRENT_HASH', NOW()) + ON CONFLICT (id) DO UPDATE SET schema_hash = EXCLUDED.schema_hash, deployed_at = NOW();" + + # Verify the hash was updated + UPDATED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A) + if [ "$CURRENT_HASH" = "$UPDATED_HASH" ]; then + echo "✅ Schema hash successfully updated in the database to: $UPDATED_HASH" + else + echo "ERROR: Failed to update schema hash in the database." + fi + + echo "--- Cleaning up test-generated flyer assets from production directories ---" + PROD_APP_PATH="/var/www/flyer-crawler.projectium.com" + find "$PROD_APP_PATH/flyer-images" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer images to delete in prod." + find "$PROD_APP_PATH/flyer-images/icons" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer icons to delete in prod." + find "$PROD_APP_PATH/flyer-images/archive" -mindepth 1 -maxdepth 1 -type f -delete || echo "Prod archive directory is empty or not found, skipping." + echo "✅ Test artifacts cleared from production asset directories." + + - name: Show PM2 Environment for Test + run: | + echo "--- Displaying recent PM2 logs for flyer-crawler-api-test ---" + # After a reload, the server restarts. We'll show the last 20 lines of the log to see the startup messages. + sleep 5 # Wait a few seconds for the app to start and log its output. + pm2 describe flyer-crawler-api-test || echo "Could not find test pm2 process." + pm2 logs flyer-crawler-api-test --lines 20 --nostream || echo "Could not find test pm2 process." + pm2 env flyer-crawler-api-test || echo "Could not find test pm2 process." + + deploy-production: + runs-on: projectium.com + needs: deploy-to-test # This job runs only after deploy-to-test succeeds. + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: { fetch-depth: 0 } + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install Dependencies + run: npm ci + + - name: Check for Production Database Schema Changes + env: + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "--- Checking for production schema changes ---" + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + echo "Current Git Schema Hash: $CURRENT_HASH" + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A || echo "none") + echo "Deployed DB Schema Hash: $DEPLOYED_HASH" + if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then + echo "WARNING: No schema hash found in the production database. This is expected for a first-time deployment." + elif [ "$CURRENT_HASH" != "$DEPLOYED_HASH" ]; then + echo "ERROR: Database schema mismatch detected! A manual database migration is required." + exit 1 + else + echo "✅ Schema is up to date. No changes detected." + fi + + - name: Build React Application for Production + run: | + if [ -z "${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}" ]; then + echo "ERROR: The VITE_GOOGLE_GENAI_API_KEY secret is not set." + exit 1 + fi + GITEA_SERVER_URL="https://gitea.projectium.com" + COMMIT_MESSAGE=$(git log -1 --pretty=%s) + VITE_APP_VERSION="$(date +'%Y%m%d-%H%M'):$(git rev-parse --short HEAD)" \ + VITE_APP_COMMIT_URL="$GITEA_SERVER_URL/${{ gitea.repository }}/commit/${{ gitea.sha }}" \ + VITE_APP_COMMIT_MESSAGE="$COMMIT_MESSAGE" \ + VITE_API_BASE_URL=/api VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} npm run build + + - name: Deploy Application to Production Server + run: | + echo "Deploying application files to /var/www/flyer-crawler.projectium.com..." + APP_PATH="/var/www/flyer-crawler.projectium.com" + mkdir -p "$APP_PATH" + mkdir -p "$APP_PATH/flyer-images/icons" "$APP_PATH/flyer-images/archive" + rsync -avz --delete --exclude 'node_modules' --exclude '.git' --exclude 'dist' --exclude 'flyer-images' ./ "$APP_PATH/" + rsync -avz dist/ "$APP_PATH" + echo "Application deployment complete." + + - name: Install Backend Dependencies and Restart Production Server + env: + # --- Production Secrets Injection --- + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + REDIS_URL: "redis://localhost:6379" + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_PROD }} + FRONTEND_URL: "https://flyer-crawler.projectium.com" + JWT_SECRET: ${{ secrets.JWT_SECRET }} + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} + GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + SMTP_HOST: "localhost" + SMTP_PORT: "1025" + SMTP_SECURE: "false" + SMTP_USER: "" + SMTP_PASS: "" + SMTP_FROM_EMAIL: "noreply@flyer-crawler.projectium.com" + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "Installing production dependencies and restarting server..." + cd /var/www/flyer-crawler.projectium.com + npm install --omit=dev + pm2 startOrReload ecosystem.config.cjs --env production && pm2 save + echo "Production backend server reloaded successfully." + + echo "Updating schema hash in production database..." + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c \ + "INSERT INTO public.schema_info (id, schema_hash, deployed_at) VALUES (1, '$CURRENT_HASH', NOW()) + ON CONFLICT (id) DO UPDATE SET schema_hash = EXCLUDED.schema_hash, deployed_at = NOW();" + + UPDATED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A) + if [ "$CURRENT_HASH" = "$UPDATED_HASH" ]; then + echo "✅ Schema hash successfully updated in the database to: $UPDATED_HASH" + else + echo "ERROR: Failed to update schema hash in the database." + fi + + - name: Show PM2 Environment for Production + run: | + echo "--- Displaying recent PM2 logs for flyer-crawler-api ---" + sleep 5 + pm2 describe flyer-crawler-api || echo "Could not find production pm2 process." + pm2 logs flyer-crawler-api --lines 20 --nostream || echo "Could not find production pm2 process." + pm2 env flyer-crawler-api || echo "Could not find production pm2 process." \ No newline at end of file diff --git a/.gitea/workflows/deploy-to-prod.yml b/.gitea/workflows/deploy-to-prod.yml new file mode 100644 index 00000000..84635f3f --- /dev/null +++ b/.gitea/workflows/deploy-to-prod.yml @@ -0,0 +1,142 @@ +# .gitea/workflows/deploy-to-prod.yml +# +# This workflow provides a MANUAL trigger to deploy the application to the +# PRODUCTION environment. This should only be run after the 'Deploy to Test' +# workflow has succeeded on the desired commit. +name: Manual - Deploy to Production + +on: + workflow_dispatch: + inputs: + confirmation: + description: 'Type "deploy-to-prod" to confirm you want to deploy the main branch.' + required: true + default: 'do-not-run' + +jobs: + deploy-production: + runs-on: projectium.com + + steps: + - name: Verify Confirmation Phrase + run: | + if [ "${{ gitea.event.inputs.confirmation }}" != "deploy-to-prod" ]; then + echo "ERROR: Confirmation phrase did not match. Aborting deployment." + exit 1 + fi + echo "✅ Confirmation accepted. Proceeding with production deployment." + + - name: Checkout Code from 'main' branch + uses: actions/checkout@v3 + with: + ref: 'main' # Explicitly check out the main branch for production deployment + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install Dependencies + run: npm ci + + - name: Check for Production Database Schema Changes + env: + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "--- Checking for production schema changes ---" + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + echo "Current Git Schema Hash: $CURRENT_HASH" + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A || echo "none") + echo "Deployed DB Schema Hash: $DEPLOYED_HASH" + if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then + echo "WARNING: No schema hash found in the production database. This is expected for a first-time deployment." + elif [ "$CURRENT_HASH" != "$DEPLOYED_HASH" ]; then + echo "ERROR: Database schema mismatch detected! A manual database migration is required." + exit 1 + else + echo "✅ Schema is up to date. No changes detected." + fi + + - name: Build React Application for Production + run: | + if [ -z "${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}" ]; then + echo "ERROR: The VITE_GOOGLE_GENAI_API_KEY secret is not set." + exit 1 + fi + GITEA_SERVER_URL="https://gitea.projectium.com" + COMMIT_MESSAGE=$(git log -1 --pretty=%s) + VITE_APP_VERSION="$(date +'%Y%m%d-%H%M'):$(git rev-parse --short HEAD)" \ + VITE_APP_COMMIT_URL="$GITEA_SERVER_URL/${{ gitea.repository }}/commit/${{ gitea.sha }}" \ + VITE_APP_COMMIT_MESSAGE="$COMMIT_MESSAGE" \ + VITE_API_BASE_URL=/api VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} npm run build + + - name: Deploy Application to Production Server + run: | + echo "Deploying application files to /var/www/flyer-crawler.projectium.com..." + APP_PATH="/var/www/flyer-crawler.projectium.com" + mkdir -p "$APP_PATH" + mkdir -p "$APP_PATH/flyer-images/icons" "$APP_PATH/flyer-images/archive" + rsync -avz --delete --exclude 'node_modules' --exclude '.git' --exclude 'dist' --exclude 'flyer-images' ./ "$APP_PATH/" + rsync -avz dist/ "$APP_PATH" + echo "Application deployment complete." + + - name: Install Backend Dependencies and Restart Production Server + env: + # --- Production Secrets Injection --- + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + REDIS_URL: "redis://localhost:6379" + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_PROD }} + FRONTEND_URL: "https://flyer-crawler.projectium.com" + JWT_SECRET: ${{ secrets.JWT_SECRET }} + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} + GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + SMTP_HOST: "localhost" + SMTP_PORT: "1025" + SMTP_SECURE: "false" + SMTP_USER: "" + SMTP_PASS: "" + SMTP_FROM_EMAIL: "noreply@flyer-crawler.projectium.com" + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "Installing production dependencies and restarting server..." + cd /var/www/flyer-crawler.projectium.com + npm install --omit=dev + pm2 startOrReload ecosystem.config.cjs --env production && pm2 save + echo "Production backend server reloaded successfully." + + echo "Updating schema hash in production database..." + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c \ + "INSERT INTO public.schema_info (id, schema_hash, deployed_at) VALUES (1, '$CURRENT_HASH', NOW()) + ON CONFLICT (id) DO UPDATE SET schema_hash = EXCLUDED.schema_hash, deployed_at = NOW();" + + UPDATED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE id = 1;" -t -A) + if [ "$CURRENT_HASH" = "$UPDATED_HASH" ]; then + echo "✅ Schema hash successfully updated in the database to: $UPDATED_HASH" + else + echo "ERROR: Failed to update schema hash in the database." + fi + + - name: Show PM2 Environment for Production + run: | + echo "--- Displaying recent PM2 logs for flyer-crawler-api ---" + sleep 5 + pm2 describe flyer-crawler-api || echo "Could not find production pm2 process." + pm2 logs flyer-crawler-api --lines 20 --nostream || echo "Could not find production pm2 process." + pm2 env flyer-crawler-api || echo "Could not find production pm2 process." \ No newline at end of file diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy-to-test.yml similarity index 79% rename from .gitea/workflows/deploy.yml rename to .gitea/workflows/deploy-to-test.yml index 6bd55633..f38f2080 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy-to-test.yml @@ -1,9 +1,9 @@ -# .gitea/workflows/deploy.yml +# .gitea/workflows/deploy-to-test.yml # -# deploy to production which is an ubuntu co-lo server with nginx + postgres -# -# note to AI - the order in this file matters - also, minor changes to this file can have big impacts and is easy to break -name: Deploy to Web Server flyer-crawler.projectium.com +# This workflow automatically deploys the application to the TEST environment +# on every push to the 'main' branch. It handles linting, running all tests, +# and deploying the code to the test server. +name: Deploy to Test Environment on: push: @@ -11,18 +11,14 @@ on: - main # This pipeline runs only on a push to the 'main' branch. jobs: - deploy: + deploy-to-test: runs-on: projectium.com # This job runs on your self-hosted Gitea runner. - # Environment variables are used to pass secrets and configuration to the steps below. - # These must be configured as secrets in your Gitea repository settings. - env: - # Public keys needed for the React build process. - # (Empty in original, assuming secrets are injected below) - steps: - name: Checkout Code uses: actions/checkout@v3 + # Fetch all history for all tags and branches + with: { fetch-depth: 0 } # Add this NEW STEP FOR DEBUGGING - name: Show Git REF @@ -63,16 +59,16 @@ jobs: run: npm run lint # Run the linter to check for code quality issues. continue-on-error: true # Allows the workflow to proceed even if linting fails. - - name: Stop Production Server Before Tests + - name: Stop Test Server Before Tests # This is a critical step to ensure a clean test environment. # It stops the currently running pm2 process, freeing up port 3001 so that the # integration test suite can launch its own, fresh server instance. # '|| true' ensures the workflow doesn't fail if the process isn't running. run: | - pm2 stop flyer-crawler-api || true - pm2 stop flyer-crawler-worker || true - pm2 delete flyer-crawler-api || true - pm2 delete flyer-crawler-worker || true + pm2 stop flyer-crawler-api-test || true + pm2 stop flyer-crawler-worker-test || true + pm2 delete flyer-crawler-api-test || true + pm2 delete flyer-crawler-worker-test || true - name: Run All Tests and Generate Merged Coverage Report # This single step runs both unit and integration tests, then merges their @@ -195,27 +191,17 @@ jobs: - name: Clean Up Test Artifacts if: always() # This step runs even if the previous test or coverage steps failed. - run: | - APP_PATH="/var/www/flyer-crawler.projectium.com" - echo "--- Cleaning up test-generated flyer assets ---" - # Use find to delete files within the directories, but not the directories themselves. - # Target only the specific test files by name pattern. We now explicitly exclude the test report directory. - find "$APP_PATH/flyer-images" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer images to delete." - find "$APP_PATH/flyer-images/icons" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer icons to delete." - find "$APP_PATH/flyer-images/archive" -mindepth 1 -maxdepth 1 -type f -delete || echo "Archive directory is empty or not found, skipping." - echo "✅ Test artifacts cleared from asset directories." + run: echo "Skipping test artifact cleanup on runner; this is handled on the server." - name: Deploy Coverage Report to Public URL if: always() run: | - TARGET_DIR="/var/www/flyer-crawler.projectium.com/coverage" + TARGET_DIR="/var/www/flyer-crawler-test.projectium.com/coverage" echo "Deploying HTML coverage report to $TARGET_DIR..." - # Ensure the target directory exists and clear its contents before deploying. mkdir -p "$TARGET_DIR" rm -rf "$TARGET_DIR"/* - # Copy the entire contents of the generated coverage report directory. cp -r .coverage/* "$TARGET_DIR/" - echo "✅ Coverage report deployed to https://flyer-crawler.projectium.com/coverage" + echo "✅ Coverage report deployed to https://flyer-crawler-test.projectium.com/coverage" - name: Archive Code Coverage Report # This action saves the generated HTML coverage report as a downloadable artifact. @@ -226,21 +212,21 @@ jobs: continue-on-error: true # Allows the workflow to proceed even if tests fail. - - name: Check for Database Schema Changes + - name: Check for Test Database Schema Changes env: - # Use production database credentials for this check. + # Use test database credentials for this check. DB_HOST: ${{ secrets.DB_HOST }} DB_USER: ${{ secrets.DB_USER }} DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # This is used by psql - DB_NAME: ${{ secrets.DB_DATABASE_PROD }} # This is used by the application + DB_NAME: ${{ secrets.DB_DATABASE_TEST }} # This is used by the application run: | # Fail-fast check to ensure secrets are configured in Gitea. if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then - echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set in Gitea repository settings." + echo "ERROR: One or more test database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_TEST) are not set in Gitea repository settings." exit 1 fi - echo "--- Checking for schema changes ---" + echo "--- Checking for test schema changes ---" # Calculate the hash of the current schema file in the repository. # We normalize line endings to ensure the hash is consistent across different OS environments. CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') @@ -255,7 +241,7 @@ jobs: # Check if the hash is "none" (command failed) OR if it's an empty string (table exists but is empty). if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then - echo "WARNING: No schema hash found in the production database." + echo "WARNING: No schema hash found in the test database." echo "This is expected for a first-time deployment. The hash will be set after a successful deployment." # We allow the deployment to continue, but a manual schema update is required. # You could choose to fail here by adding `exit 1`. @@ -284,76 +270,76 @@ jobs: VITE_APP_VERSION="$(date +'%Y%m%d-%H%M'):$(git rev-parse --short HEAD)" \ VITE_APP_COMMIT_URL="$GITEA_SERVER_URL/${{ gitea.repository }}/commit/${{ gitea.sha }}" \ VITE_APP_COMMIT_MESSAGE="$COMMIT_MESSAGE" \ - VITE_API_BASE_URL=/api VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} npm run build + VITE_API_BASE_URL="https://flyer-crawler-test.projectium.com/api" VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY_TEST }} npm run build - - name: Deploy Application to Server + - name: Deploy Application to Test Server run: | - echo "Deploying application files to /var/www/flyer-crawler.projectium.com..." - APP_PATH="/var/www/flyer-crawler.projectium.com" + echo "Deploying application files to /var/www/flyer-crawler-test.projectium.com..." + APP_PATH="/var/www/flyer-crawler-test.projectium.com" # Ensure the destination directory exists mkdir -p "$APP_PATH" mkdir -p "$APP_PATH/flyer-images/icons" "$APP_PATH/flyer-images/archive" # Ensure all required subdirectories exist # 1. Copy the backend source code and project files first. - # CRITICAL: We exclude 'node_modules', '.git', 'dist', and 'flyer-images' to protect user content and avoid overwriting build artifacts. + # CRITICAL: We exclude 'node_modules', '.git', and 'dist'. rsync -avz --delete --exclude 'node_modules' --exclude '.git' --exclude 'dist' --exclude 'flyer-images' ./ "$APP_PATH/" # 2. Copy the built frontend assets into the same directory. # This will correctly place index.html and the assets/ folder in the webroot. - rsync -avz dist/ "/var/www/flyer-crawler.projectium.com" + rsync -avz dist/ "$APP_PATH" echo "Application deployment complete." - - name: Install Backend Dependencies and Restart Server + - name: Install Backend Dependencies and Restart Test Server env: - # --- Production Secrets Injection --- + # --- Test Secrets Injection --- # These secrets are injected into the environment for the PM2 process. # Your Node.js application will read these directly from `process.env`. # Database Credentials DB_HOST: ${{ secrets.DB_HOST }} DB_USER: ${{ secrets.DB_USER }} - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} # Used by psql - DB_NAME: ${{ secrets.DB_DATABASE_PROD }} # Standardize on the existing prod secret name + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_TEST }} # Redis Credentials - REDIS_URL: "redis://localhost:6379" # Assuming Redis is on the same server - REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_PROD }} + REDIS_URL: "redis://localhost:6379" + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_TEST }} # Application Secrets - FRONTEND_URL: "https://flyer-crawler.projectium.com" - JWT_SECRET: ${{ secrets.JWT_SECRET }} - GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} # Re-use the same secret for the server + FRONTEND_URL: "https://flyer-crawler-test.projectium.com" + JWT_SECRET: ${{ secrets.JWT_SECRET_TEST }} + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY_TEST }} GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} # SMTP (email) SMTP_HOST: "localhost" SMTP_PORT: "1025" SMTP_SECURE: "false" - SMTP_USER: "" - SMTP_PASS: "" - SMTP_FROM_EMAIL: "noreply@flyer-crawler.projectium.com" + SMTP_USER: "" # Using MailHog, no auth needed + SMTP_PASS: "" # Using MailHog, no auth needed + SMTP_FROM_EMAIL: "noreply@flyer-crawler-test.projectium.com" run: | # Fail-fast check to ensure secrets are configured in Gitea. if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then - echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME_PROD) are not set in Gitea repository settings." + echo "ERROR: One or more test database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_TEST) are not set in Gitea repository settings." exit 1 fi - echo "Installing production dependencies and restarting server..." - cd /var/www/flyer-crawler.projectium.com + echo "Installing production dependencies and restarting test server..." + cd /var/www/flyer-crawler-test.projectium.com npm install --omit=dev # Install only production dependencies # Use `startOrReload` with the ecosystem file. This is the standard, idempotent way to deploy. # It will START the process if it's not running, or RELOAD it if it is. # We also add `&& pm2 save` to persist the process list across server reboots. - pm2 startOrReload ecosystem.config.cjs --env production && pm2 save - echo "Backend server reloaded successfully." + pm2 startOrReload ecosystem.config.cjs --env test && pm2 save + echo "Test backend server reloaded successfully." # After a successful deployment, update the schema hash in the database. # This ensures the next deployment will compare against this new state. - echo "Updating schema hash in production database..." + echo "Updating schema hash in test database..." CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c \ "INSERT INTO public.schema_info (id, schema_hash, deployed_at) VALUES (1, '$CURRENT_HASH', NOW()) @@ -366,12 +352,19 @@ jobs: else echo "ERROR: Failed to update schema hash in the database." fi + + echo "--- Cleaning up test-generated flyer assets from production directories ---" + PROD_APP_PATH="/var/www/flyer-crawler.projectium.com" + find "$PROD_APP_PATH/flyer-images" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer images to delete in prod." + find "$PROD_APP_PATH/flyer-images/icons" -type f -name '*-test-flyer-image.*' -delete || echo "No test flyer icons to delete in prod." + find "$PROD_APP_PATH/flyer-images/archive" -mindepth 1 -maxdepth 1 -type f -delete || echo "Prod archive directory is empty or not found, skipping." + echo "✅ Test artifacts cleared from production asset directories." - - name: Show PM2 Environment for Production + - name: Show PM2 Environment for Test run: | - echo "--- Displaying recent PM2 logs for flyer-crawler-api ---" + echo "--- Displaying recent PM2 logs for flyer-crawler-api-test ---" # After a reload, the server restarts. We'll show the last 20 lines of the log to see the startup messages. sleep 5 # Wait a few seconds for the app to start and log its output. - pm2 describe flyer-crawler-api || echo "Could not find pm2 process." - pm2 logs flyer-crawler-api --lines 20 --nostream || echo "Could not find pm2 process." - pm2 env 0 || echo "Could not find pm2 process." \ No newline at end of file + pm2 describe flyer-crawler-api-test || echo "Could not find test pm2 process." + pm2 logs flyer-crawler-api-test --lines 20 --nostream || echo "Could not find test pm2 process." + pm2 env flyer-crawler-api-test || echo "Could not find test pm2 process." \ No newline at end of file diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 9954f1ef..d5f3327a 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -6,33 +6,60 @@ module.exports = { apps: [ { - name: 'flyer-crawler-api', // The name of our API application in PM2 + // --- API Server --- + // The name is now dynamically set based on the environment. + // This is a common pattern but requires you to call pm2 with the correct name. + // The deploy script handles this by using 'flyer-crawler-api' for prod and 'flyer-crawler-api-test' for test. + name: 'flyer-crawler-api', script: './node_modules/.bin/tsx', args: 'server.ts', // tsx will execute this file - // Explicitly set the working directory. This is crucial for reliability. - cwd: '/var/www/flyer-crawler.projectium.com', + // Production Environment Settings env_production: { NODE_ENV: 'production', // Set the Node.js environment to production + name: 'flyer-crawler-api', + cwd: '/var/www/flyer-crawler.projectium.com', + }, + // Test Environment Settings + env_test: { + NODE_ENV: 'development', // Use 'development' for test to enable more verbose logging if needed + name: 'flyer-crawler-api-test', + cwd: '/var/www/flyer-crawler-test.projectium.com', }, }, { - name: 'flyer-crawler-worker', // The name of our worker process in PM2 + // --- General Worker --- + name: 'flyer-crawler-worker', script: './node_modules/.bin/tsx', args: 'src/services/queueService.server.ts', // tsx will execute this file - // Explicitly set the working directory. - cwd: '/var/www/flyer-crawler.projectium.com', + // Production Environment Settings env_production: { NODE_ENV: 'production', + name: 'flyer-crawler-worker', + cwd: '/var/www/flyer-crawler.projectium.com', + }, + // Test Environment Settings + env_test: { + NODE_ENV: 'development', + name: 'flyer-crawler-worker-test', + cwd: '/var/www/flyer-crawler-test.projectium.com', }, }, { - name: 'flyer-crawler-analytics-worker', // A dedicated worker for analytics + // --- Analytics Worker --- + name: 'flyer-crawler-analytics-worker', script: './node_modules/.bin/tsx', - // Point to the main worker service file. It will start all workers. args: 'src/services/queueService.server.ts', // tsx will execute this file - cwd: '/var/www/flyer-crawler.projectium.com', + // Production Environment Settings env_production: { NODE_ENV: 'production', + name: 'flyer-crawler-analytics-worker', + cwd: '/var/www/flyer-crawler.projectium.com', + }, + // Test Environment Settings + env_test: { + NODE_ENV: 'development', + name: 'flyer-crawler-analytics-worker-test', + cwd: '/var/www/flyer-crawler-test.projectium.com', }, }, ],