name: Build Web

on:
  pull_request_target:
    types: [opened, synchronize]
    branches-ignore:
      - 'l10n_master'
      - 'cf-pages'
    paths:
      - 'apps/web/**'
      - 'libs/**'
      - '*'
      - '!*.md'
      - '!*.txt'
      - '.github/workflows/build-web.yml'
  push:
    branches:
      - 'main'
      - 'rc'
      - 'hotfix-rc-web'
    paths:
      - 'apps/web/**'
      - 'libs/**'
      - '*'
      - '!*.md'
      - '!*.txt'
      - '.github/workflows/build-web.yml'
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      custom_tag_extension:
        description: "Custom image tag extension"
        required: false
      sdk_branch:
        description: "Custom SDK branch"
        required: false
        type: string

env:
  _AZ_REGISTRY: bitwardenprod.azurecr.io

jobs:
  check-run:
    name: Check PR run
    uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main

  setup:
    name: Setup
    runs-on: ubuntu-22.04
    needs:
      - check-run
    outputs:
      version: ${{ steps.version.outputs.value }}
      node_version: ${{ steps.retrieve-node-version.outputs.node_version }}
    steps:
      - name: Check out repo
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          ref: ${{  github.event.pull_request.head.sha }}

      - name: Get GitHub sha as version
        id: version
        run: echo "value=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT

      - name: Get Node Version
        id: retrieve-node-version
        run: |
          NODE_NVMRC=$(cat .nvmrc)
          NODE_VERSION=${NODE_NVMRC/v/''}
          echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT

  build-artifacts:
    name: Build artifacts
    runs-on: ubuntu-22.04
    needs:
      - setup
    env:
      _VERSION: ${{ needs.setup.outputs.version }}
      _NODE_VERSION: ${{ needs.setup.outputs.node_version }}
    strategy:
      matrix:
        include:
          - name: "selfhosted-open-source"
            npm_command: "dist:oss:selfhost"
          - name: "cloud-COMMERCIAL"
            npm_command: "dist:bit:cloud"
          - name: "selfhosted-COMMERCIAL"
            npm_command: "dist:bit:selfhost"
          - name: "cloud-QA"
            npm_command: "build:bit:qa"
            git_metadata: true
          - name: "ee"
            npm_command: "build:bit:ee"
            git_metadata: true
          - name: "cloud-euprd"
            npm_command: "build:bit:euprd"
          - name: "cloud-euqa"
            npm_command: "build:bit:euqa"
            git_metadata: true
          - name: "cloud-usdev"
            npm_command: "build:bit:usdev"
            git_metadata: true

    steps:
      - name: Check out repo
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          ref: ${{  github.event.pull_request.head.sha }}

      - name: Set up Node
        uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
        with:
          cache: 'npm'
          cache-dependency-path: '**/package-lock.json'
          node-version: ${{ env._NODE_VERSION }}

      - name: Print environment
        run: |
          whoami
          node --version
          npm --version
          docker --version
          echo "GitHub ref: $GITHUB_REF"
          echo "GitHub event: $GITHUB_EVENT"

      - name: Install dependencies
        run: npm ci

      - name: Download SDK Artifacts
        if: ${{ inputs.sdk_branch != '' }}
        uses: bitwarden/gh-actions/download-artifacts@main
        with:
          github_token: ${{secrets.GITHUB_TOKEN}}
          workflow: build-wasm-internal.yml
          workflow_conclusion: success
          branch: ${{ inputs.sdk_branch }}
          artifacts: sdk-internal
          repo: bitwarden/sdk-internal
          path: ../sdk-internal
          if_no_artifact_found: fail

      - name: Override SDK
        if: ${{ inputs.sdk_branch != '' }}
        working-directory: ./
        run: |
          ls -l ../
          npm link ../sdk-internal

      - name: Add Git metadata to build version
        working-directory: apps/web
        if: matrix.git_metadata
        run: |
          VERSION=$( jq -r ".version" package.json)
          jq --arg version "$VERSION+${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp
          mv package.json.tmp package.json

      - name: Build ${{ matrix.name }}
        working-directory: apps/web
        run: npm run ${{ matrix.npm_command }}

      - name: Package artifact
        working-directory: apps/web
        run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build

      - name: Upload ${{ matrix.name }} artifact
        uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
        with:
          name: web-${{ env._VERSION }}-${{ matrix.name }}.zip
          path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip
          if-no-files-found: error


  build-containers:
    name: Build Docker images
    runs-on: ubuntu-22.04
    permissions:
      security-events: write
      id-token: write
    needs:
      - setup
      - build-artifacts
    strategy:
      fail-fast: false
      matrix:
        include:
          - artifact_name: cloud-QA
            image_name: web-qa-cloud
          - artifact_name: ee
            image_name: web-ee
          - artifact_name: selfhosted-COMMERCIAL
            image_name: web
    env:
      _VERSION: ${{ needs.setup.outputs.version }}
    steps:
      - name: Check out repo
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          ref: ${{  github.event.pull_request.head.sha }}

      - name: Check Branch to Publish
        env:
          PUBLISH_BRANCHES: "main,rc,hotfix-rc-web"
        id: publish-branch-check
        run: |
          IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES

          if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then
            echo "is_publish_branch=true" >> $GITHUB_ENV
          else
            echo "is_publish_branch=false" >> $GITHUB_ENV
          fi

      ########## ACRs ##########
      - name: Login to Prod Azure
        uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
        with:
          creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}

      - name: Log into Prod container registry
        run: az acr login -n bitwardenprod

      - name: Login to Azure - CI Subscription
        uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
        with:
          creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

      - name: Retrieve github PAT secrets
        id: retrieve-secret-pat
        uses: bitwarden/gh-actions/get-keyvault-secrets@main
        with:
          keyvault: "bitwarden-ci"
          secrets: "github-pat-bitwarden-devops-bot-repo-scope"

      - name: Download ${{ matrix.artifact_name }} artifact
        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
        with:
          name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip
          path: apps/web

      ########## Generate image tag and build Docker image ##########
      - name: Generate Docker image tag
        id: tag
        run: |
          if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
            IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g")
          else
            IMAGE_TAG=$(echo "${GITHUB_REF_NAME}" | sed "s#/#-#g")
          fi

          if [[ "$IMAGE_TAG" == "main" ]]; then
            IMAGE_TAG=dev
          fi

          TAG_EXTENSION=${{ github.event.inputs.custom_tag_extension }}

          if [[ $TAG_EXTENSION ]]; then
            IMAGE_TAG=$IMAGE_TAG-$TAG_EXTENSION
          fi

          echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT

      ########## Build Image ##########
      - name: Extract artifact
        working-directory: apps/web
        run: unzip web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip

      - name: Generate image full name
        id: image-name
        env:
          IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
          PROJECT_NAME: ${{ matrix.image_name }}
        run: echo "name=$_AZ_REGISTRY/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT

      - name: Build Docker image
        id: build-docker
        uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
        with:
          context: apps/web
          file: apps/web/Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.image-name.outputs.name }}
          secrets: |
            "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
      
      - name: Install Cosign
        if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
        uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0

      - name: Sign image with Cosign
        if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
        env:
          DIGEST: ${{ steps.build-docker.outputs.digest }}
          TAGS: ${{ steps.image-name.outputs.name }}
        run: |
          IFS="," read -a tags <<< "${TAGS}"
          images=""
          for tag in "${tags[@]}"; do
            images+="${tag}@${DIGEST} "
          done
          cosign sign --yes ${images}

      - name: Scan Docker image
        id: container-scan
        uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0
        with:
          image: ${{ steps.image-name.outputs.name }}
          fail-build: false
          output-format: sarif

      - name: Upload Grype results to GitHub
        uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
        with:
          sarif_file: ${{ steps.container-scan.outputs.sarif }}

      - name: Log out of Docker
        run: docker logout

  crowdin-push:
    name: Crowdin Push
    if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
    needs:
      - build-artifacts
    runs-on: ubuntu-22.04
    steps:
      - name: Check out repo
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
        with:
          ref: ${{  github.event.pull_request.head.sha }}

      - name: Login to Azure
        uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
        with:
          creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

      - name: Retrieve secrets
        id: retrieve-secrets
        uses: bitwarden/gh-actions/get-keyvault-secrets@main
        with:
          keyvault: "bitwarden-ci"
          secrets: "crowdin-api-token"

      - name: Upload Sources
        uses: crowdin/github-action@30849777a3cba6ee9a09e24e195272b8287a0a5b # v1.20.4
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
          CROWDIN_PROJECT_ID: "308189"
        with:
          config: apps/web/crowdin.yml
          crowdin_branch_name: main
          upload_sources: true
          upload_translations: false

  trigger-web-vault-deploy:
    name: Trigger web vault deploy
    if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-22.04
    needs:
      - build-artifacts
    steps:
      - name: Login to Azure - CI Subscription
        uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
        with:
          creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

      - name: Retrieve github PAT secrets
        id: retrieve-secret-pat
        uses: bitwarden/gh-actions/get-keyvault-secrets@main
        with:
          keyvault: "bitwarden-ci"
          secrets: "github-pat-bitwarden-devops-bot-repo-scope"

      - name: Trigger web vault deploy using GitHub Run ID
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: 'bitwarden',
              repo: 'clients',
              workflow_id: 'deploy-web.yml',
              ref: 'main',
              inputs: {
                'environment': 'USDEV',
                'build-web-run-id': '${{ github.run_id }}'
              }
            })

  check-failures:
    name: Check for failures
    if: always()
    runs-on: ubuntu-22.04
    needs:
      - setup
      - build-artifacts
      - build-containers
      - crowdin-push
      - trigger-web-vault-deploy
    steps:
      - name: Check if any job failed
        if: |
          github.event_name != 'pull_request_target'
          && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-web')
          && contains(needs.*.result, 'failure')
        run: exit 1

      - name: Login to Azure - Prod Subscription
        uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
        if: failure()
        with:
          creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

      - name: Retrieve secrets
        id: retrieve-secrets
        if: failure()
        uses: bitwarden/gh-actions/get-keyvault-secrets@main
        with:
          keyvault: "bitwarden-ci"
          secrets: "devops-alerts-slack-webhook-url"

      - name: Notify Slack on failure
        uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
        if: failure()
        env:
          SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
        with:
          status: ${{ job.status }}
