#!/bin/bash
set -eu

###
#
# Publishes all *.tgz files to npm
#
# Usage: ./publib-npm [DIR]
#
# DIR: directory where npm tarballs are found (default is `dist/js`).
#
# NPM_TOKEN (optional): registry authentication token (either from npmjs or a GitHub personal access token), not used for AWS CodeArtifact
# NPM_TRUSTED_PUBLISHER (optional): use a Trusted Publisher configuration instead of tokens, any value 
# NPM_REGISTRY (optional): the registry URL (defaults to "registry.npmjs.org")
# AWS_ACCESS_KEY_ID (optional): If AWS CodeArtifact is used as registry, an AWS access key can be spedified.
# AWS_SECRET_ACCESS_KEY (optional): Secret access key that belongs to the AWS access key.
# AWS_ROLE_TO_ASSUME (optional): If AWS CodeArtifact is used as registry and need to assume role.
# DISABLE_HTTPS (optional): connect to the registry with HTTP instead of HTTPS (defaults to false)
# VERIFY_PUBLISHED (optional): if "true", wait until every published version is available from the registry
# VERIFY_PUBLISHED_TIMEOUT_SECONDS (optional): seconds to wait for the published versions to be available (defaults to 3600)
#
###

dir="${1:-"dist/js"}"

# Evaluate trusted publisher flag once as boolean
use_trusted_publisher=false
if [ -n "${NPM_TRUSTED_PUBLISHER:-}" ] && [ "${NPM_TRUSTED_PUBLISHER}" != "false" ]; then
  use_trusted_publisher=true
fi

if ! [ -z "${NPM_REGISTRY:-}" ] && [[ $NPM_REGISTRY =~ .codeartifact.*.amazonaws.com ]]; then
  codeartifact_account="$(echo $NPM_REGISTRY | cut -d. -f1 | rev | cut -d- -f1 | rev)"
  codeartifact_subdomain="$(echo $NPM_REGISTRY | cut -d. -f1)"
  codeartifact_domain="$(echo $codeartifact_subdomain | cut -b -$((${#codeartifact_subdomain}-${#codeartifact_account}-1)))"
  codeartifact_region="$(echo $NPM_REGISTRY | cut -d. -f4)"
  export AWS_DEFAULT_REGION="$(echo $codeartifact_region)" 
  if [ -n "${AWS_ROLE_TO_ASSUME:-}" ]; then
    credentials=`aws sts assume-role --role-session-name "publib-code-artifact" --role-arn ${AWS_ROLE_TO_ASSUME} --output text | sed -n '2 p'`
    export AWS_ACCESS_KEY_ID="$(echo $credentials | cut -d' ' -f2)"
    export AWS_SECRET_ACCESS_KEY="$(echo $credentials | cut -d' ' -f4)"
    export AWS_SESSION_TOKEN="$(echo $credentials | cut -d' ' -f5)"
  fi
  NPM_TOKEN=`aws codeartifact get-authorization-token --domain $codeartifact_domain --domain-owner $codeartifact_account --region $codeartifact_region --query authorizationToken --output text`
elif [ -z "${NPM_TOKEN:-}" ] && [ "$use_trusted_publisher" = "false" ]; then
  echo "NPM_TOKEN=<token> or NPM_TRUSTED_PUBLISHER=true is required"
  exit 1
fi

if [ "$use_trusted_publisher" = "true" ]; then
  unset NPM_TOKEN
fi

NPM_REGISTRY=${NPM_REGISTRY:-"registry.npmjs.org"}
if ! [ -z "${NPM_TOKEN:-}" ]; then
  echo "//${NPM_REGISTRY%%/}/:_authToken=${NPM_TOKEN}" > ~/.npmrc
fi

# this overrides any registry configuration defined externally. For example, yarn sets the registry to the yarn proxy
# which requires `yarn login`. but since we are logging in through ~/.npmrc, we must make sure we publish directly to npm.
if ! [ -z "${DISABLE_HTTPS:-}" ]; then
  export NPM_CONFIG_REGISTRY="http://${NPM_REGISTRY}"
else
  export NPM_CONFIG_REGISTRY="https://${NPM_REGISTRY}"
fi

# dist-tags
tag=""
if [ -n "${NPM_DIST_TAG:-}" ]; then
  tag="--tag ${NPM_DIST_TAG}"
  echo "Publishing under the following dist-tag: ${NPM_DIST_TAG}"
fi

# access level
access=""
if [ -n "${NPM_ACCESS_LEVEL:-}" ]; then
  if ! [[ "${NPM_ACCESS_LEVEL}" =~ ^(public|restricted)$ ]]; then
    echo "Invalid package access level: ${NPM_ACCESS_LEVEL}. Valid values are: public, restricted (default is restricted for scoped packages and public for unscoped packages)"
    exit 1
  fi

  access="--access ${NPM_ACCESS_LEVEL}"
  echo "Publishing package with access level: ${NPM_ACCESS_LEVEL}"
fi

# verification timeout
verify_timeout_seconds="${VERIFY_PUBLISHED_TIMEOUT_SECONDS:-3600}"
if ! [[ "${verify_timeout_seconds}" =~ ^[0-9]+$ ]]; then
  echo "Invalid VERIFY_PUBLISHED_TIMEOUT_SECONDS: ${verify_timeout_seconds}. Must be a number of seconds"
  exit 1
fi

log=$(mktemp -d)/npmlog.txt

# Check for dry run
if [ "${PUBLIB_DRYRUN:-}" = "true" ]; then
  echo "🏜️ Dry run: skipping npm publish"
  for file in ${dir}/**.tgz; do
    echo "  (would publish ${file})"
  done
  echo "SUCCESS (dry run)"
  exit 0
fi

for file in ${dir}/**.tgz; do
  npm publish ${tag} ${access} ${file} 2>&1 | tee ${log}
  exit_code="${PIPESTATUS[0]}"

  if [ ${exit_code} -ne 0 ]; then

    # error returned from npmjs
    if cat ${log} | grep -q "You cannot publish over the previously published versions"; then
      echo "SKIPPING: already published"
      continue
    fi

    # error returned from github packages
    if cat ${log} | grep -q "Cannot publish over existing version"; then
      echo "SKIPPING: already published"
      continue
    fi

    echo "ERROR"
    exit 1
  fi
done

# The registry may not serve a version right after it was published (npmjs scans packages first,
# and larger packages take longer). Wait until it does, so that packages depending on it can be installed.
if [ "${VERIFY_PUBLISHED:-}" = "true" ]; then
  deadline_seconds=$((SECONDS + verify_timeout_seconds))

  for file in ${dir}/**.tgz; do
    pkgdir=$(mktemp -d)
    tar -xzf ${file} -C ${pkgdir} package/package.json
    spec=$(node -p 'const p = require(process.argv[1]); `${p.name}@${p.version}`' ${pkgdir}/package/package.json)
    until npm view ${spec} version --prefer-online > /dev/null 2>&1; do
      if [ ${SECONDS} -ge ${deadline_seconds} ]; then
        echo "ERROR: ${spec} is not available after ${verify_timeout_seconds} seconds"
        exit 1
      fi
      echo "Waiting for ${spec} to be available..."
      sleep 20
    done
    echo "${spec} is available"
  done
fi

echo "SUCCESS"
