name: Release
run-name: Testing and Releasing DC
on:
  push:
    branches:
      - main

env:
  NODE_VERSION: '24.5.0'

jobs:
  Build-production-and-ng-test:
    runs-on: ubuntu-latest
    env:
      CHROME_BIN: /usr/bin/google-chrome

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Write .npmrc file
        run: |
          touch client/.npmrc
          echo '${{ secrets.NPMRC}}' > client/.npmrc

      - name: Install Chrome for Angular tests
        run: |
          apt-get update
          wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
          apt install -y ./google-chrome*.deb

      - name: Write cypress credentials
        run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
        shell: bash
        env:
          CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}

      - name: Install dependencies
        run: |
          cd client
          # Decrypt and Install sheet
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
          npm ci

      - name: Check audit
        # Audit should fail and stop the CI on any vulnerability in root and sas, and on low+ in client
        run: |
          npm audit --omit=dev
          cd ./sas
          npm audit --omit=dev
          cd ../client
          npm audit --omit=dev

      - name: Angular Tests
        run: |
          cd client
          npm run test:headless

      - name: Angular Production Build
        run: |
          cd client
          npm run postinstall
          npm run build

  Build-and-test-development:
    runs-on: ubuntu-latest
    needs: Build-production-and-ng-test
    env:
      CHROME_BIN: /usr/bin/google-chrome
      # Pin OS locale
      LANG: en_GB.UTF-8
      LC_ALL: en_GB.UTF-8

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Write .npmrc file
        run: |
          touch client/.npmrc
          echo '${{ secrets.NPMRC}}' > client/.npmrc

      - name: Install system dependencies
        run: |
          apt-get update
          wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
          apt install -y ./google-chrome*.deb
          apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb jq zip locales
          # Generate the en_GB.UTF-8 locale referenced by LANG/LC_ALL
          locale-gen en_GB.UTF-8
          update-locale LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8

      - name: Write cypress credentials
        run: echo "$CYPRESS_CREDS" > ./client/cypress.env.json
        shell: bash
        env:
          CYPRESS_CREDS: ${{ secrets.CYPRESS_CREDS }}

      - name: Install dependencies
        run: |
          cd client
          # Decrypt and Install sheet
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
          npm ci

      - name: Setup and start SASjs server
        run: |
          npm i -g pm2
          curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
          unzip linux.zip
          touch .env
          echo RUN_TIMES=js >> .env
          echo NODE_PATH=node >> .env
          echo CORS=enable >> .env
          echo WHITELIST=http://localhost:4200 >> .env
          cat .env
          pm2 start api-linux --wait-ready

      - name: Deploy mocked services
        run: |
          cd ./sas/mocks/sasjs
          npm install -g @sasjs/cli
          npm install -g replace-in-files-cli
          sasjs cbd -t server-ci
          # sasjs request services/admin/makedata -t server-ci -d ./deploy/makeData4GL.json -c ./deploy/requestConfig.json -o ./output.json

      - name: Prepare and run frontend and cypress
        run: |
          cd ./client
          mv ./cypress.env.example.json ./cypress.env.json
          replace-in-files --regex='"username".*' --replacement='"username":"'${{ secrets.CYPRESS_USERNAME_SASJS }}'",' ./cypress.env.json
          replace-in-files --regex='"password".*' --replacement='"password":"'${{ secrets.CYPRESS_PWD_SASJS }}'" ' ./cypress.env.json
          cat ./cypress.env.json
          npm run postinstall
          # Prepare index.html to SASJS local
          replace-in-files --regex='serverUrl=".*?"' --replacement='serverUrl="http://localhost:5000"' ./src/index.html
          replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/Public/app/devtest"' ./src/index.html
          replace-in-files --regex='serverType=".*?"' --replacement='serverType="SASJS"' ./src/index.html
          replace-in-files --regex='"hosturl".*' --replacement='hosturl:"http://localhost:4200",' ./cypress.config.ts
          cat ./cypress.config.ts
          # Start frontend and run cypress
          npx ng serve --host 0.0.0.0 --port 4200 & npx wait-on http://localhost:4200 && npx cypress run --browser chrome --spec "cypress/e2e/csv-limited.cy.ts,cypress/e2e/liveness.cy.ts,cypress/e2e/editor.cy.ts,cypress/e2e/excel-multi-load.cy.ts,cypress/e2e/excel.cy.ts,cypress/e2e/csv.cy.ts,cypress/e2e/filtering.cy.ts,cypress/e2e/licensing.cy.ts,cypress/e2e/viewer-labels.cy.ts"

      - name: Zip Cypress videos
        if: always()
        run: |
          mkdir -p ./client/cypress/videos
          zip -r cypress-videos ./client/cypress/videos

      - name: Add cypress videos artifacts
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: cypress-videos.zip
          path: cypress-videos.zip

  release:
    runs-on: ubuntu-latest
    needs: [Build-production-and-ng-test, Build-and-test-development]
    # The semantic-release step below uses the per-job token injected by Gitea
    # Actions (secrets.GITEA_TOKEN). That token is scoped to THIS repository
    # and its permissions are derived from this block.
    #   contents: write -> push CHANGELOG.md / package.json commit + tag, and
    #                      create the Release via the Gitea API.
    # The TSDOC_TOKEN and CODE_DATACONTROLLER_IO secrets continue to be used
    # because they target *different* servers (webdoc.datacontroller.io and
    # code.datacontroller.io), not the Gitea API.
    permissions:
      contents: write

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Write .npmrc file
        run: |
          echo "$NPMRC" > client/.npmrc
          echo "legacy-peer-deps=true" >> client/.npmrc
        shell: bash
        env:
          NPMRC: ${{ secrets.NPMRC}}

      - name: Install system packages
        run: |
          apt-get update
          apt-get install -y zip jq doxygen
          npm i -g @sasjs/cli

      - name: Frontend Preliminary Build
        description: We want to prevent creating empty release if frontend fails
        run: |
          cd client
          # Decrypt and Install sheet
          echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
          npm ci
          npm i webpack
          npm run build

      - name: Create Empty Release (assets are posted later)
        env:
          # Use the ephemeral per-job token injected by Gitea Actions instead
          # of a manually-managed PAT. This token always reports push=true on
          # its own repo via the Gitea API, which is what the
          # @saithodev/semantic-release-gitea verifyConditions step checks.
          GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
          GITEA_URL: ${{ gitea.server_url }}
        run: |
          set -euo pipefail
          npm i
          npm i -g semantic-release

          # We do a semantic-release DRY RUN to make the job fail if there are
          # no changes to release. The previous implementation piped --dry-run
          # into `grep`, which swallowed semantic-release's non-zero exit codes
          # (e.g. auth/verifyConditions failures) — the real error then only
          # surfaced from the second invocation. Capture output and inspect it
          # so authentication failures fail the step cleanly with context.
          echo "::group::semantic-release --dry-run"
          DRY_EXIT=0
          DRY_OUTPUT=$(semantic-release --dry-run 2>&1) || DRY_EXIT=$?
          echo "$DRY_OUTPUT"
          echo "::endgroup::"

          if [ "$DRY_EXIT" -ne 0 ]; then
            echo "::error::semantic-release dry-run failed (exit $DRY_EXIT). See log above; common cause is the Gitea token lacking push permission on this repo (verifyConditions of @saithodev/semantic-release-gitea)."
            exit "$DRY_EXIT"
          fi

          if echo "$DRY_OUTPUT" | grep -q "There are no relevant changes, so no new version is released."; then
            echo "::error::No releasable changes since last tag - aborting release job."
            exit 1
          fi

          semantic-release

      - name: Frontend Build
        description: Must be created AFTER the release as the version (git tag) is used in the interface
        run: |
          cd client
          npm run build

      - name: Build SAS9 EBI Release
        description: Compile SAS 9 services, remove tests & create deployment program
        run: |
          cd sas
          npm i
          sasjs c -t sas9
          rm -rf sasjsbuild/tests
          sasjs b -t sas9
          cp sasjsbuild/mysas9deploy.sas ./demostream_sas9.sas
          #
          # remove streamed component and rebuild SAS 9 services
          #
          rm -rf sasjsbuild/services/web9
          rm sasjsbuild/services/clickme.sas
          sasjs b -t sas9
          cp sasjsbuild/mysas9deploy.sas ./sas9.sas

      - name: Build SASjs Server Release
        description: Compile Base (SASjs) services, remove tests & create deployment JSON
        run: |
          cd sas
          cp sasjs/utils/favicon.ico ../client/dist/favicon.ico
          sasjs c -t server
          rm -rf sasjsbuild/tests
          server_apploc="/Public/app/dc"
          sed -i "s|apploc=\"[^\"]*\"|apploc=\"${server_apploc}\"|g" sasjsbuild/services/web/index.html
          sasjs b -t server
          cp sasjsbuild/server.json.zip ./sasjs_server.json.zip

      - name: Build Viya Release
        description: compile Viya Streaming Deploy (without tests)
        run: |
          cd sas
          sasjs c -t viya
          rm -rf sasjsbuild/tests
          sed -i -e 's/servertype="SASJS"/servertype="SASVIYA"/g' sasjsbuild/services/DC.html
          sasjs b -t viya
          cp sasjsbuild/viya.sas ./viya.sas
          # compile Viya Full deploy (without web)
          rm -rf sasjsbuild/services/web
          rm sasjsbuild/services/DC.html
          sasjs b -t viya
          cp sasjsbuild/viya.sas ./viya_noweb.sas
          cp sasjsbuild/viya.json ./viya_noweb.json

      - name: Zip Frontend (including viya.json for full viya deploy)
        run: |
          cd sas
          cp sasjsbuild/viya.json ../client/dist/viya.json
          cd ..
          zip -r frontend.zip ./client/dist

      - name: Release Typedoc
        run: |
          cd client
          npm -g install cloudron-surfer
          npm run compodoc:build
          surfer put --token ${{ secrets.TSDOC_TOKEN }} --server webdoc.datacontroller.io documentation/* /

      - name: Release code.datacontroller.io
        run: |
          cd sas
          sasjs doc
          surfer put --token ${{ secrets.CODE_DATACONTROLLER_IO }} --server code.datacontroller.io sasjsbuild/sasdocs/* /

      - name: Upload assets to release
        env:
          # Use the same ephemeral per-job Gitea Actions token that the
          # "Create Empty Release" step uses for semantic-release. It is scoped
          # to this repo and is granted write access via the workflow's
          # `permissions:` block at the top of the file (see contents: write).
          GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
        run: |
          set -euo pipefail
          # Send the token via Authorization header rather than ?access_token=
          # (the query-string form is deprecated and leaks into access logs).
          AUTH_HEADER="Authorization: token ${GITEA_TOKEN}"
          BASE="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"

          RELEASE_JSON=$(curl -k --fail-with-body -sS -H "$AUTH_HEADER" "$BASE/releases/latest")
          RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id')
          RELEASE_BODY=$(echo "$RELEASE_JSON" | jq -r '.body')

          # Update body (also confirms the token has contents:write on this repo)
          curl -k --fail-with-body -sS -X PATCH \
            -H "$AUTH_HEADER" \
            -H 'Content-Type: application/json' \
            --data "$(jq -n --arg body "$RELEASE_BODY"$'\n\nFor installation instructions, please visit https://docs.datacontroller.io/' \
                        '{draft:false, body:$body}')" \
            "$BASE/releases/$RELEASE_ID"

          # Upload assets
          URL="$BASE/releases/$RELEASE_ID/assets"
          for f in frontend.zip \
                   sas/demostream_sas9.sas \
                   sas/viya.sas \
                   sas/sasjs_server.json.zip \
                   sas/sas9.sas \
                   sas/viya_noweb.sas \
                   sas/viya_noweb.json; do
            echo "Uploading $f ..."
            curl -k --fail-with-body -sS -H "$AUTH_HEADER" "$URL" -F "attachment=@$f"
          done
