# CircleCI v2 Config
version: 2.1

##
# orbs
#
# Orbs used in this pipeline
##
orbs:
  slack: circleci/slack@4.12.5 # Ref: https://github.com/mojaloop/ci-config/tree/master/slack-templates
  pr-tools: mojaloop/pr-tools@0.1.10 # Ref: https://github.com/mojaloop/ci-config/
  gh: circleci/github-cli@2.2.0

##
# defaults
#
# YAML defaults templates, in alphabetical order
##
defaults_docker_Dependencies: &defaults_docker_Dependencies |
    apk --no-cache add git
    apk --no-cache add ca-certificates
    apk --no-cache add curl
    apk --no-cache add openssh-client
    apk add --no-cache -t build-dependencies make gcc g++ python3 libtool autoconf automake jq bash
    npm install -g node-gyp

defaults_awsCliDependencies: &defaults_awsCliDependencies |
    apk --no-cache add aws-cli

defaults_license_scanner: &defaults_license_scanner
  name: Install and set up license-scanner
  command: |
    git clone https://github.com/mojaloop/license-scanner /tmp/license-scanner
    cd /tmp/license-scanner && make build default-files set-up

defaults_npm_auth: &defaults_npm_auth
  name: Update NPM registry auth token
  command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc


defaults_npm_publish_release: &defaults_npm_publish_release
  name: Publish NPM $RELEASE_TAG artifact
  command: |
    source $BASH_ENV
    echo "Publishing tag $RELEASE_TAG"
    npm publish --tag $RELEASE_TAG --access public

defaults_export_version_from_package: &defaults_export_version_from_package
  name: Format the changelog into the github release body and get release tag
  command: |
    git diff --no-indent-heuristic main~1 HEAD CHANGELOG.md | sed -n '/^+[^+]/ s/^+//p' > /tmp/changes
    echo 'export RELEASE_CHANGES=`cat /tmp/changes`' >> $BASH_ENV
    echo 'export RELEASE_TAG=`cat package-lock.json | jq -r .version`' >> $BASH_ENV

defaults_configure_git: &defaults_configure_git
  name: Configure git
  command: |
    git config user.email ${GIT_CI_EMAIL}
    git config user.name ${GIT_CI_USER}

defaults_configure_nvm: &defaults_configure_nvm
  name: Configure NVM
  command: |
    cd $HOME

    export ENV_DOT_PROFILE=$HOME/.profile
    touch $ENV_DOT_PROFILE

    echo "1. Export env variable"
    export NVM_DIR="$HOME/.nvm"
    if [ -z "$NVMRC_VERSION" ]; then
      echo "==> Configuring NVMRC_VERSION!"
      export NVMRC_VERSION=$(cat $CIRCLE_WORKING_DIRECTORY/.nvmrc)
      echo "export NVMRC_VERSION=$NVMRC_VERSION" >> $ENV_DOT_PROFILE
    fi

    if [ -f "$NVM_DIR" ]; then
      echo "==> $NVM_DIR exists. Skipping steps 2-4!"
    else
      echo "==> $NVM_DIR does not exists. Executing steps 2-4!"

      echo "2. Installing NVM"
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

      echo "3. Executing $NVM_DIR/nvm.sh"
      [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    fi

    ## Ref: https://github.com/nvm-sh/nvm/issues/1102#issuecomment-550572252
    if [ ! -z "$NVM_ARCH_UNOFFICIAL_OVERRIDE" ]; then
      echo "==> Handle NVM_ARCH_UNOFFICIAL_OVERRIDE=$NVM_ARCH_UNOFFICIAL_OVERRIDE!"
      echo "nvm_get_arch() { nvm_echo \"${NVM_ARCH_UNOFFICIAL_OVERRIDE}\"; }" >> $ENV_DOT_PROFILE
      echo "export NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release" >> $ENV_DOT_PROFILE
      source $ENV_DOT_PROFILE
    fi

    echo "4. Installing Node version: $NVMRC_VERSION"
    nvm install $NVMRC_VERSION
    nvm alias default $NVMRC_VERSION
    nvm use $NVMRC_VERSION

    cd $CIRCLE_WORKING_DIRECTORY

defaults_display_versions: &defaults_display_versions
  name: Display Versions
  command: |
    echo "What is the active version of Nodejs?"
    echo "node: $(node --version)"
    echo "yarn: $(yarn --version)"
    echo "npm: $(npm --version)"
    echo "nvm: $(nvm --version)"

##
# Executors
#
# CircleCI Executors
##
executors:
  default-docker:
    working_directory: &WORKING_DIR /home/circleci/project
    shell: "/bin/sh -leo pipefail" ## Ref: https://circleci.com/docs/env-vars/#alpine-linux
    environment:
      BASH_ENV: /etc/profile ## Ref: https://circleci.com/docs/env-vars/#alpine-linux
      NVM_ARCH_UNOFFICIAL_OVERRIDE: x64-musl ## Ref: https://github.com/nvm-sh/nvm/issues/1102#issuecomment-550572252
    docker:
      - image: node:22.15.0-alpine3.21

  default-machine:
    working_directory: *WORKING_DIR
    shell: "/bin/bash -leo pipefail"
    machine:
      image: ubuntu-2204:2023.04.2 # Ref: https://circleci.com/developer/machine/image/ubuntu-2204

jobs:
  setup:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - run:
          name: Install NPM dependencies
          command: npm ci
      - save_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
          paths:
            - node_modules

  test-dependencies:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Execute dependency tests
          command: npm run dep:check

  test-lint:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Execute lint tests
          command: npm run lint

  test-unit:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          # This is needed for legacy core tests. Remove this once 'tape' is fully deprecated.
          name: Install tape, tapes and tap-xunit
          command: npm install tape tapes tap-xunit
      - run:
          name: Create dir for test results
          command: mkdir -p ./test/results
      - run:
          name: Execute unit tests
          command: npm -s run test:xunit
      - store_artifacts:
          path: ./test/results
          destination: test
      - store_test_results:
          path: ./test/results

  test-coverage:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - run:
          name: Install AWS CLI dependencies
          command: *defaults_awsCliDependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Execute code coverage check
          command: npm -s run test:coverage-check
      - store_artifacts:
          path: coverage
          destination: test
      - store_test_results:
          path: coverage

  vulnerability-check:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - run:
          <<: *defaults_configure_nvm
      - run:
          <<: *defaults_display_versions
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Create dir for test results
          command: mkdir -p ./audit/results
      - run:
          name: Check for new npm vulnerabilities
          command: npm run audit:check -- -o json > ./audit/results/auditResults.json
      - store_artifacts:
          path: ./audit/results
          destination: audit

  audit-licenses:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - run:
          <<: *defaults_license_scanner
      - checkout
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Run the license-scanner
          command: cd /tmp/license-scanner && pathToRepo=$CIRCLE_WORKING_DIRECTORY make run
      - store_artifacts:
          path: /tmp/license-scanner/results
          destination: licenses

  release:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - restore_cache:
          keys:
          - dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          <<: *defaults_configure_git
      - run:
          name: Setup Slack config
          command: |
            echo "export SLACK_PROJECT_NAME=${CIRCLE_PROJECT_REPONAME}" >> $BASH_ENV
            echo "export SLACK_RELEASE_TYPE='GitHub Release'" >> $BASH_ENV
            echo "export SLACK_RELEASE_TAG='${RELEASE_TAG} on ${CIRCLE_BRANCH} branch'" >> $BASH_ENV
            echo "export SLACK_BUILD_ID=${CIRCLE_BUILD_NUM}" >> $BASH_ENV
            echo "export SLACK_CI_URL=${CIRCLE_BUILD_URL}" >> $BASH_ENV
      - run:
          name: Generate changelog and bump package version
          command: npm run release -- --no-verify
      - run:
          name: Push the release
          command: git push --follow-tags origin ${CIRCLE_BRANCH}
      - slack/notify:
          event: fail
          template: SLACK_TEMP_RELEASE_FAILURE

  github-release:
    executor: default-machine
    shell: "/bin/bash -eo pipefail"
    steps:
      - run:
          name: Install git
          command: |
            sudo apt-get update && sudo apt-get install -y git
      - gh/install
      - checkout
      - run:
          <<: *defaults_configure_git
      - run:
          name: Fetch updated release branch
          command: |
            git fetch origin
            git checkout origin/${CIRCLE_BRANCH}
      - run:
          <<: *defaults_export_version_from_package
      - run:
          name: Check the release changes
          command: |
            echo "Changes are: ${RELEASE_CHANGES}"
      - run:
          name: Setup Slack config
          command: |
            echo "export SLACK_PROJECT_NAME=${CIRCLE_PROJECT_REPONAME}" >> $BASH_ENV
            echo "export SLACK_RELEASE_TYPE='Github Release'" >> $BASH_ENV
            echo "export SLACK_RELEASE_TAG=v${RELEASE_TAG}" >> $BASH_ENV
            echo "export SLACK_RELEASE_URL=https://github.com/mojaloop/${CIRCLE_PROJECT_REPONAME}/releases/tag/v${RELEASE_TAG}" >> $BASH_ENV
            echo "export SLACK_BUILD_ID=${CIRCLE_BUILD_NUM}" >> $BASH_ENV
            echo "export SLACK_CI_URL=${CIRCLE_BUILD_URL}" >> $BASH_ENV
      - run:
          name: Create Release
          command: |
            gh release create "v${RELEASE_TAG}" --title "v${RELEASE_TAG} Release" --draft=false --notes "${RELEASE_CHANGES}" ./CHANGELOG.md
      - slack/notify:
          event: pass
          template: SLACK_TEMP_RELEASE_SUCCESS
      - slack/notify:
          event: fail
          template: SLACK_TEMP_RELEASE_FAILURE

  publish:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Setup for LATEST release
          command: |
            echo "export RELEASE_TAG=$RELEASE_TAG_PROD" >> $BASH_ENV
            echo "RELEASE_TAG=$RELEASE_TAG_PROD"

            PACKAGE_VERSION=$(cat package-lock.json | jq -r .version)
            echo "export PACKAGE_VERSION=${PACKAGE_VERSION}" >> $BASH_ENV
            echo "PACKAGE_VERSION=${PACKAGE_VERSION}"
      - run:
          name: Setup Slack config
          command: |
            echo "export SLACK_PROJECT_NAME=${CIRCLE_PROJECT_REPONAME}" >> $BASH_ENV
            echo "export SLACK_RELEASE_TYPE='NPM Release'" >> $BASH_ENV
            echo "export SLACK_RELEASE_TAG=v${CIRCLE_TAG:1}" >> $BASH_ENV
            echo "export SLACK_RELEASE_URL=https://www.npmjs.com/package/@mojaloop/${CIRCLE_PROJECT_REPONAME}/v/${CIRCLE_TAG:1}" >> $BASH_ENV
            echo "export SLACK_BUILD_ID=${CIRCLE_BUILD_NUM}" >> $BASH_ENV
            echo "export SLACK_CI_URL=${CIRCLE_BUILD_URL}" >> $BASH_ENV
      - run:
          <<: *defaults_npm_auth
      - run:
          <<: *defaults_npm_publish_release
      - slack/notify:
          event: pass
          template: SLACK_TEMP_RELEASE_SUCCESS
      - slack/notify:
          event: fail
          template: SLACK_TEMP_RELEASE_FAILURE

  publish-snapshot:
    executor: default-docker
    steps:
      - run:
          name: Install general dependencies
          command: *defaults_docker_Dependencies
      - checkout
      - restore_cache:
          key: dependency-cache-{{ .Environment.CIRCLE_SHA1 }}
      - run:
          name: Setup for SNAPSHOT release
          command: |
            echo "export RELEASE_TAG=${RELEASE_TAG_SNAPSHOT}" >> $BASH_ENV
            echo "RELEASE_TAG=${RELEASE_TAG_SNAPSHOT}"

            echo "Override package version: ${CIRCLE_TAG:1}"
            npx standard-version --skip.tag --skip.commit --skip.changelog --release-as ${CIRCLE_TAG:1}

            PACKAGE_VERSION=$(cat package-lock.json | jq -r .version)
            echo "export PACKAGE_VERSION=${PACKAGE_VERSION}" >> $BASH_ENV
            echo "PACKAGE_VERSION=${PACKAGE_VERSION}"
      - run:
          name: Setup Slack config
          command: |
            echo "export SLACK_PROJECT_NAME=${CIRCLE_PROJECT_REPONAME}" >> $BASH_ENV
            echo "export SLACK_RELEASE_TYPE='NPM Snapshot'" >> $BASH_ENV
            echo "export SLACK_RELEASE_TAG=v${CIRCLE_TAG:1}" >> $BASH_ENV
            echo "export SLACK_RELEASE_URL=https://www.npmjs.com/package/@mojaloop/${CIRCLE_PROJECT_REPONAME}/v/${CIRCLE_TAG:1}" >> $BASH_ENV
            echo "export SLACK_BUILD_ID=${CIRCLE_BUILD_NUM}" >> $BASH_ENV
            echo "export SLACK_CI_URL=${CIRCLE_BUILD_URL}" >> $BASH_ENV
      - run:
          <<: *defaults_npm_auth
      - run:
          <<: *defaults_npm_publish_release
      - slack/notify:
          event: pass
          template: SLACK_TEMP_RELEASE_SUCCESS
      - slack/notify:
          event: fail
          template: SLACK_TEMP_RELEASE_FAILURE

workflows:
  build_and_test:
    jobs:
      - pr-tools/pr-title-check:
          context: org-global
      - setup:
          context: org-global
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      - test-dependencies:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              ignore: /.*/
            branches:
              ignore:
                - main
      - test-lint:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      - test-unit:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      - test-coverage:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      - vulnerability-check:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      - audit-licenses:
          context: org-global
          requires:
            - setup
          filters:
            tags:
              only: /.*/
            branches:
              ignore:
                - /feature*/
                - /bugfix*/
      # New commits to main release automatically
      - release:
          context: org-global
          requires:
            - pr-tools/pr-title-check
            - test-lint
            - test-unit
            - test-coverage
            - vulnerability-check
            - audit-licenses
          filters:
            branches:
              only:
                - main
                - /release\/v.*/
      - github-release:
          context: org-global
          requires:
            - release
          filters:
            branches:
              only:
                - main
                - /release\/v.*/
      - publish:
          context: org-global
          requires:
            - pr-tools/pr-title-check
            - test-lint
            - test-unit
            - test-coverage
            - vulnerability-check
            - audit-licenses
          filters:
            tags:
              only: /v[0-9]+(\.[0-9]+)*/
            branches:
              ignore:
                - /.*/
      - publish-snapshot:
          context: org-global
          requires:
            - pr-tools/pr-title-check
            - test-lint
            - test-unit
            - test-coverage
            - vulnerability-check
            - audit-licenses
          filters:
            tags:
              only: /v[0-9]+(\.[0-9]+)*\-snapshot+((\.[0-9]+)?)/
            branches:
              ignore:
                - /.*/
