From 7d2bb77b2443be73b44d0b58952f96a0fbfababe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:46:18 +0000 Subject: [PATCH 01/15] Initial plan for issue From 949a873a9c3c6b481d6ff13a0aa7212d3ec092ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:56:10 +0000 Subject: [PATCH 02/15] Add Docker support: Dockerfile and .dockerignore for containerized deployment Co-authored-by: warengonzaga <15052701+warengonzaga@users.noreply.github.com> --- .dockerignore | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2b2a06e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,82 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build output +dist/ +build/ + +# Git +.git/ +.gitignore + +# Environment files +.env +.env.* +!.env.example + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs/ +*.log + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Coverage directory +coverage/ +*.lcov + +# Temporary folders +tmp/ +temp/ + +# Documentation +docs/ +ai_context/ +README.md +CONTRIBUTING.md +CODE_OF_CONDUCT.md +LICENSE +SECURITY.md + +# Package manager files +package-lock.json +.yarn-integrity +.yarn/ + +# TypeScript +*.tsbuildinfo + +# Cache +.cache/ +.parcel-cache/ +.eslintcache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Testing +test/ +tests/ +__tests__/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6fae4c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,59 @@ +# Multi-stage build for Unthread Webhook Server + +# Build stage +FROM node:20-alpine AS builder + +# Set working directory +WORKDIR /app + +# Copy package manager files +COPY package.json yarn.lock .yarnrc ./ + +# Install all dependencies (including devDependencies for building) +RUN yarn config set strict-ssl false && \ + yarn install --frozen-lockfile --ignore-scripts + +# Copy source code and build configuration +COPY src/ ./src/ +COPY tsconfig.json ./ + +# Build the TypeScript application +RUN yarn build + +# Production stage +FROM node:20-alpine AS production + +# Set working directory +WORKDIR /app + +# Copy package manager files +COPY package.json yarn.lock .yarnrc ./ + +# Install only production dependencies +RUN yarn config set strict-ssl false && \ + yarn install --frozen-lockfile --production --ignore-scripts && \ + yarn cache clean + +# Copy built application from builder stage +COPY --from=builder /app/dist ./dist + +# Copy environment example (for reference) +COPY .env.example ./ + +# Create non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 + +# Change ownership of the app directory to nodejs user +RUN chown -R nodejs:nodejs /app +USER nodejs + +# Expose the port the app runs on +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" + +# Start the application +CMD ["node", "dist/app.js"] \ No newline at end of file From 3762efab08d682b150cf55a525d881d97c4e3681 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 18:03:43 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E2=98=95=20chore:=20apply=20suggestions?= =?UTF-8?q?=20from=20code=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6fae4c3..7be3254 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,7 @@ WORKDIR /app COPY package.json yarn.lock .yarnrc ./ # Install all dependencies (including devDependencies for building) -RUN yarn config set strict-ssl false && \ - yarn install --frozen-lockfile --ignore-scripts +RUN yarn install --frozen-lockfile --ignore-scripts # Copy source code and build configuration COPY src/ ./src/ @@ -29,6 +28,8 @@ WORKDIR /app # Copy package manager files COPY package.json yarn.lock .yarnrc ./ +# Set environment to production +ENV NODE_ENV=production # Install only production dependencies RUN yarn config set strict-ssl false && \ yarn install --frozen-lockfile --production --ignore-scripts && \ @@ -53,7 +54,7 @@ EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })" + CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))" # Start the application CMD ["node", "dist/app.js"] \ No newline at end of file From a69df6c5f4f5be6d42273f3bd710fdda15b4e474 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 10:11:52 +0000 Subject: [PATCH 04/15] =?UTF-8?q?=E2=98=95=20chore:=20update=20version=20t?= =?UTF-8?q?o=201.0.0-beta.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3219a70..c6d0977 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unthread-webhook-server", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "A Node.js server application that receives webhook events from Unthread.io and queues them for processing.", "license": "GPL-3.0", "private": true, From 8462ad8bafbca80b3b1e22ab099ce89a1696026f Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 10:16:48 +0000 Subject: [PATCH 05/15] =?UTF-8?q?=E2=98=95=20chore:=20simplify=20yarn=20in?= =?UTF-8?q?stall?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7be3254..f13570a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,8 +31,7 @@ COPY package.json yarn.lock .yarnrc ./ # Set environment to production ENV NODE_ENV=production # Install only production dependencies -RUN yarn config set strict-ssl false && \ - yarn install --frozen-lockfile --production --ignore-scripts && \ +RUN yarn install --frozen-lockfile --production --ignore-scripts && \ yarn cache clean # Copy built application from builder stage From d5ee2f1a735af5f5f370835eb9843122bcde2149 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 22:45:20 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E2=98=95=20chore:=20update=20environment?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.docker.example | 5 +++++ .env.example | 4 ++-- .gitignore | 8 ++++++++ README.md | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .env.docker.example diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 0000000..26fe75a --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,5 @@ +NODE_ENV=production +PORT=3000 +TARGET_PLATFORM=telegram +REDIS_URL=redis://host.docker.internal:6379 +UNTHREAD_WEBHOOK_SECRET=your_docker_webhook_secret_here diff --git a/.env.example b/.env.example index 9ba41f1..c58c88d 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -NODE_ENV=development +NODE_ENV=production PORT=3000 TARGET_PLATFORM=telegram REDIS_URL=redis://localhost:6379 -UNTHREAD_WEBHOOK_SECRET=your_signing_secret_here \ No newline at end of file +UNTHREAD_WEBHOOK_SECRET=your_production_webhook_secret_here \ No newline at end of file diff --git a/.gitignore b/.gitignore index 39bc54b..a34f3af 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,11 @@ ai_context/ # Enforce Yarn usage - ignore npm lockfile package-lock.json + +# Environment files with sensitive data +.env +.env.docker +.env.test +.env.local +.env.*.local +.env.production diff --git a/README.md b/README.md index 65d2166..7fdb530 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,26 @@ Server runs on `http://localhost:3000` with endpoints: - `GET /health` - Health check - `POST /unthread-webhook` - Webhook endpoint +## 🐳 Docker Setup + +```bash +# Copy Docker template +cp .env.docker.example .env.docker +# Edit .env.docker with your secrets + +# Start Redis for Docker +docker run -d --name docker-redis -p 6379:6379 redis:7-alpine + +# Run with Docker (using cloud builder) +docker run --name docker-unthread-webhook-server --env-file .env.docker -p 3000:3000 --rm wgtechlabs/unthread-webhook-server:latest +``` + +**Environment Files:** + +- `.env` - Local development (Node.js directly) +- `.env.docker` - Docker testing (`redis://host.docker.internal:6379`) +- `.env.example` + `.env.docker.example` - Safe templates (committed to git) + ## ⚙️ Configuration ### Environment Variables From e9a698a040c046eae757d3b517fd72aedafc477f Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 22:48:57 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E2=98=95=20chore:=20reorder=20environmen?= =?UTF-8?q?t=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.docker.example | 4 ++-- .env.example | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.docker.example b/.env.docker.example index 26fe75a..3542799 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -1,5 +1,5 @@ NODE_ENV=production PORT=3000 -TARGET_PLATFORM=telegram REDIS_URL=redis://host.docker.internal:6379 -UNTHREAD_WEBHOOK_SECRET=your_docker_webhook_secret_here +TARGET_PLATFORM=telegram +UNTHREAD_WEBHOOK_SECRET=your_docker_webhook_secret_here \ No newline at end of file diff --git a/.env.example b/.env.example index c58c88d..d0bac61 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ NODE_ENV=production PORT=3000 -TARGET_PLATFORM=telegram REDIS_URL=redis://localhost:6379 +TARGET_PLATFORM=telegram UNTHREAD_WEBHOOK_SECRET=your_production_webhook_secret_here \ No newline at end of file From 997048fe7ae8b17a142410338d83ced2678717bd Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 23:08:03 +0800 Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=93=A6=20new:=20add=20docker=20comp?= =?UTF-8?q?ose=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.docker.example | 5 ----- .env.example | 6 ++++-- .gitignore | 2 -- README.md | 25 +++++++++++++++---------- docker-compose.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 19 deletions(-) delete mode 100644 .env.docker.example create mode 100644 docker-compose.yml diff --git a/.env.docker.example b/.env.docker.example deleted file mode 100644 index 3542799..0000000 --- a/.env.docker.example +++ /dev/null @@ -1,5 +0,0 @@ -NODE_ENV=production -PORT=3000 -REDIS_URL=redis://host.docker.internal:6379 -TARGET_PLATFORM=telegram -UNTHREAD_WEBHOOK_SECRET=your_docker_webhook_secret_here \ No newline at end of file diff --git a/.env.example b/.env.example index d0bac61..f014a85 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ -NODE_ENV=production +NODE_ENV=development PORT=3000 +# For local development use: redis://localhost:6379 +# For Docker Compose, this gets overridden automatically to: redis://redis:6379 REDIS_URL=redis://localhost:6379 TARGET_PLATFORM=telegram -UNTHREAD_WEBHOOK_SECRET=your_production_webhook_secret_here \ No newline at end of file +UNTHREAD_WEBHOOK_SECRET=your_webhook_secret_here \ No newline at end of file diff --git a/.gitignore b/.gitignore index a34f3af..c517f4a 100644 --- a/.gitignore +++ b/.gitignore @@ -137,8 +137,6 @@ package-lock.json # Environment files with sensitive data .env -.env.docker -.env.test .env.local .env.*.local .env.production diff --git a/README.md b/README.md index 7fdb530..1a5077f 100644 --- a/README.md +++ b/README.md @@ -53,22 +53,27 @@ Server runs on `http://localhost:3000` with endpoints: ## 🐳 Docker Setup ```bash -# Copy Docker template -cp .env.docker.example .env.docker -# Edit .env.docker with your secrets +# 1. Copy environment template +cp .env.example .env +# Edit .env with your webhook secret + +# 2. Start with Docker Compose +docker-compose up -d + +# 3. Check status +docker-compose ps -# Start Redis for Docker -docker run -d --name docker-redis -p 6379:6379 redis:7-alpine +# 4. View logs +docker-compose logs -f -# Run with Docker (using cloud builder) -docker run --name docker-unthread-webhook-server --env-file .env.docker -p 3000:3000 --rm wgtechlabs/unthread-webhook-server:latest +# 5. Stop services +docker-compose down ``` **Environment Files:** -- `.env` - Local development (Node.js directly) -- `.env.docker` - Docker testing (`redis://host.docker.internal:6379`) -- `.env.example` + `.env.docker.example` - Safe templates (committed to git) +- `.env` - Single config file for both local development and Docker +- `.env.example` - Template (Redis URL gets overridden automatically for Docker) ## ⚙️ Configuration diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4befc2b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +version: '3.8' + +services: + redis: + image: redis:7-alpine + container_name: docker-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 30s + webhook-server: + image: wgtechlabs/unthread-webhook-server:latest + container_name: docker-unthread-webhook-server + ports: + - "3000:3000" + env_file: + - .env + environment: + - REDIS_URL=redis://redis:6379 + depends_on: + redis: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + redis_data: + driver: local + +networks: + default: + name: unthread-integration-network From 64491e8c6b64cd4f6b969bb711f665b9675250a8 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sat, 21 Jun 2025 23:14:58 +0800 Subject: [PATCH 09/15] =?UTF-8?q?=E2=98=95=20chore:=20remove=20container?= =?UTF-8?q?=20name=20for=20redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4befc2b..90ec322 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,6 @@ version: '3.8' services: redis: image: redis:7-alpine - container_name: docker-redis ports: - "6379:6379" volumes: From 9e0423fc1eadc210040de3b1805b035ae5b68f15 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:01:16 +0800 Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=93=A6=20new:=20add=20build=20and?= =?UTF-8?q?=20release=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 74 ++++++++++++++++++ .github/workflows/release.yml | 135 +++++++++++++++++++++++++++++++++ .github/workflows/validate.yml | 37 +++++++++ 3 files changed, 246 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..91aa5d8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,74 @@ +name: Build + +on: + push: + branches: [dev] + +env: + REGISTRY_DOCKERHUB: wgtechlabs/unthread-webhook-server + REGISTRY_GHCR: ghcr.io/wgtechlabs/unthread-webhook-server + +jobs: + build-dev: + name: Build Development Images + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + run: | + echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + - name: Build and push development images + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64 + tags: | + ${{ env.REGISTRY_DOCKERHUB }}:dev + ${{ env.REGISTRY_DOCKERHUB }}:dev-${{ steps.meta.outputs.short_sha }} + ${{ env.REGISTRY_GHCR }}:dev + ${{ env.REGISTRY_GHCR }}:dev-${{ steps.meta.outputs.short_sha }} + labels: | + org.opencontainers.image.title=Unthread Webhook Server + org.opencontainers.image.description=Development build of Unthread Webhook Server + org.opencontainers.image.version=dev-${{ steps.meta.outputs.short_sha }} + org.opencontainers.image.created=${{ steps.meta.outputs.build_date }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Development build summary + run: | + echo "## 🔨 Development Build Complete" >> $GITHUB_STEP_SUMMARY + echo "**Images built and pushed:**" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:dev-${{ steps.meta.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:dev-${{ steps.meta.outputs.short_sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Test the dev image:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY_DOCKERHUB }}:dev" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c401fbd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,135 @@ +name: Release + +on: + release: + types: [published] + +env: + REGISTRY_DOCKERHUB: wgtechlabs/unthread-webhook-server + REGISTRY_GHCR: ghcr.io/wgtechlabs/unthread-webhook-server + +jobs: + build-production: + name: Build Production Images + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: cloud + endpoint: "wgtechlabs/unthread-bot-builder" + install: true + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version from package.json + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "major=$(echo $VERSION | cut -d. -f1)" >> $GITHUB_OUTPUT + echo "minor=$(echo $VERSION | cut -d. -f1-2)" >> $GITHUB_OUTPUT + echo "patch=$(echo $VERSION | cut -d. -f1-3)" >> $GITHUB_OUTPUT + echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + - name: Generate Docker tags + id: tags + run: | + VERSION="${{ steps.version.outputs.version }}" + MAJOR="${{ steps.version.outputs.major }}" + MINOR="${{ steps.version.outputs.minor }}" + PATCH="${{ steps.version.outputs.patch }}" + + # Docker Hub tags (no 'v' prefix) + DOCKERHUB_TAGS="${{ env.REGISTRY_DOCKERHUB }}:latest" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$VERSION" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$PATCH" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MINOR" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MAJOR" + + # GitHub Container Registry tags (with 'v' prefix) + GHCR_TAGS="${{ env.REGISTRY_GHCR }}:latest" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$VERSION" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$PATCH" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MINOR" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MAJOR" + + # Combine all tags + ALL_TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" + + echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT + + - name: Build and push production images + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.tags.outputs.tags }} + labels: | + org.opencontainers.image.title=Unthread Webhook Server + org.opencontainers.image.description=A Node.js server application that receives webhook events from Unthread.io + org.opencontainers.image.version=${{ steps.version.outputs.version }} + org.opencontainers.image.created=${{ steps.version.outputs.build_date }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.licenses=GPL-3.0 + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }} + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Production release summary + run: | + echo "## 🚀 Production Release Complete" >> $GITHUB_STEP_SUMMARY + echo "**Version:** \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Release:** \`${{ github.event.release.tag_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Docker Hub Images:**" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**GitHub Container Registry Images:**" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Deploy with:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY_DOCKERHUB }}:latest" >> $GITHUB_STEP_SUMMARY + echo "# OR" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY_GHCR }}:latest" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..d9fdcab --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,37 @@ +name: Validate + +on: + pull_request: + branches: [dev, main] + +jobs: + validate: + name: Validate Changes + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Type checking + run: yarn type-check + + - name: Build TypeScript + run: yarn build + + - name: Test Docker build (no push) + run: | + echo "Testing Docker build..." + docker build -t test-build . + echo "Build successful, cleaning up..." + docker image rm test-build + echo "✅ Docker build test completed" From 05a1ced7d632a95229e3e1537d51f1b837ae48c1 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:06:55 +0800 Subject: [PATCH 11/15] =?UTF-8?q?=E2=9C=A8=20tweak:=20update=20release=20w?= =?UTF-8?q?orkflow=20conditions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c401fbd..52d7bdc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: build-production: name: Build Production Images runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' + if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout code @@ -74,8 +74,7 @@ jobs: ALL_TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT - - - name: Build and push production images + - name: Build and push production images uses: docker/build-push-action@v5 with: context: . @@ -95,7 +94,7 @@ jobs: cache-to: type=gha,mode=max - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.28.0 with: image-ref: ${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }} format: 'sarif' From 79502f2e05e0173c77923b8dc6371c9e0e9819f9 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:15:15 +0800 Subject: [PATCH 12/15] =?UTF-8?q?=E2=9C=A8=20tweak:=20update=20build=20ste?= =?UTF-8?q?p=20position?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52d7bdc..625afd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,7 +74,8 @@ jobs: ALL_TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT - - name: Build and push production images + + - name: Build and push production images uses: docker/build-push-action@v5 with: context: . From f0d3b3995016aa0716e2a14f6285d030103a2fab Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:20:58 +0800 Subject: [PATCH 13/15] trigger: refresh GitHub Actions workflow detection From 00ff5ce74dc5e80df308819562b9853b4f1a9888 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:22:18 +0800 Subject: [PATCH 14/15] =?UTF-8?q?=E2=98=95=20chore:=20remove=20release=20w?= =?UTF-8?q?orkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 135 ---------------------------------- 1 file changed, 135 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 625afd6..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: Release - -on: - release: - types: [published] - -env: - REGISTRY_DOCKERHUB: wgtechlabs/unthread-webhook-server - REGISTRY_GHCR: ghcr.io/wgtechlabs/unthread-webhook-server - -jobs: - build-production: - name: Build Production Images - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver: cloud - endpoint: "wgtechlabs/unthread-bot-builder" - install: true - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract version from package.json - id: version - run: | - VERSION=$(node -p "require('./package.json').version") - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "major=$(echo $VERSION | cut -d. -f1)" >> $GITHUB_OUTPUT - echo "minor=$(echo $VERSION | cut -d. -f1-2)" >> $GITHUB_OUTPUT - echo "patch=$(echo $VERSION | cut -d. -f1-3)" >> $GITHUB_OUTPUT - echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - - - name: Generate Docker tags - id: tags - run: | - VERSION="${{ steps.version.outputs.version }}" - MAJOR="${{ steps.version.outputs.major }}" - MINOR="${{ steps.version.outputs.minor }}" - PATCH="${{ steps.version.outputs.patch }}" - - # Docker Hub tags (no 'v' prefix) - DOCKERHUB_TAGS="${{ env.REGISTRY_DOCKERHUB }}:latest" - DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$VERSION" - DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$PATCH" - DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MINOR" - DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MAJOR" - - # GitHub Container Registry tags (with 'v' prefix) - GHCR_TAGS="${{ env.REGISTRY_GHCR }}:latest" - GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$VERSION" - GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$PATCH" - GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MINOR" - GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MAJOR" - - # Combine all tags - ALL_TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" - - echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT - - - name: Build and push production images - uses: docker/build-push-action@v5 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.tags.outputs.tags }} - labels: | - org.opencontainers.image.title=Unthread Webhook Server - org.opencontainers.image.description=A Node.js server application that receives webhook events from Unthread.io - org.opencontainers.image.version=${{ steps.version.outputs.version }} - org.opencontainers.image.created=${{ steps.version.outputs.build_date }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.licenses=GPL-3.0 - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: ${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }} - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: 'trivy-results.sarif' - - - name: Production release summary - run: | - echo "## 🚀 Production Release Complete" >> $GITHUB_STEP_SUMMARY - echo "**Version:** \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Release:** \`${{ github.event.release.tag_name }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Docker Hub Images:**" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_DOCKERHUB }}:latest\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**GitHub Container Registry Images:**" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_GHCR }}:latest\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY - echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Deploy with:**" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY - echo "docker pull ${{ env.REGISTRY_DOCKERHUB }}:latest" >> $GITHUB_STEP_SUMMARY - echo "# OR" >> $GITHUB_STEP_SUMMARY - echo "docker pull ${{ env.REGISTRY_GHCR }}:latest" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY From dfb9c560bbb776b9b553271bfb2901f31e66b6f4 Mon Sep 17 00:00:00 2001 From: Waren Gonzaga Date: Sun, 22 Jun 2025 00:26:13 +0800 Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=93=A6=20new:=20add=20release=20wor?= =?UTF-8?q?kflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 135 ++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..625afd6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,135 @@ +name: Release + +on: + release: + types: [published] + +env: + REGISTRY_DOCKERHUB: wgtechlabs/unthread-webhook-server + REGISTRY_GHCR: ghcr.io/wgtechlabs/unthread-webhook-server + +jobs: + build-production: + name: Build Production Images + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: cloud + endpoint: "wgtechlabs/unthread-bot-builder" + install: true + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version from package.json + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "major=$(echo $VERSION | cut -d. -f1)" >> $GITHUB_OUTPUT + echo "minor=$(echo $VERSION | cut -d. -f1-2)" >> $GITHUB_OUTPUT + echo "patch=$(echo $VERSION | cut -d. -f1-3)" >> $GITHUB_OUTPUT + echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + - name: Generate Docker tags + id: tags + run: | + VERSION="${{ steps.version.outputs.version }}" + MAJOR="${{ steps.version.outputs.major }}" + MINOR="${{ steps.version.outputs.minor }}" + PATCH="${{ steps.version.outputs.patch }}" + + # Docker Hub tags (no 'v' prefix) + DOCKERHUB_TAGS="${{ env.REGISTRY_DOCKERHUB }}:latest" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$VERSION" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$PATCH" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MINOR" + DOCKERHUB_TAGS="$DOCKERHUB_TAGS,${{ env.REGISTRY_DOCKERHUB }}:$MAJOR" + + # GitHub Container Registry tags (with 'v' prefix) + GHCR_TAGS="${{ env.REGISTRY_GHCR }}:latest" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$VERSION" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$PATCH" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MINOR" + GHCR_TAGS="$GHCR_TAGS,${{ env.REGISTRY_GHCR }}:v$MAJOR" + + # Combine all tags + ALL_TAGS="$DOCKERHUB_TAGS,$GHCR_TAGS" + + echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT + + - name: Build and push production images + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.tags.outputs.tags }} + labels: | + org.opencontainers.image.title=Unthread Webhook Server + org.opencontainers.image.description=A Node.js server application that receives webhook events from Unthread.io + org.opencontainers.image.version=${{ steps.version.outputs.version }} + org.opencontainers.image.created=${{ steps.version.outputs.build_date }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.licenses=GPL-3.0 + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: ${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }} + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Production release summary + run: | + echo "## 🚀 Production Release Complete" >> $GITHUB_STEP_SUMMARY + echo "**Version:** \`${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Release:** \`${{ github.event.release.tag_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Docker Hub Images:**" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_DOCKERHUB }}:${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**GitHub Container Registry Images:**" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:latest\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.patch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.minor }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY_GHCR }}:v${{ steps.version.outputs.major }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Deploy with:**" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY_DOCKERHUB }}:latest" >> $GITHUB_STEP_SUMMARY + echo "# OR" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY_GHCR }}:latest" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY