danny-avila/LibreChat

21 workflows · maturity 50% · 5 patterns · GitHub ↗

Security 10.24/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache○ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
3.6
security scan
0
supply chain
6.7
secret handling
0
harden runner
0

Workflows (21)

a11y .github/workflows/a11y.yml
Triggers
pull_request, workflow_dispatch
Runs on
ubuntu-latest
Jobs
axe-linter
Actions
dequelabs/axe-linter-action
View raw YAML
name: Lint for accessibility issues

on:
  pull_request:
    paths:
      - 'client/src/**'
  workflow_dispatch:
    inputs:
      run_workflow:
        description: 'Set to true to run this workflow'
        required: true
        default: 'false'

jobs:
  axe-linter:
    runs-on: ubuntu-latest
    if: >
      (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'danny-avila/LibreChat') ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.run_workflow == 'true')
    
    steps:
      - uses: actions/checkout@v4
      - uses: dequelabs/axe-linter-action@v1
        with:
          api_key: ${{ secrets.AXE_LINTER_API_KEY }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
backend-review .github/workflows/backend-review.yml
Triggers
pull_request
Runs on
ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest, ubuntu-latest
Jobs
build, circular-deps, test-api, test-data-provider, test-data-schemas, test-packages-api
Commands
  • npm ci
  • npm run build:data-provider
  • npm run build:data-schemas
  • npm run build:api
  • npm ci
  • output=$(npm run build:api 2>&1) echo "$output" if echo "$output" | grep -q "Circular depend"; then echo "Error: Circular dependency detected in @librechat/api!" exit 1 fi
  • output=$(npm run rollup:api) echo "$output" if echo "$output" | grep -q "Circular dependency"; then echo "Error: Circular dependency detected!" exit 1 fi
  • npm ci
View raw YAML
name: Backend Unit Tests
on:
  pull_request:
    branches:
      - main
      - dev
      - dev-staging
      - release/*
    paths:
      - 'api/**'
      - 'packages/**'

env:
  NODE_ENV: CI
  NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'

jobs:
  build:
    name: Build packages
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Restore data-provider build cache
        id: cache-data-provider
        uses: actions/cache@v4
        with:
          path: packages/data-provider/dist
          key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }}

      - name: Build data-provider
        if: steps.cache-data-provider.outputs.cache-hit != 'true'
        run: npm run build:data-provider

      - name: Restore data-schemas build cache
        id: cache-data-schemas
        uses: actions/cache@v4
        with:
          path: packages/data-schemas/dist
          key: build-data-schemas-${{ runner.os }}-${{ hashFiles('packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/rollup.config.js', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }}

      - name: Build data-schemas
        if: steps.cache-data-schemas.outputs.cache-hit != 'true'
        run: npm run build:data-schemas

      - name: Restore api build cache
        id: cache-api
        uses: actions/cache@v4
        with:
          path: packages/api/dist
          key: build-api-${{ runner.os }}-${{ hashFiles('packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/server-rollup.config.js', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/rollup.config.js', 'packages/data-schemas/package.json') }}

      - name: Build api
        if: steps.cache-api.outputs.cache-hit != 'true'
        run: npm run build:api

      - name: Upload data-provider build
        uses: actions/upload-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist
          retention-days: 2

      - name: Upload data-schemas build
        uses: actions/upload-artifact@v4
        with:
          name: build-data-schemas
          path: packages/data-schemas/dist
          retention-days: 2

      - name: Upload api build
        uses: actions/upload-artifact@v4
        with:
          name: build-api
          path: packages/api/dist
          retention-days: 2

  circular-deps:
    name: Circular dependency checks
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download data-schemas build
        uses: actions/download-artifact@v4
        with:
          name: build-data-schemas
          path: packages/data-schemas/dist

      - name: Rebuild @librechat/api and check for circular dependencies
        run: |
          output=$(npm run build:api 2>&1)
          echo "$output"
          if echo "$output" | grep -q "Circular depend"; then
            echo "Error: Circular dependency detected in @librechat/api!"
            exit 1
          fi

      - name: Detect circular dependencies in rollup
        working-directory: ./packages/data-provider
        run: |
          output=$(npm run rollup:api)
          echo "$output"
          if echo "$output" | grep -q "Circular dependency"; then
            echo "Error: Circular dependency detected!"
            exit 1
          fi

  test-api:
    name: 'Tests: api'
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      MONGO_URI: ${{ secrets.MONGO_URI }}
      OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      JWT_SECRET: ${{ secrets.JWT_SECRET }}
      CREDS_KEY: ${{ secrets.CREDS_KEY }}
      CREDS_IV: ${{ secrets.CREDS_IV }}
      BAN_VIOLATIONS: ${{ secrets.BAN_VIOLATIONS }}
      BAN_DURATION: ${{ secrets.BAN_DURATION }}
      BAN_INTERVAL: ${{ secrets.BAN_INTERVAL }}
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download data-schemas build
        uses: actions/download-artifact@v4
        with:
          name: build-data-schemas
          path: packages/data-schemas/dist

      - name: Download api build
        uses: actions/download-artifact@v4
        with:
          name: build-api
          path: packages/api/dist

      - name: Create empty auth.json file
        run: |
          mkdir -p api/data
          echo '{}' > api/data/auth.json

      - name: Prepare .env.test file
        run: cp api/test/.env.test.example api/test/.env.test

      - name: Run unit tests
        run: cd api && npm run test:ci

  test-data-provider:
    name: 'Tests: data-provider'
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Run unit tests
        run: cd packages/data-provider && npm run test:ci

  test-data-schemas:
    name: 'Tests: data-schemas'
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download data-schemas build
        uses: actions/download-artifact@v4
        with:
          name: build-data-schemas
          path: packages/data-schemas/dist

      - name: Run unit tests
        run: cd packages/data-schemas && npm run test:ci

  test-packages-api:
    name: 'Tests: @librechat/api'
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            api/node_modules
            packages/api/node_modules
            packages/data-provider/node_modules
            packages/data-schemas/node_modules
          key: node-modules-backend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download data-schemas build
        uses: actions/download-artifact@v4
        with:
          name: build-data-schemas
          path: packages/data-schemas/dist

      - name: Download api build
        uses: actions/download-artifact@v4
        with:
          name: build-api
          path: packages/api/dist

      - name: Run unit tests
        run: cd packages/api && npm run test:ci
build .github/workflows/build.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
build-and-push
Actions
azure/login, azure/docker-login, azure/docker-login
Commands
  • docker build --build-arg RUNNER_VERSION=${{ env.RUNNER_VERSION }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} .
  • docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }}
View raw YAML
name: Linux_Container_Workflow

on:
  workflow_dispatch:

env:
  RUNNER_VERSION: 2.293.0

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      # checkout the repo
      - name: 'Checkout GitHub Action'
        uses: actions/checkout@main

      - name: 'Login via Azure CLI'
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: 'Build GitHub Runner container image'
        uses: azure/docker-login@v1
        with:
          login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}
      - run: |
          docker build --build-arg RUNNER_VERSION=${{ env.RUNNER_VERSION }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }} .

      - name: 'Push container image to ACR'
        uses: azure/docker-login@v1
        with:
          login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}
      - run: |
          docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }}
cache-integration-tests .github/workflows/cache-integration-tests.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
cache_integration_tests
Commands
  • sudo apt-get update sudo apt-get install -y redis-server redis-tools
  • redis-server --daemonize yes --port 6379 sleep 2 # Verify single Redis is running redis-cli -p 6379 ping || exit 1
  • chmod +x start-cluster.sh stop-cluster.sh ./start-cluster.sh sleep 10 # Verify cluster is running redis-cli -p 7001 cluster info || exit 1 redis-cli -p 7002 cluster info || exit 1 redis-cli -p 7003 cluster info || exit 1
  • npm ci
  • npm run build:data-provider npm run build:data-schemas npm run build:api
  • npm run test:cache-integration
  • npm run test:cache-integration
  • ./stop-cluster.sh || true
View raw YAML
name: Cache Integration Tests

on:
  pull_request:
    branches:
      - main
      - dev
      - dev-staging
      - release/*
    paths:
      - 'packages/api/src/cache/**'
      - 'packages/api/src/cluster/**'
      - 'packages/api/src/mcp/**'
      - 'packages/api/src/stream/**'
      - 'redis-config/**'
      - '.github/workflows/cache-integration-tests.yml'

jobs:
  cache_integration_tests:
    name: Integration Tests that use actual Redis Cache
    timeout-minutes: 30
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Use Node.js 20.x
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install Redis tools
        run: |
          sudo apt-get update
          sudo apt-get install -y redis-server redis-tools

      - name: Start Single Redis Instance
        run: |
          redis-server --daemonize yes --port 6379
          sleep 2
          # Verify single Redis is running
          redis-cli -p 6379 ping || exit 1

      - name: Start Redis Cluster
        working-directory: redis-config
        run: |
          chmod +x start-cluster.sh stop-cluster.sh
          ./start-cluster.sh
          sleep 10
          # Verify cluster is running
          redis-cli -p 7001 cluster info || exit 1
          redis-cli -p 7002 cluster info || exit 1
          redis-cli -p 7003 cluster info || exit 1

      - name: Install dependencies
        run: npm ci

      - name: Build packages
        run: |
          npm run build:data-provider
          npm run build:data-schemas
          npm run build:api

      - name: Run all cache integration tests (Single Redis Node)
        working-directory: packages/api
        env:
          NODE_ENV: test
          USE_REDIS: true
          USE_REDIS_CLUSTER: false
          REDIS_URI: redis://127.0.0.1:6379
        run: npm run test:cache-integration

      - name: Run all cache integration tests (Redis Cluster)
        working-directory: packages/api
        env:
          NODE_ENV: test
          USE_REDIS: true
          USE_REDIS_CLUSTER: true
          REDIS_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
        run: npm run test:cache-integration

      - name: Stop Redis Cluster
        if: always()
        working-directory: redis-config
        run: ./stop-cluster.sh || true

      - name: Stop Single Redis Instance
        if: always()
        run: redis-cli -p 6379 shutdown || true
client perms .github/workflows/client.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
build-and-publish
Commands
  • npm install -g npm@latest
  • cd packages/client && npm ci
  • cd packages/client && npm run build
  • PACKAGE_VERSION=$(node -p "require('./package.json').version") PUBLISHED_VERSION=$(npm view @librechat/client version 2>/dev/null || echo "0.0.0") if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then echo "No version change, skipping publish" echo "skip=true" >> $GITHUB_OUTPUT else echo "Version changed, proceeding with publish" echo "skip=false" >> $GITHUB_OUTPUT fi
  • npm pack
  • npm publish *.tgz --access public --provenance
View raw YAML
name: Publish `@librechat/client` to NPM

on:
  push:
    branches:
      - main
    paths:
      - 'packages/client/package.json'
  workflow_dispatch:
    inputs:
      reason:
        description: 'Reason for manual trigger'
        required: false
        default: 'Manual publish requested'

permissions:
  id-token: write  # Required for OIDC trusted publishing
  contents: read

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    environment: publish  # Must match npm trusted publisher config
    steps:
      - uses: actions/checkout@v4
      
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Update npm for OIDC support
        run: npm install -g npm@latest  # Must be 11.5.1+ for provenance
        
      - name: Install client dependencies
        run: cd packages/client && npm ci
        
      - name: Build client
        run: cd packages/client && npm run build
        
      - name: Check version change
        id: check
        working-directory: packages/client
        run: |
          PACKAGE_VERSION=$(node -p "require('./package.json').version")
          PUBLISHED_VERSION=$(npm view @librechat/client version 2>/dev/null || echo "0.0.0")
          if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then
            echo "No version change, skipping publish"
            echo "skip=true" >> $GITHUB_OUTPUT
          else
            echo "Version changed, proceeding with publish"
            echo "skip=false" >> $GITHUB_OUTPUT
          fi
            
      - name: Pack package
        if: steps.check.outputs.skip != 'true'
        working-directory: packages/client
        run: npm pack
        
      - name: Publish
        if: steps.check.outputs.skip != 'true'
        working-directory: packages/client
        run: npm publish *.tgz --access public --provenance
data-provider perms .github/workflows/data-provider.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
build, publish-npm
Commands
  • cd packages/data-provider && npm ci
  • cd packages/data-provider && npm run build
  • npm install -g npm@latest
  • cd packages/data-provider && npm ci
  • cd packages/data-provider && npm run build
  • cd packages/data-provider && npm publish --provenance
View raw YAML
name: Publish `librechat-data-provider` to NPM

on:
  push:
    branches:
      - main
    paths:
      - 'packages/data-provider/package.json'
  workflow_dispatch:
    inputs:
      reason:
        description: 'Reason for manual trigger'
        required: false
        default: 'Manual publish requested'

permissions:
  id-token: write  # Required for OIDC trusted publishing
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: cd packages/data-provider && npm ci
      - run: cd packages/data-provider && npm run build

  publish-npm:
    needs: build
    runs-on: ubuntu-latest
    environment: publish  # Must match npm trusted publisher config
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'
      
      - name: Update npm for OIDC support
        run: npm install -g npm@latest  # Must be 11.5.1+ for provenance
      
      - run: cd packages/data-provider && npm ci
      - run: cd packages/data-provider && npm run build
      - run: cd packages/data-provider && npm publish --provenance
data-schemas perms .github/workflows/data-schemas.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest
Jobs
build-and-publish
Commands
  • npm install -g npm@latest
  • cd packages/data-schemas && npm ci
  • cd packages/data-schemas && npm run build
  • PACKAGE_VERSION=$(node -p "require('./package.json').version") PUBLISHED_VERSION=$(npm view @librechat/data-schemas version 2>/dev/null || echo "0.0.0") if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then echo "No version change, skipping publish" echo "skip=true" >> $GITHUB_OUTPUT else echo "Version changed, proceeding with publish" echo "skip=false" >> $GITHUB_OUTPUT fi
  • npm pack
  • npm publish *.tgz --access public --provenance
View raw YAML
name: Publish `@librechat/data-schemas` to NPM

on:
  push:
    branches:
      - main
    paths:
      - 'packages/data-schemas/package.json'
  workflow_dispatch:
    inputs:
      reason:
        description: 'Reason for manual trigger'
        required: false
        default: 'Manual publish requested'

permissions:
  id-token: write  # Required for OIDC trusted publishing
  contents: read

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    environment: publish  # Must match npm trusted publisher config
    steps:
      - uses: actions/checkout@v4
      
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Update npm for OIDC support
        run: npm install -g npm@latest  # Must be 11.5.1+ for provenance
          
      - name: Install dependencies
        run: cd packages/data-schemas && npm ci
        
      - name: Build
        run: cd packages/data-schemas && npm run build
        
      - name: Check version change
        id: check
        working-directory: packages/data-schemas
        run: |
          PACKAGE_VERSION=$(node -p "require('./package.json').version")
          PUBLISHED_VERSION=$(npm view @librechat/data-schemas version 2>/dev/null || echo "0.0.0")
          if [ "$PACKAGE_VERSION" = "$PUBLISHED_VERSION" ]; then
            echo "No version change, skipping publish"
            echo "skip=true" >> $GITHUB_OUTPUT
          else
            echo "Version changed, proceeding with publish"
            echo "skip=false" >> $GITHUB_OUTPUT
          fi
            
      - name: Pack package
        if: steps.check.outputs.skip != 'true'
        working-directory: packages/data-schemas
        run: npm pack
        
      - name: Publish
        if: steps.check.outputs.skip != 'true'
        working-directory: packages/data-schemas
        run: npm publish *.tgz --access public --provenance
deploy .github/workflows/deploy.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
deploy-gh-runner-aci
Actions
azure/login, azure/aci-deploy
View raw YAML
name: Deploy_GHRunner_Linux_ACI

on:
  workflow_dispatch:

env:
  RUNNER_VERSION: 2.293.0
  ACI_RESOURCE_GROUP: 'Demo-ACI-GitHub-Runners-RG'
  ACI_NAME: 'gh-runner-linux-01'
  DNS_NAME_LABEL: 'gh-lin-01'
  GH_OWNER: ${{ github.repository_owner }}
  GH_REPOSITORY: 'LibreChat' #Change here to deploy self hosted runner ACI to another repo.

jobs:
  deploy-gh-runner-aci:
    runs-on: ubuntu-latest
    steps:
      # checkout the repo
      - name: 'Checkout GitHub Action'
        uses: actions/checkout@v4

      - name: 'Login via Azure CLI'
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: 'Deploy to Azure Container Instances'
        uses: 'azure/aci-deploy@v1'
        with:
          resource-group: ${{ env.ACI_RESOURCE_GROUP }}
          image: ${{ secrets.REGISTRY_LOGIN_SERVER }}/pwd9000-github-runner-lin:${{ env.RUNNER_VERSION }}
          registry-login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
          registry-username: ${{ secrets.REGISTRY_USERNAME }}
          registry-password: ${{ secrets.REGISTRY_PASSWORD }}
          name: ${{ env.ACI_NAME }}
          dns-name-label: ${{ env.DNS_NAME_LABEL }}
          environment-variables: GH_TOKEN=${{ secrets.PAT_TOKEN }} GH_OWNER=${{ env.GH_OWNER }} GH_REPOSITORY=${{ env.GH_REPOSITORY }}
          location: 'eastus'
deploy-dev .github/workflows/deploy-dev.yml
Triggers
workflow_run, workflow_dispatch
Runs on
ubuntu-latest
Jobs
deploy
Actions
shimataro/ssh-key-action
Commands
  • ssh -o StrictHostKeyChecking=no ${DO_USER}@${DO_HOST} << EOF sudo -i -u danny bash << 'EEOF' cd ~/LibreChat && \ git fetch origin main && \ sudo npm run stop:deployed && \ sudo docker images --format "{{.Repository}}:{{.ID}}" | grep -E "lc-dev|librechat" | cut -d: -f2 | xargs -r sudo docker rmi -f || true && \ sudo npm run update:deployed && \ git checkout dev && \ git pull origin dev && \ git checkout do-deploy && \ git rebase dev && \ sudo npm run start:deployed && \ echo "Update completed. Application should be running now." EEOF EOF
View raw YAML
name: Update Test Server

on:
  workflow_run:
    workflows: ["Docker Dev Branch Images Build"]
    types:
      - completed
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: |
      github.repository == 'danny-avila/LibreChat' &&
      (github.event_name == 'workflow_dispatch' || 
       (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'dev'))
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Install SSH Key
      uses: shimataro/ssh-key-action@v2
      with:
        key: ${{ secrets.DO_SSH_PRIVATE_KEY }}
        known_hosts: ${{ secrets.DO_KNOWN_HOSTS }}

    - name: Run update script on DigitalOcean Droplet
      env:
        DO_HOST: ${{ secrets.DO_HOST }}
        DO_USER: ${{ secrets.DO_USER }}
      run: |
        ssh -o StrictHostKeyChecking=no ${DO_USER}@${DO_HOST} << EOF
        sudo -i -u danny bash << 'EEOF'
        cd ~/LibreChat && \
        git fetch origin main && \
        sudo npm run stop:deployed && \
        sudo docker images --format "{{.Repository}}:{{.ID}}" | grep -E "lc-dev|librechat" | cut -d: -f2 | xargs -r sudo docker rmi -f || true && \
        sudo npm run update:deployed && \
        git checkout dev && \
        git pull origin dev && \
        git checkout do-deploy && \
        git rebase dev && \
        sudo npm run start:deployed && \
        echo "Update completed. Application should be running now."
        EEOF
        EOF
dev-branch-images matrix .github/workflows/dev-branch-images.yml
Triggers
workflow_dispatch, push
Runs on
ubuntu-latest
Jobs
build
Matrix
include, include.file, include.image_name, include.target→ Dockerfile, Dockerfile.multi, api-build, lc-dev, lc-dev-api, node
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action
Commands
  • cp .env.example .env
View raw YAML
name: Docker Dev Branch Images Build

on:
  workflow_dispatch:
  push:
    branches:
      - dev
    paths:
      - 'api/**'
      - 'client/**'
      - 'packages/**'

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - target: api-build
            file: Dockerfile.multi
            image_name: lc-dev-api
          - target: node
            file: Dockerfile
            image_name: lc-dev

    steps:
      # Check out the repository
      - name: Checkout
        uses: actions/checkout@v4

      # Set up QEMU
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Login to Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Prepare the environment
      - name: Prepare environment
        run: |
          cp .env.example .env

      # Build and push Docker images for each target
      - name: Build and push Docker images
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.file }}
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.sha }}
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
          platforms: linux/amd64,linux/arm64
          target: ${{ matrix.target }} 
dev-images matrix .github/workflows/dev-images.yml
Triggers
workflow_dispatch, push
Runs on
ubuntu-latest
Jobs
build
Matrix
include, include.file, include.image_name, include.target→ Dockerfile, Dockerfile.multi, api-build, librechat-dev, librechat-dev-api, node
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action
Commands
  • cp .env.example .env
View raw YAML
name: Docker Dev Images Build

on:
  workflow_dispatch:
  push:
    branches:
      - main
    paths:
      - 'api/**'
      - 'client/**'
      - 'packages/**'

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - target: api-build
            file: Dockerfile.multi
            image_name: librechat-dev-api
          - target: node
            file: Dockerfile
            image_name: librechat-dev

    steps:
      # Check out the repository
      - name: Checkout
        uses: actions/checkout@v4

      # Set up QEMU
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Login to Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Prepare the environment
      - name: Prepare environment
        run: |
          cp .env.example .env

      # Build and push Docker images for each target
      - name: Build and push Docker images
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.file }}
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.sha }}
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
          platforms: linux/amd64,linux/arm64
          target: ${{ matrix.target }}
dev-staging-images matrix .github/workflows/dev-staging-images.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
build
Matrix
include, include.file, include.image_name, include.target→ Dockerfile, Dockerfile.multi, api-build, lc-dev-staging, lc-dev-staging-api, node
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action
Commands
  • cp .env.example .env
View raw YAML
name: Docker Dev Staging Images Build

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - target: api-build
            file: Dockerfile.multi
            image_name: lc-dev-staging-api
          - target: node
            file: Dockerfile
            image_name: lc-dev-staging

    steps:
      # Check out the repository
      - name: Checkout
        uses: actions/checkout@v4

      # Set up QEMU
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Login to Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Prepare the environment
      - name: Prepare environment
        run: |
          cp .env.example .env

      # Build and push Docker images for each target
      - name: Build and push Docker images
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.file }}
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.sha }}
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.sha }}
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
          platforms: linux/amd64,linux/arm64
          target: ${{ matrix.target }}

eslint-ci .github/workflows/eslint-ci.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
eslint_checks
Commands
  • npm ci
  • # Extract the base commit SHA from the pull_request event payload. BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH") echo "Base commit SHA: $BASE_SHA" # Get changed files (only JS/TS files in api/ or client/) CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "$BASE_SHA" HEAD | grep -E '^(api|client)/.*\.(js|jsx|ts|tsx)$' || true) # Debug output echo "Changed files:" echo "$CHANGED_FILES" # Ensure there are files to lint before running ESLint if [[ -z "$CHANGED_FILES" ]]; then echo "No matching files changed. Skipping ESLint." exit 0 fi # Run ESLint npx eslint --no-error-on-unmatched-pattern \ --config eslint.config.mjs \ $CHANGED_FILES
View raw YAML
name: ESLint Code Quality Checks

on:
  pull_request:
    branches:
      - main
      - dev
      - dev-staging
      - release/*
    paths:
      - 'api/**'
      - 'client/**'

jobs:
  eslint_checks:
    name: Run ESLint Linting
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
      actions: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Node.js 20.x
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      # Run ESLint on changed files within the api/ and client/ directories.
      - name: Run ESLint on changed files
        run: |
          # Extract the base commit SHA from the pull_request event payload.
          BASE_SHA=$(jq --raw-output .pull_request.base.sha "$GITHUB_EVENT_PATH")
          echo "Base commit SHA: $BASE_SHA"

          # Get changed files (only JS/TS files in api/ or client/)
          CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "$BASE_SHA" HEAD | grep -E '^(api|client)/.*\.(js|jsx|ts|tsx)$' || true)

          # Debug output
          echo "Changed files:"
          echo "$CHANGED_FILES"

          # Ensure there are files to lint before running ESLint
          if [[ -z "$CHANGED_FILES" ]]; then
            echo "No matching files changed. Skipping ESLint."
            exit 0
          fi

          # Run ESLint
          npx eslint --no-error-on-unmatched-pattern \
            --config eslint.config.mjs \
            $CHANGED_FILES
frontend-review .github/workflows/frontend-review.yml
Triggers
pull_request
Runs on
ubuntu-latest, ubuntu-latest, windows-latest, ubuntu-latest
Jobs
build, test-ubuntu, test-windows, build-verify
Commands
  • npm ci
  • npm run build:data-provider
  • npm run build:client-package
  • npm ci
  • npm run test:ci --verbose
  • npm ci
  • npm run test:ci --verbose
  • npm ci
View raw YAML
name: Frontend Unit Tests

on:
  pull_request:
    branches:
      - main
      - dev
      - dev-staging
      - release/*
    paths:
      - 'client/**'
      - 'packages/data-provider/**'

env:
  NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'

jobs:
  build:
    name: Build packages
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            client/node_modules
            packages/client/node_modules
            packages/data-provider/node_modules
          key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Restore data-provider build cache
        id: cache-data-provider
        uses: actions/cache@v4
        with:
          path: packages/data-provider/dist
          key: build-data-provider-${{ runner.os }}-${{ hashFiles('packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }}

      - name: Build data-provider
        if: steps.cache-data-provider.outputs.cache-hit != 'true'
        run: npm run build:data-provider

      - name: Restore client-package build cache
        id: cache-client-package
        uses: actions/cache@v4
        with:
          path: packages/client/dist
          key: build-client-package-${{ runner.os }}-${{ hashFiles('packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/rollup.config.js', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/rollup.config.js', 'packages/data-provider/package.json') }}

      - name: Build client-package
        if: steps.cache-client-package.outputs.cache-hit != 'true'
        run: npm run build:client-package

      - name: Upload data-provider build
        uses: actions/upload-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist
          retention-days: 2

      - name: Upload client-package build
        uses: actions/upload-artifact@v4
        with:
          name: build-client-package
          path: packages/client/dist
          retention-days: 2

  test-ubuntu:
    name: 'Tests: Ubuntu'
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            client/node_modules
            packages/client/node_modules
            packages/data-provider/node_modules
          key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download client-package build
        uses: actions/download-artifact@v4
        with:
          name: build-client-package
          path: packages/client/dist

      - name: Run unit tests
        run: npm run test:ci --verbose
        working-directory: client

  test-windows:
    name: 'Tests: Windows'
    needs: build
    runs-on: windows-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            client/node_modules
            packages/client/node_modules
            packages/data-provider/node_modules
          key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download client-package build
        uses: actions/download-artifact@v4
        with:
          name: build-client-package
          path: packages/client/dist

      - name: Run unit tests
        run: npm run test:ci --verbose
        working-directory: client

  build-verify:
    name: Vite build verification
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.19
        uses: actions/setup-node@v4
        with:
          node-version: '20.19'

      - name: Restore node_modules cache
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            client/node_modules
            packages/client/node_modules
            packages/data-provider/node_modules
          key: node-modules-frontend-${{ runner.os }}-20.19-${{ hashFiles('package-lock.json') }}

      - name: Install dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci

      - name: Download data-provider build
        uses: actions/download-artifact@v4
        with:
          name: build-data-provider
          path: packages/data-provider/dist

      - name: Download client-package build
        uses: actions/download-artifact@v4
        with:
          name: build-client-package
          path: packages/client/dist

      - name: Build client
        run: cd client && npm run build:ci
generate_embeddings .github/workflows/generate_embeddings.yml
Triggers
workflow_dispatch, push
Runs on
ubuntu-latest
Jobs
generate
Actions
supabase/embeddings-generator
View raw YAML
name: 'generate_embeddings'
on:
  workflow_dispatch:
  push:
    branches:
      - main
    paths:
      - 'docs/**'

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: supabase/embeddings-generator@v0.0.5
        with:
          supabase-url: ${{ secrets.SUPABASE_URL }}
          supabase-service-role-key: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
          openai-key: ${{ secrets.OPENAI_DOC_EMBEDDINGS_KEY }}
          docs-root-path: 'docs'
helmcharts .github/workflows/helmcharts.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
release
Actions
azure/setup-helm, docker/login-action, appany/helm-oci-chart-releaser, appany/helm-oci-chart-releaser
Commands
  • git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
  • cd helm/librechat helm dependency build cd ../librechat-rag-api helm dependency build
  • CHART_VERSION=$(echo "${{ github.ref_name }}" | cut -d'-' -f2) echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
View raw YAML
name: Build Helm Charts on Tag

# The workflow is triggered when a tag is pushed
on:
  push:
    tags:
      - "chart-*"

jobs:
  release:
    permissions:
      contents: write
      packages: write
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Configure Git
        run: |
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

      - name: Install Helm
        uses: azure/setup-helm@v4
        env:
          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
      
      - name: Build Subchart Deps
        run: |
          cd helm/librechat
          helm dependency build  
          cd ../librechat-rag-api
          helm dependency build      

      - name: Get Chart Version
        id: chart-version
        run: |
          CHART_VERSION=$(echo "${{ github.ref_name }}" | cut -d'-' -f2)
          echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_OUTPUT"

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Run Helm OCI Charts Releaser
      # This is for the librechat chart
      - name: Release Helm OCI Charts for librechat
        uses: appany/helm-oci-chart-releaser@v0.4.2
        with:
          name: librechat
          repository: ${{ github.actor }}/librechat-chart
          tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
          path: helm/librechat
          registry: ghcr.io
          registry_username: ${{ github.actor }}
          registry_password: ${{ secrets.GITHUB_TOKEN }}
      
      # this is for the librechat-rag-api chart
      - name: Release Helm OCI Charts for librechat-rag-api
        uses: appany/helm-oci-chart-releaser@v0.4.2
        with:
          name: librechat-rag-api
          repository: ${{ github.actor }}/librechat-chart
          tag: ${{ steps.chart-version.outputs.CHART_VERSION }}
          path: helm/librechat-rag-api
          registry: ghcr.io
          registry_username: ${{ github.actor }}
          registry_password: ${{ secrets.GITHUB_TOKEN }}
i18n-unused-keys .github/workflows/i18n-unused-keys.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
detect-unused-i18n-keys
Commands
  • echo "🔍 Scanning for unused i18next keys..." # Define paths I18N_FILE="client/src/locales/en/translation.json" SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client" "packages/data-schemas/src") # Check if translation file exists if [[ ! -f "$I18N_FILE" ]]; then echo "::error title=Missing i18n File::Translation file not found: $I18N_FILE" exit 1 fi # Extract all keys from the JSON file KEYS=$(jq -r 'keys[]' "$I18N_FILE") # Track unused keys UNUSED_KEYS=() # Check if each key is used in the source code for KEY in $KEYS; do FOUND=false # Special case for dynamically constructed special variable keys if [[ "$KEY" == com_ui_special_var_* ]]; then # Check if TSpecialVarLabel is used in the codebase for DIR in "${SOURCE_DIRS[@]}"; do if grep -r --include=\*.{js,jsx,ts,tsx} -q "TSpecialVarLabel" "$DIR"; then FOUND=true break fi done # Also check if the key is directly used somewhere if [[ "$FOUND" == false ]]; then for DIR in "${SOURCE_DIRS[@]}"; do if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then FOUND=true break fi done fi # Special case for agent category keys that are dynamically used from database elif [[ "$KEY" == com_agents_category_* ]]; then # Check if agent category localization is being used for DIR in "${SOURCE_DIRS[@]}"; do # Check for dynamic category label/description usage if grep -r --include=\*.{js,jsx,ts,tsx} -E "category\.(label|description).*startsWith.*['\"]com_" "$DIR" > /dev/null 2>&1 || \ # Check for the method that defines these keys grep -r --include=\*.{js,jsx,ts,tsx} "ensureDefaultCategories" "$DIR" > /dev/null 2>&1 || \ # Check for direct usage in agentCategory.ts grep -r --include=\*.ts -E "label:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1 || \ grep -r --include=\*.ts -E "description:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1; then FOUND=true break fi done # Also check if the key is directly used somewhere if [[ "$FOUND" == false ]]; then for DIR in "${SOURCE_DIRS[@]}"; do if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then FOUND=true break fi done fi else # Regular check for other keys for DIR in "${SOURCE_DIRS[@]}"; do if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then FOUND=true break fi done fi if [[ "$FOUND" == false ]]; then UNUSED_KEYS+=("$KEY") fi done # Output results if [[ ${#UNUSED_KEYS[@]} -gt 0 ]]; then echo "🛑 Found ${#UNUSED_KEYS[@]} unused i18n keys:" echo "unused_keys=$(echo "${UNUSED_KEYS[@]}" | jq -R -s -c 'split(" ")')" >> $GITHUB_ENV for KEY in "${UNUSED_KEYS[@]}"; do echo "::warning title=Unused i18n Key::'$KEY' is defined but not used in the codebase." done else echo "✅ No unused i18n keys detected!" echo "unused_keys=[]" >> $GITHUB_ENV fi
  • PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") # Format the unused keys list as checkboxes for easy manual checking. FILTERED_KEYS=$(echo "$unused_keys" | jq -r '.[]' | grep -v '^\s*$' | sed 's/^/- [ ] `/;s/$/`/' ) COMMENT_BODY=$(cat <<EOF ### 🚨 Unused i18next Keys Detected The following translation keys are defined in \`translation.json\` but are **not used** in the codebase: $FILTERED_KEYS ⚠️ **Please remove these unused keys to keep the translation files clean.** EOF ) gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ -f body="$COMMENT_BODY" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
  • exit 1
View raw YAML
name: Detect Unused i18next Strings

# This workflow checks for unused i18n keys in translation files.
# It has special handling for:
# - com_ui_special_var_* keys that are dynamically constructed
# - com_agents_category_* keys that are stored in the database and used dynamically

on:
  pull_request:
    paths:
      - "client/src/**"
      - "api/**"
      - "packages/data-provider/src/**"
      - "packages/client/**"
      - "packages/data-schemas/src/**"

jobs:
  detect-unused-i18n-keys:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Find unused i18next keys
        id: find-unused
        run: |
          echo "🔍 Scanning for unused i18next keys..."

          # Define paths
          I18N_FILE="client/src/locales/en/translation.json"
          SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client" "packages/data-schemas/src")

          # Check if translation file exists
          if [[ ! -f "$I18N_FILE" ]]; then
            echo "::error title=Missing i18n File::Translation file not found: $I18N_FILE"
            exit 1
          fi

          # Extract all keys from the JSON file
          KEYS=$(jq -r 'keys[]' "$I18N_FILE")

          # Track unused keys
          UNUSED_KEYS=()

          # Check if each key is used in the source code
          for KEY in $KEYS; do
            FOUND=false
            
            # Special case for dynamically constructed special variable keys
            if [[ "$KEY" == com_ui_special_var_* ]]; then
              # Check if TSpecialVarLabel is used in the codebase
              for DIR in "${SOURCE_DIRS[@]}"; do
                if grep -r --include=\*.{js,jsx,ts,tsx} -q "TSpecialVarLabel" "$DIR"; then
                  FOUND=true
                  break
                fi
              done
              
              # Also check if the key is directly used somewhere
              if [[ "$FOUND" == false ]]; then
                for DIR in "${SOURCE_DIRS[@]}"; do
                  if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
                    FOUND=true
                    break
                  fi
                done
              fi
            # Special case for agent category keys that are dynamically used from database
            elif [[ "$KEY" == com_agents_category_* ]]; then
              # Check if agent category localization is being used
              for DIR in "${SOURCE_DIRS[@]}"; do
                # Check for dynamic category label/description usage
                if grep -r --include=\*.{js,jsx,ts,tsx} -E "category\.(label|description).*startsWith.*['\"]com_" "$DIR" > /dev/null 2>&1 || \
                   # Check for the method that defines these keys
                   grep -r --include=\*.{js,jsx,ts,tsx} "ensureDefaultCategories" "$DIR" > /dev/null 2>&1 || \
                   # Check for direct usage in agentCategory.ts
                   grep -r --include=\*.ts -E "label:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1 || \
                   grep -r --include=\*.ts -E "description:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1; then
                  FOUND=true
                  break
                fi
              done
              
              # Also check if the key is directly used somewhere
              if [[ "$FOUND" == false ]]; then
                for DIR in "${SOURCE_DIRS[@]}"; do
                  if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
                    FOUND=true
                    break
                  fi
                done
              fi
            else
              # Regular check for other keys
              for DIR in "${SOURCE_DIRS[@]}"; do
                if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
                  FOUND=true
                  break
                fi
              done
            fi
          
            if [[ "$FOUND" == false ]]; then
              UNUSED_KEYS+=("$KEY")
            fi
          done

          # Output results
          if [[ ${#UNUSED_KEYS[@]} -gt 0 ]]; then
            echo "🛑 Found ${#UNUSED_KEYS[@]} unused i18n keys:"
            echo "unused_keys=$(echo "${UNUSED_KEYS[@]}" | jq -R -s -c 'split(" ")')" >> $GITHUB_ENV
            for KEY in "${UNUSED_KEYS[@]}"; do
              echo "::warning title=Unused i18n Key::'$KEY' is defined but not used in the codebase."
            done
          else
            echo "✅ No unused i18n keys detected!"
            echo "unused_keys=[]" >> $GITHUB_ENV
          fi

      - name: Post verified comment on PR
        if: env.unused_keys != '[]'
        run: |
          PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH")

          # Format the unused keys list as checkboxes for easy manual checking.
          FILTERED_KEYS=$(echo "$unused_keys" | jq -r '.[]' | grep -v '^\s*$' | sed 's/^/- [ ] `/;s/$/`/' )

          COMMENT_BODY=$(cat <<EOF
          ### 🚨 Unused i18next Keys Detected

          The following translation keys are defined in \`translation.json\` but are **not used** in the codebase:

          $FILTERED_KEYS

          ⚠️ **Please remove these unused keys to keep the translation files clean.**
          EOF
          )

          gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
            -f body="$COMMENT_BODY" \
            -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Fail workflow if unused keys found
        if: env.unused_keys != '[]'
        run: exit 1
locize-i18n-sync .github/workflows/locize-i18n-sync.yml
Triggers
push, repository_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
sync-translations, create-pull-request
Actions
locize/download, peter-evans/create-pull-request
Commands
  • npm install -g locize-cli
  • cd client/src/locales locize sync --api-key ${{ secrets.LOCIZE_API_KEY }} --project-id ${{ secrets.LOCIZE_PROJECT_ID }} --language en
  • echo "Skipping sync as the event is not a push."
View raw YAML
name: Sync Locize Translations & Create Translation PR

on:
  push:
    branches: [main]
  repository_dispatch:
    types: [locize/versionPublished]

jobs:
  sync-translations:
    name: Sync Translation Keys with Locize
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4

      - name: Set Up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install locize CLI
        run: npm install -g locize-cli

      # Sync translations (Push missing keys & remove deleted ones)
      - name: Sync Locize with Repository
        if: ${{ github.event_name == 'push' }}
        run: |
          cd client/src/locales
          locize sync --api-key ${{ secrets.LOCIZE_API_KEY }} --project-id ${{ secrets.LOCIZE_PROJECT_ID }} --language en

      # When triggered by repository_dispatch, skip sync step.
      - name: Skip sync step on non-push events
        if: ${{ github.event_name != 'push' }}
        run: echo "Skipping sync as the event is not a push."

  create-pull-request:
    name: Create Translation PR on Version Published
    runs-on: ubuntu-latest
    needs: sync-translations
    permissions:
      contents: write
      pull-requests: write
    steps:
      # 1. Check out the repository.
      - name: Checkout Repository
        uses: actions/checkout@v4

      # 2. Download translation files from locize.
      - name: Download Translations from locize
        uses: locize/download@v2
        with:
          project-id: ${{ secrets.LOCIZE_PROJECT_ID }}
          path: "client/src/locales"

      # 3. Create a Pull Request using built-in functionality.
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v7
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          sign-commits: true
          commit-message: "🌍 i18n: Update translation.json with latest translations"
          base: main
          branch: i18n/locize-translation-update
          reviewers: danny-avila
          title: "🌍 i18n: Update translation.json with latest translations"
          body: |
            **Description**:
            - 🎯 **Objective**: Update `translation.json` with the latest translations from locize.
            - 🔍 **Details**: This PR is automatically generated upon receiving a versionPublished event with version "latest". It reflects the newest translations provided by locize.
            - ✅ **Status**: Ready for review.
          labels: "🌍 i18n"
main-image-workflow matrix .github/workflows/main-image-workflow.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
build
Matrix
include, include.file, include.image_name, include.target→ Dockerfile, Dockerfile.multi, api-build, librechat, librechat-api, node
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action
Commands
  • git fetch --tags echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
  • cp .env.example .env
View raw YAML
name: Docker Compose Build Latest Main Image Tag (Manual Dispatch)

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - target: api-build
            file: Dockerfile.multi
            image_name: librechat-api
          - target: node
            file: Dockerfile
            image_name: librechat

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Fetch tags and set the latest tag
        run: |
          git fetch --tags
          echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV

      # Set up QEMU
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Login to Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Prepare the environment
      - name: Prepare environment
        run: |
          cp .env.example .env

      # Build and push Docker images for each target
      - name: Build and push Docker images
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.file }}
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ env.LATEST_TAG }}
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ env.LATEST_TAG }}
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
          platforms: linux/amd64,linux/arm64
          target: ${{ matrix.target }}
tag-images matrix .github/workflows/tag-images.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
build
Matrix
include, include.file, include.image_name, include.target→ Dockerfile, Dockerfile.multi, api-build, librechat, librechat-api, node
Actions
docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action, docker/login-action, docker/build-push-action
Commands
  • cp .env.example .env
View raw YAML
name: Docker Images Build on Tag

on:
  push:
    tags:
      - '*'

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - target: api-build
            file: Dockerfile.multi
            image_name: librechat-api
          - target: node
            file: Dockerfile
            image_name: librechat

    steps:
      # Check out the repository
      - name: Checkout
        uses: actions/checkout@v4

      # Set up QEMU
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      # Set up Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # Log in to GitHub Container Registry
      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Login to Docker Hub
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Prepare the environment
      - name: Prepare environment
        run: |
          cp .env.example .env

      # Build and push Docker images for each target
      - name: Build and push Docker images
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ${{ matrix.file }}
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:${{ github.ref_name }}
            ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}:latest
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:${{ github.ref_name }}
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.image_name }}:latest
          platforms: linux/amd64,linux/arm64
          target: ${{ matrix.target }}
unused-packages .github/workflows/unused-packages.yml
Triggers
pull_request
Runs on
ubuntu-latest
Jobs
detect-unused-packages
Commands
  • npm install -g depcheck
  • for FILE in package.json client/package.json api/package.json packages/client/package.json; do if [[ -f "$FILE" ]]; then jq empty "$FILE" || (echo "::error title=Invalid JSON::$FILE is invalid" && exit 1) fi done
  • extract_deps_from_scripts() { local package_file=$1 if [[ -f "$package_file" ]]; then jq -r '.scripts | to_entries[].value' "$package_file" | \ grep -oE '([a-zA-Z0-9_-]+)' | sort -u > used_scripts.txt else touch used_scripts.txt fi } extract_deps_from_scripts "package.json" mv used_scripts.txt root_used_deps.txt extract_deps_from_scripts "client/package.json" mv used_scripts.txt client_used_deps.txt extract_deps_from_scripts "api/package.json" mv used_scripts.txt api_used_deps.txt
  • extract_deps_from_code() { local folder=$1 local output_file=$2 # Initialize empty output file > "$output_file" if [[ -d "$folder" ]]; then # Extract require() statements (use explicit includes for portability) grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" \ --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" >> "$output_file" || true # Extract ES6 imports - import x from 'module' grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true # import 'module' (side-effect imports) grep -rEho "import ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ sed -E "s/import ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true # export { x } from 'module' or export * from 'module' grep -rEho "export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ sed -E "s/export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true # import type { x } from 'module' (TypeScript) grep -rEho "import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ --include='*.ts' --include='*.tsx' 2>/dev/null | \ sed -E "s/import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true # Remove subpath imports but keep the base package # For scoped packages: '@scope/pkg/subpath' -> '@scope/pkg' # For regular packages: 'pkg/subpath' -> 'pkg' # Scoped packages (must keep @scope/package, strip anything after) sed -i -E 's|^(@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true # Non-scoped packages (keep package name, strip subpath) sed -i -E 's|^([a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true sort -u "$output_file" -o "$output_file" fi } extract_deps_from_code "." root_used_code.txt extract_deps_from_code "client" client_used_code.txt extract_deps_from_code "api" api_used_code.txt # Extract dependencies used by workspace packages # These packages are used in the workspace but dependencies are provided by parent package.json extract_deps_from_code "packages/client" packages_client_used_code.txt extract_deps_from_code "packages/api" packages_api_used_code.txt
  • if [[ -f "packages/client/package.json" ]]; then # Get all dependencies from @librechat/client (dependencies, devDependencies, and peerDependencies) DEPS=$(jq -r '.dependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") # Combine all dependencies echo "$DEPS" > librechat_client_deps.txt echo "$DEV_DEPS" >> librechat_client_deps.txt echo "$PEER_DEPS" >> librechat_client_deps.txt # Also include dependencies that are imported in packages/client cat packages_client_used_code.txt >> librechat_client_deps.txt # Remove empty lines and sort grep -v '^$' librechat_client_deps.txt | sort -u > temp_deps.txt mv temp_deps.txt librechat_client_deps.txt else touch librechat_client_deps.txt fi
  • if [[ -f "packages/api/package.json" ]]; then # Get all dependencies from @librechat/api (dependencies, devDependencies, and peerDependencies) DEPS=$(jq -r '.dependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") # Combine all dependencies echo "$DEPS" > librechat_api_deps.txt echo "$DEV_DEPS" >> librechat_api_deps.txt echo "$PEER_DEPS" >> librechat_api_deps.txt # Also include dependencies that are imported in packages/api cat packages_api_used_code.txt >> librechat_api_deps.txt # Remove empty lines and sort grep -v '^$' librechat_api_deps.txt | sort -u > temp_deps.txt mv temp_deps.txt librechat_api_deps.txt else touch librechat_api_deps.txt fi
  • # Function to get dependencies from a workspace package that are used by another package get_workspace_package_deps() { local package_json=$1 local output_file=$2 # Get all workspace dependencies (starting with @librechat/) if [[ -f "$package_json" ]]; then local workspace_deps=$(jq -r '.dependencies // {} | to_entries[] | select(.key | startswith("@librechat/")) | .key' "$package_json" 2>/dev/null || echo "") # For each workspace dependency, get its dependencies for dep in $workspace_deps; do # Convert @librechat/api to packages/api local workspace_path=$(echo "$dep" | sed 's/@librechat\//packages\//') local workspace_package_json="${workspace_path}/package.json" if [[ -f "$workspace_package_json" ]]; then # Extract all dependencies from the workspace package jq -r '.dependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file" # Also extract peerDependencies jq -r '.peerDependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file" fi done fi if [[ -f "$output_file" ]]; then sort -u "$output_file" -o "$output_file" else touch "$output_file" fi } # Get workspace dependencies for each package get_workspace_package_deps "package.json" root_workspace_deps.txt get_workspace_package_deps "client/package.json" client_workspace_deps.txt get_workspace_package_deps "api/package.json" api_workspace_deps.txt
  • if [[ -f "package.json" ]]; then UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "") # Exclude dependencies used in scripts, code, and workspace packages UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt root_workspace_deps.txt | sort) || echo "") echo "ROOT_UNUSED<<EOF" >> $GITHUB_ENV echo "$UNUSED" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV fi
View raw YAML
name: Detect Unused NPM Packages

on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'
      - 'client/**'
      - 'api/**'
      - 'packages/client/**'
      - 'packages/api/**'

jobs:
  detect-unused-packages:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js 20.x
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install depcheck
        run: npm install -g depcheck

      - name: Validate JSON files
        run: |
          for FILE in package.json client/package.json api/package.json packages/client/package.json; do
            if [[ -f "$FILE" ]]; then
              jq empty "$FILE" || (echo "::error title=Invalid JSON::$FILE is invalid" && exit 1)
            fi
          done

      - name: Extract Dependencies Used in Scripts
        id: extract-used-scripts
        run: |
          extract_deps_from_scripts() {
            local package_file=$1
            if [[ -f "$package_file" ]]; then
              jq -r '.scripts | to_entries[].value' "$package_file" | \
                grep -oE '([a-zA-Z0-9_-]+)' | sort -u > used_scripts.txt
            else
              touch used_scripts.txt
            fi
          }

          extract_deps_from_scripts "package.json"
          mv used_scripts.txt root_used_deps.txt

          extract_deps_from_scripts "client/package.json"
          mv used_scripts.txt client_used_deps.txt

          extract_deps_from_scripts "api/package.json"
          mv used_scripts.txt api_used_deps.txt

      - name: Extract Dependencies Used in Source Code
        id: extract-used-code
        run: |
          extract_deps_from_code() {
            local folder=$1
            local output_file=$2
            
            # Initialize empty output file
            > "$output_file"
            
            if [[ -d "$folder" ]]; then
              # Extract require() statements (use explicit includes for portability)
              grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" \
                --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
                sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" >> "$output_file" || true
          
              # Extract ES6 imports - import x from 'module'
              grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
                --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
                sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
              
              # import 'module' (side-effect imports)
              grep -rEho "import ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
                --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
                sed -E "s/import ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
              
              # export { x } from 'module' or export * from 'module'
              grep -rEho "export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
                --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \
                sed -E "s/export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
              
              # import type { x } from 'module' (TypeScript)
              grep -rEho "import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \
                --include='*.ts' --include='*.tsx' 2>/dev/null | \
                sed -E "s/import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true
          
              # Remove subpath imports but keep the base package
              # For scoped packages: '@scope/pkg/subpath' -> '@scope/pkg'
              # For regular packages: 'pkg/subpath' -> 'pkg'
              # Scoped packages (must keep @scope/package, strip anything after)
              sed -i -E 's|^(@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true
              # Non-scoped packages (keep package name, strip subpath)
              sed -i -E 's|^([a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true
              
              sort -u "$output_file" -o "$output_file"
            fi
          }

          extract_deps_from_code "." root_used_code.txt
          extract_deps_from_code "client" client_used_code.txt
          extract_deps_from_code "api" api_used_code.txt
          
          # Extract dependencies used by workspace packages
          # These packages are used in the workspace but dependencies are provided by parent package.json
          extract_deps_from_code "packages/client" packages_client_used_code.txt
          extract_deps_from_code "packages/api" packages_api_used_code.txt

      - name: Get @librechat/client dependencies
        id: get-librechat-client-deps
        run: |
          if [[ -f "packages/client/package.json" ]]; then
            # Get all dependencies from @librechat/client (dependencies, devDependencies, and peerDependencies)
            DEPS=$(jq -r '.dependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
            DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
            PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "")
            
            # Combine all dependencies
            echo "$DEPS" > librechat_client_deps.txt
            echo "$DEV_DEPS" >> librechat_client_deps.txt
            echo "$PEER_DEPS" >> librechat_client_deps.txt
            
            # Also include dependencies that are imported in packages/client
            cat packages_client_used_code.txt >> librechat_client_deps.txt
            
            # Remove empty lines and sort
            grep -v '^$' librechat_client_deps.txt | sort -u > temp_deps.txt
            mv temp_deps.txt librechat_client_deps.txt
          else
            touch librechat_client_deps.txt
          fi

      - name: Get @librechat/api dependencies
        id: get-librechat-api-deps
        run: |
          if [[ -f "packages/api/package.json" ]]; then
            # Get all dependencies from @librechat/api (dependencies, devDependencies, and peerDependencies)
            DEPS=$(jq -r '.dependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
            DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
            PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "")
            
            # Combine all dependencies
            echo "$DEPS" > librechat_api_deps.txt
            echo "$DEV_DEPS" >> librechat_api_deps.txt
            echo "$PEER_DEPS" >> librechat_api_deps.txt
            
            # Also include dependencies that are imported in packages/api
            cat packages_api_used_code.txt >> librechat_api_deps.txt
            
            # Remove empty lines and sort
            grep -v '^$' librechat_api_deps.txt | sort -u > temp_deps.txt
            mv temp_deps.txt librechat_api_deps.txt
          else
            touch librechat_api_deps.txt
          fi

      - name: Extract Workspace Dependencies
        id: extract-workspace-deps
        run: |
          # Function to get dependencies from a workspace package that are used by another package
          get_workspace_package_deps() {
            local package_json=$1
            local output_file=$2
            
            # Get all workspace dependencies (starting with @librechat/)
            if [[ -f "$package_json" ]]; then
              local workspace_deps=$(jq -r '.dependencies // {} | to_entries[] | select(.key | startswith("@librechat/")) | .key' "$package_json" 2>/dev/null || echo "")
              
              # For each workspace dependency, get its dependencies
              for dep in $workspace_deps; do
                # Convert @librechat/api to packages/api
                local workspace_path=$(echo "$dep" | sed 's/@librechat\//packages\//')
                local workspace_package_json="${workspace_path}/package.json"
                
                if [[ -f "$workspace_package_json" ]]; then
                  # Extract all dependencies from the workspace package
                  jq -r '.dependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file"
                  # Also extract peerDependencies
                  jq -r '.peerDependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file"
                fi
              done
            fi
            
            if [[ -f "$output_file" ]]; then
              sort -u "$output_file" -o "$output_file"
            else
              touch "$output_file"
            fi
          }
          
          # Get workspace dependencies for each package
          get_workspace_package_deps "package.json" root_workspace_deps.txt
          get_workspace_package_deps "client/package.json" client_workspace_deps.txt
          get_workspace_package_deps "api/package.json" api_workspace_deps.txt

      - name: Run depcheck for root package.json
        id: check-root
        run: |
          if [[ -f "package.json" ]]; then
            UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
            # Exclude dependencies used in scripts, code, and workspace packages
            UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt root_workspace_deps.txt | sort) || echo "")
            echo "ROOT_UNUSED<<EOF" >> $GITHUB_ENV
            echo "$UNUSED" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV
          fi

      - name: Run depcheck for client/package.json
        id: check-client
        run: |
          if [[ -f "client/package.json" ]]; then
            chmod -R 755 client
            cd client
            UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
            # Exclude dependencies used in scripts, code, workspace packages, and @librechat/client imports
            UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../client_used_deps.txt ../client_used_code.txt ../client_workspace_deps.txt ../packages_client_used_code.txt ../librechat_client_deps.txt 2>/dev/null | sort -u) || echo "")
            # Filter out false positives
            UNUSED=$(echo "$UNUSED" | grep -v "^micromark-extension-llm-math$" || echo "")
            echo "CLIENT_UNUSED<<EOF" >> $GITHUB_ENV
            echo "$UNUSED" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV
            cd ..
          fi

      - name: Run depcheck for api/package.json
        id: check-api
        run: |
          if [[ -f "api/package.json" ]]; then
            chmod -R 755 api
            cd api
            UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "")
            # Exclude dependencies used in scripts, code, workspace packages, and @librechat/api imports
            UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../api_used_deps.txt ../api_used_code.txt ../api_workspace_deps.txt ../packages_api_used_code.txt ../librechat_api_deps.txt 2>/dev/null | sort -u) || echo "")
            echo "API_UNUSED<<EOF" >> $GITHUB_ENV
            echo "$UNUSED" >> $GITHUB_ENV
            echo "EOF" >> $GITHUB_ENV
            cd ..
          fi

      - name: Post comment on PR if unused dependencies are found
        if: env.ROOT_UNUSED != '' || env.CLIENT_UNUSED != '' || env.API_UNUSED != ''
        run: |
          PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH")

          ROOT_LIST=$(echo "$ROOT_UNUSED" | awk '{print "- `" $0 "`"}')
          CLIENT_LIST=$(echo "$CLIENT_UNUSED" | awk '{print "- `" $0 "`"}')
          API_LIST=$(echo "$API_UNUSED" | awk '{print "- `" $0 "`"}')

          COMMENT_BODY=$(cat <<EOF
          ### 🚨 Unused NPM Packages Detected

          The following **unused dependencies** were found:

          $(if [[ ! -z "$ROOT_UNUSED" ]]; then echo "#### 📂 Root \`package.json\`"; echo ""; echo "$ROOT_LIST"; echo ""; fi)

          $(if [[ ! -z "$CLIENT_UNUSED" ]]; then echo "#### 📂 Client \`client/package.json\`"; echo ""; echo "$CLIENT_LIST"; echo ""; fi)

          $(if [[ ! -z "$API_UNUSED" ]]; then echo "#### 📂 API \`api/package.json\`"; echo ""; echo "$API_LIST"; echo ""; fi)

          ⚠️ **Please remove these unused dependencies to keep your project clean.**
          EOF
          )

          gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
            -f body="$COMMENT_BODY" \
            -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Fail workflow if unused dependencies found
        if: env.ROOT_UNUSED != '' || env.CLIENT_UNUSED != '' || env.API_UNUSED != ''
        run: exit 1