From 72e4d99005ee672c50717ad9f2b47899797a9ec2 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Fri, 22 Mar 2019 13:16:33 +0900 Subject: [PATCH] ops: Renew CI scripts (#155) * Renew CI scripts * Wip ensure-branch * WIP * Check branch * Fix up ensure-branch * Cosme [skip ci] * Skip test for dev [skip ci] * Impl release-staging * Add master release * Rename dir * Fix path * Wip is-release-ready * Fix env var usage * Wip master * Sort deploy target * Wip notify * Rename * Wip npm publish * Wip publsh github * Test persist/attach * Fix yaml * Fix yml again * Finalize config.yaml * Split release checker * Impl slack notify * Use promisify exec * Use --no-save * Impl create github release * Fix up config * Fix repo/owner * Add comments * Split functions * Use identical config wip * Use config staging * Use config master * Kick CI for test * Fix up config * Cover merge commit * Fix yaml format * Fix inconsistency in workflow filter * Add comment [skip ci] --- .circleci/check_is_new_release.sh | 17 ---- .circleci/check_latest_version.sh | 31 ------- .circleci/check_pr_branch.sh | 25 ----- .circleci/common/upload_to_s3.sh | 47 ---------- .circleci/config.yml | 90 +++++++----------- .circleci/deploy_gcp_staging.sh | 34 ------- .circleci/deploy_master/create_release.sh | 55 ----------- .circleci/deploy_master/deploy_npm.sh | 14 --- .circleci/deploy_master/deploy_s3.sh | 40 -------- .circleci/deploy_master/send_notification.sh | 23 ----- .circleci/deploy_staging.sh | 33 ------- scripts/config.js | 35 +++++++ scripts/ensure-branch/fetch-base-branch.js | 13 +++ scripts/ensure-branch/index.js | 56 ++++++++++++ scripts/release-master/index.js | 88 ++++++++++++++++++ scripts/release-master/is-new-release.js | 58 ++++++++++++ scripts/release-master/is-release-ready.js | 36 ++++++++ scripts/release-master/notify-slack.js | 11 +++ scripts/release-master/publish-to-github.js | 91 +++++++++++++++++++ scripts/release-master/publish-to-npm.js | 16 ++++ scripts/release-staging/index.js | 51 +++++++++++ scripts/shared/replace-examples-api-key.js | 15 +++ scripts/shared/replace-examples-cdn-domain.js | 15 +++ scripts/shared/replace-sdk-server-domain.js | 15 +++ scripts/shared/upload-examples-to-s3.js | 78 ++++++++++++++++ scripts/shared/upload-sdk-to-s3.js | 37 ++++++++ 26 files changed, 647 insertions(+), 377 deletions(-) delete mode 100755 .circleci/check_is_new_release.sh delete mode 100644 .circleci/check_latest_version.sh delete mode 100644 .circleci/check_pr_branch.sh delete mode 100644 .circleci/common/upload_to_s3.sh delete mode 100644 .circleci/deploy_gcp_staging.sh delete mode 100644 .circleci/deploy_master/create_release.sh delete mode 100644 .circleci/deploy_master/deploy_npm.sh delete mode 100644 .circleci/deploy_master/deploy_s3.sh delete mode 100644 .circleci/deploy_master/send_notification.sh delete mode 100644 .circleci/deploy_staging.sh create mode 100644 scripts/config.js create mode 100644 scripts/ensure-branch/fetch-base-branch.js create mode 100644 scripts/ensure-branch/index.js create mode 100644 scripts/release-master/index.js create mode 100644 scripts/release-master/is-new-release.js create mode 100644 scripts/release-master/is-release-ready.js create mode 100644 scripts/release-master/notify-slack.js create mode 100644 scripts/release-master/publish-to-github.js create mode 100644 scripts/release-master/publish-to-npm.js create mode 100644 scripts/release-staging/index.js create mode 100644 scripts/shared/replace-examples-api-key.js create mode 100644 scripts/shared/replace-examples-cdn-domain.js create mode 100644 scripts/shared/replace-sdk-server-domain.js create mode 100644 scripts/shared/upload-examples-to-s3.js create mode 100644 scripts/shared/upload-sdk-to-s3.js diff --git a/.circleci/check_is_new_release.sh b/.circleci/check_is_new_release.sh deleted file mode 100755 index ea8bc89c..00000000 --- a/.circleci/check_is_new_release.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -version=`cat package.json | jq -r ".version"`; -tag_name="v$version"; - -url="https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/releases/tags/$tag_name"; -status_code=`curl -s -H "Authorization: token $GITHUB_TOKEN" "$url" -o /dev/null -sw '%{http_code}'`; - -if [[ "$status_code" -ne "404" ]] -then - exit 1; -else - exit 0; -fi \ No newline at end of file diff --git a/.circleci/check_latest_version.sh b/.circleci/check_latest_version.sh deleted file mode 100644 index 1cbd149c..00000000 --- a/.circleci/check_latest_version.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -# Check to make sure the tag name doesn't already exist -version=`cat package.json | jq -r ".version"` - -tag_name="v$version"; - -echo "package.json version is $version"; -echo "Checking if release $tag_name already exists"; -./check_is_new_release.sh; -if [ $? -ne 0 ] -then - echo "Release $tag_name already exists, exiting."; - exit 1; -fi -echo "Release doesn't exist yet!"; - -# Check the latest changelog version (grep doesn't always recognize '\d' so use '[0-9]' instead) -changelog_version=$(cat CHANGELOG.md | grep -E "^##[^#]" | grep -o -E "v[0-9]+.[0-9]+.[0-9]+" | head -n 1); -echo "Newest entry found in changelog is for $changelog_version"; - -if [[ "$changelog_version" != "$tag_name" ]] -then - echo "Changelog for $tag_name doesn't exist. Update CHANGELOG.md."; - exit 1; -fi - -echo "Changelog is up to date!"; diff --git a/.circleci/check_pr_branch.sh b/.circleci/check_pr_branch.sh deleted file mode 100644 index a46c31ba..00000000 --- a/.circleci/check_pr_branch.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -echo "Working branch is ${CIRCLE_BRANCH}" - -CIRCLE_PR_NUMBER=${CIRCLE_PULL_REQUEST##*/} -echo "PR number is ${CIRCLE_PR_NUMBER}" - -# Make sure that PRs to master are only from the staging branch. -if [[ -n ${CIRCLE_PR_NUMBER} ]] -then - url="https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER"; - target_branch=$( - curl -s -H "Authorization: token $GITHUB_TOKEN" "$url" | jq '.base.ref' | tr -d '"' - ) - - echo "target branch is $target_branch" - if [[ "$target_branch" = "master" && "$CIRCLE_BRANCH" != "staging" ]] - then - echo "You may only submit a PR to master from staging. Merge once to staging then create another PR."; - exit 1; - fi -else - echo "This build isn't associated with a pull request. Set the 'Only build pull requests' setting on circle ci and rebuild."; - exit 1; -fi diff --git a/.circleci/common/upload_to_s3.sh b/.circleci/common/upload_to_s3.sh deleted file mode 100644 index 4c825add..00000000 --- a/.circleci/common/upload_to_s3.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -# use source upload_to_s3.sh to import this function - -#### -# Description: Upload a file to an s3 bucket -# Usage: upload_to_s3 PATH_TO_FILE S3_BUCKET [S3_FILENAME] -# Arguments: -# PATH_TO_FILE: Relative or absolute path to the file to upload. e.g. "./dist/skyway.js" -# S3_BUCKET: The name of the bucket. e.g. "s3://eclrtc-cdn-staging/" -# S3_FILENAME: What to name the file when it is put in the bucket. e.g. "skyway-latest.js" -#### -function upload_to_s3() { - if [[ -z "$1" || -z "$2" ]] - then - echo "${FUNCNAME[0]} requires at least 2 arguments"; - return 1; - fi - - path_to_file=$1; - s3_bucket=$2; - s3_filename=$3; - - s3cmd --no-mime-magic --guess-mime-type put "$path_to_file" "$s3_bucket$s3_filename"; - return $?; -} - -#### -# Description: Upload a directory to an s3 bucket -# Usage: upload_to_s3 PATH_TO_DIRECTORY S3_BUCKET -# Arguments: -# PATH_TO_DIRECTORY: Relative or absolute path to the directory to upload. e.g. "./examples" -# Make sure to use quotes if it contains a wildcard character. e.g. upload_to_s3 "./examples/*" s3://bucket_name -# S3_BUCKET: The name of the bucket. e.g. "s3://eclrtc-cdn-staging/" -#### -function upload_dir_to_s3() { - if [[ -z "$1" || -z "$2" ]] - then - echo "${FUNCNAME[0]} requires at least 2 arguments"; - return 1; - fi - - path_to_directory=$1; - s3_bucket=$2; - - s3cmd --no-mime-magic --guess-mime-type put -r $path_to_directory "$s3_bucket"; - return $?; -} diff --git a/.circleci/config.yml b/.circleci/config.yml index a4394929..1a43435d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,92 +1,66 @@ version: 2 jobs: - test: - docker: - - image: skywayci/skyway-js-sdk - steps: - - checkout - - run: npm install - - - run: npm run lint - - run: npm run test - - build_deploy_staging: + ensure_branch: docker: - image: skywayci/skyway-js-sdk steps: - checkout - - run: npm install - - run: npm run build - - deploy: - name: Deploy ECL Staging - command: bash .circleci/deploy_staging.sh - - deploy: - name: Deploy GCP Staging - command: bash .circleci/deploy_gcp_staging.sh + - run: + name: Install extra deps for CI + command: npm install --no-save @octokit/rest + - run: node ./scripts/ensure-branch - build_deploy_master: + test: docker: - image: skywayci/skyway-js-sdk steps: - checkout - run: npm install + - run: npm run lint + - run: npm run test - run: npm run build - - deploy: - name: Create release - command: bash .circleci/deploy_master/create_release.sh - - deploy: - name: Deploy to s3 - command: bash .circleci/deploy_master/deploy_s3.sh - - deploy: - name: Deploy to npm - command: bash .circleci/deploy_master/deploy_npm.sh - - deploy: - name: Send - command: bash .circleci/deploy_master/send_notification.sh + - persist_to_workspace: + root: . + paths: + - ./* - fail_if_target_is_master: + release_staging: docker: - image: skywayci/skyway-js-sdk - pre: apt-get install -y curl jq steps: - - checkout - - run: bash .circleci/check_pr_branch.sh + - attach_workspace: + at: . + - run: + name: Install extra deps for CI + command: npm install --no-save replace-in-file aws-sdk + - run: node ./scripts/release-staging - check_latest_version: + release_master: docker: - image: skywayci/skyway-js-sdk - pre: apt-get install -y curl jq steps: - - checkout - - run: bash .circleci/check_latest_version.sh + - attach_workspace: + at: . + - run: + name: Install extra deps for CI + command: npm install --no-save replace-in-file aws-sdk @octokit/rest @slack/client + - run: node ./scripts/release-master workflows: version: 2 - test_and_deploy: jobs: - - test - # PRs to master must come from the staging branch. - - fail_if_target_is_master: - filters: - branches: - ignore: - - staging - - master - # Make sure the package.json version and changelog are updaded. - - check_latest_version: - filters: - branches: - only: staging - # Build and deploy to the staging environment - - build_deploy_staging: + - ensure_branch + - test: + requires: + - ensure_branch + - release_staging: requires: - test filters: branches: only: staging - # Build and deploy to the production environment - - build_deploy_master: + - release_master: requires: - test filters: diff --git a/.circleci/deploy_gcp_staging.sh b/.circleci/deploy_gcp_staging.sh deleted file mode 100644 index ab38d224..00000000 --- a/.circleci/deploy_gcp_staging.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -# Import functions -source .circleci/common/upload_to_s3.sh; - -s3_dist_bucket="s3://eclrtc-cdn-gcp-staging/" -s3_example_bucket="s3://eclrtc-example-gcp-staging/" -examples_sdk_url="\/\/cdn.stage.gcp.skyway.io\/skyway-latest.js" -base_domain="\.stage\.gcp\.skyway\.io" - -# Set API key for examples -skyway_apikey="32466e1c-c9fc-4986-a0da-ba0fb96fcdc6" -echo "window.__SKYWAY_KEY__ = '${skyway_apikey}';" > ./examples/_shared/key.js; - -# Replace variable -# TODO: When remove 'Deploy ECL staging', You must change Domain Name from '.stage\.ecl\.skyway\.io' to '.webrtc\.ecl\.ntt\.com' -find examples -name index.html | xargs sed -i -e "s/\"\/\/cdn\.stage\.ecl\.skyway\.io\/skyway-latest\.js\"/\"${examples_sdk_url}\"/g" -find dist -name "*.js" | xargs sed -i -e "s/\.stage\.ecl\.skyway\.io/${base_domain}/g" - -# Upload sdk to s3 -sdk_version=`cat package.json | jq -r .version` -echo "Uploading sdk to s3"; -for path in dist/*; do - filename=$(basename $path); - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-${sdk_version}.${filename#*.}"; - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-latest.${filename#*.}"; -done - -# Upload examples -echo "Uploading examples to s3"; -upload_dir_to_s3 "./examples/*" "$s3_example_bucket" diff --git a/.circleci/deploy_master/create_release.sh b/.circleci/deploy_master/create_release.sh deleted file mode 100644 index 91a8ed3f..00000000 --- a/.circleci/deploy_master/create_release.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -./check_is_new_release.sh; -if [ $? -ne 0 ] -then - echo "Release $tag_name already exists, skipping."; - exit 0; -fi - -# Get release info from CHANGELOG -cl_startline=$(cat CHANGELOG.md | grep -nE "^### " | head -n 1 | cut -d ":" -f 1) -cl_finishline=$(($(cat CHANGELOG.md | grep -nE "^## " | head -n 2 | tail -n 1 | cut -d ":" -f 1) - 1)) -changelog=`sed -n "${cl_startline},${cl_finishline}p" CHANGELOG.md`; -version_num=`cat package.json | jq -r ".version"` - -posix_escaped_changelog=`printf "%q" "$changelog"`; -escaped_changelog=`echo "$posix_escaped_changelog" | sed "s/^\$'\(.*\)'$/\1/"`; - -release_tag_name="v$version_num"; -release_commitish="master"; -release_name="$release_tag_name"; -release_body="\`\`\`\nhttps://cdn.webrtc.ecl.ntt.com/skyway-$version_num.js\n\`\`\`\n$escaped_changelog"; - -release_post_body=`cat << EOS -{ - "tag_name": "${release_tag_name}", - "target_commitish": "${release_commitish}", - "name": "${release_name}", - "body": "${release_body}" -} -EOS -` - -# Create release -url="https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/releases"; -response=`curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" "$url" -d "$release_post_body"`; -release_id=`echo $response | jq -r ".id"`; - -if [[ "$release_id" == "null" ]]; -then - echo "Could not find release id"; - exit 1; -fi - -# Upload builds -url="https://uploads.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/releases/$release_id/assets"; - -for path in dist/*; do - filename=$(basename $path); - echo "Uploading $filename to releases"; - curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/javascript" --data-binary @"./dist/${filename}" "${url}?name=$filename" -done diff --git a/.circleci/deploy_master/deploy_npm.sh b/.circleci/deploy_master/deploy_npm.sh deleted file mode 100644 index 62017834..00000000 --- a/.circleci/deploy_master/deploy_npm.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -./check_is_new_release.sh; -if [ $? -ne 0 ] -then - echo "Release $tag_name already exists, skipping."; - exit 0; -fi - -echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc -npm publish --access public diff --git a/.circleci/deploy_master/deploy_s3.sh b/.circleci/deploy_master/deploy_s3.sh deleted file mode 100644 index 6c64beb7..00000000 --- a/.circleci/deploy_master/deploy_s3.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -./check_is_new_release.sh; -if [ $? -ne 0 ] -then - echo "Release $tag_name already exists, skipping."; - exit 0; -fi - -# Import functions -source .circleci/common/upload_to_s3.sh; - -s3_dist_bucket="s3://eclrtc-cdn-production/" -s3_example_bucket="s3://eclrtc-example-production" -examples_sdk_url="\/\/cdn.webrtc.ecl.ntt.com\/skyway-latest.js" -base_domain="\.webrtc\.ecl\.ntt\.com" - -# Set API key for examples -skyway_apikey="5bea388b-3f95-4e1e-acb5-a34efdd0c480" -echo "window.__SKYWAY_KEY__ = '${skyway_apikey}';" > ./examples/_shared/key.js; - -# Replace variable -find examples -name index.html | xargs sed -i -e "s/\"\/\/cdn\.webrtc\.ecl\.ntt\.com\/skyway-latest\.js\"/\"${examples_sdk_url}\"/g" -find dist -name "*.js" | xargs sed -i -e "s/\.webrtc\.ecl\.ntt\.com/${base_domain}/g" - -# Upload sdk to s3 -sdk_version=`cat package.json | jq -r .version` -echo "Uploading sdk to s3"; -for path in dist/*; do - filename=$(basename $path); - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-${sdk_version}.${filename#*.}"; - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-latest.${filename#*.}"; -done - -# Upload examples -echo "Uploading examples to s3"; -upload_dir_to_s3 "./examples/*" "$s3_example_bucket" diff --git a/.circleci/deploy_master/send_notification.sh b/.circleci/deploy_master/send_notification.sh deleted file mode 100644 index c181e0fa..00000000 --- a/.circleci/deploy_master/send_notification.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -./check_is_new_release.sh; -if [ $? -ne 0 ] -then - echo "Release $tag_name already exists, skipping."; - exit 0; -fi - -# Notify SkyWay team Slack of the new release -cl_startline=$(cat CHANGELOG.md | grep -nE "^### " | head -n 1 | cut -d ":" -f 1) -cl_finishline=$(($(cat CHANGELOG.md | grep -nE "^## " | head -n 2 | tail -n 1 | cut -d ":" -f 1) - 1)) -changelog=`sed -n "${cl_startline},${cl_finishline}p" CHANGELOG.md`; -version_num=`cat package.json | jq -r ".version"` - -curl -X POST $NOTIFICATION_ENDOPOINT --data-urlencode 'payload={ - "username": "release bot", - "icon_emoji": ":tada:", - "text": "<'"$CIRCLE_BUILD_URL"'|skyway-js-sdk version '"$version_num"' released>\n*Change Log*\n```'"$changelog"'```" -}' diff --git a/.circleci/deploy_staging.sh b/.circleci/deploy_staging.sh deleted file mode 100644 index c86c003a..00000000 --- a/.circleci/deploy_staging.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# Stop script if error occurs -set -e -set -o pipefail - -# Import functions -source .circleci/common/upload_to_s3.sh; - -s3_dist_bucket="s3://eclrtc-cdn-staging/" -s3_example_bucket="s3://eclrtc-example-staging/" -examples_sdk_url="\/\/cdn.stage.ecl.skyway.io\/skyway-latest.js" -base_domain="\.stage\.ecl\.skyway\.io" - -# Set API key for examples -skyway_apikey="5bea388b-3f95-4e1e-acb5-a34efdd0c480" -echo "window.__SKYWAY_KEY__ = '${skyway_apikey}';" > ./examples/_shared/key.js; - -# Replace variable -find examples -name index.html | xargs sed -i -e "s/\"\/\/cdn\.webrtc\.ecl\.ntt\.com\/skyway-latest\.js\"/\"${examples_sdk_url}\"/g" -find dist -name "*.js" | xargs sed -i -e "s/\.webrtc\.ecl\.ntt\.com/${base_domain}/g" - -# Upload sdk to s3 -sdk_version=`cat package.json | jq -r .version` -echo "Uploading sdk to s3"; -for path in dist/*; do - filename=$(basename $path); - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-${sdk_version}.${filename#*.}"; - upload_to_s3 "./dist/${filename}" "$s3_dist_bucket" "${filename%%.*}-latest.${filename#*.}"; -done - -# Upload examples -echo "Uploading examples to s3"; -upload_dir_to_s3 "./examples/*" "$s3_example_bucket" diff --git a/scripts/config.js b/scripts/config.js new file mode 100644 index 00000000..d2a932ab --- /dev/null +++ b/scripts/config.js @@ -0,0 +1,35 @@ +module.exports = function(env = 'staging') { + const config = { + staging: { + API_KEY: '32466e1c-c9fc-4986-a0da-ba0fb96fcdc6', + SERVER_DOMAIN: 'stage.gcp.skyway.io', + CDN_DOMAIN: 'cdn.stage.gcp.skyway.io', + S3_SDK_BUCKET: 'eclrtc-cdn-gcp-staging', + S3_EXAMPLES_BUCKET: 'eclrtc-example-gcp-staging', + }, + master: { + API_KEY: '5bea388b-3f95-4e1e-acb5-a34efdd0c480', + // do not need to replace(= default is master) + SERVER_DOMAIN: '', + CDN_DOMAIN: '', + S3_SDK_BUCKET: 'eclrtc-cdn-production', + S3_EXAMPLES_BUCKET: 'eclrtc-example-production', + }, + }[env]; + + return Object.assign( + config, + process.env, + // debug zone + { + // CIRCLE_BRANCH: '', + // CIRCLE_PULL_REQUEST: '', + // CIRCLE_BUILD_URL: '', + // AWS_ACCESS_KEY_ID: '', + // AWS_SECRET_ACCESS_KEY: '', + // NPM_TOKEN: '', + // GITHUB_TOKEN: '', + // NOTIFICATION_ENDOPOINT: '', + } + ); +}; diff --git a/scripts/ensure-branch/fetch-base-branch.js b/scripts/ensure-branch/fetch-base-branch.js new file mode 100644 index 00000000..e4eebea1 --- /dev/null +++ b/scripts/ensure-branch/fetch-base-branch.js @@ -0,0 +1,13 @@ +const Octokit = require('@octokit/rest'); + +module.exports = async function fetchBaseBranch(number, { GITHUB_TOKEN }) { + const octokit = new Octokit({ auth: `token ${GITHUB_TOKEN}` }); + + const { data: { base: { ref } } } = await octokit.pulls.get({ + owner: 'skyway', + repo: 'skyway-js-sdk', + number, + }); + + return ref; +}; diff --git a/scripts/ensure-branch/index.js b/scripts/ensure-branch/index.js new file mode 100644 index 00000000..fc08645c --- /dev/null +++ b/scripts/ensure-branch/index.js @@ -0,0 +1,56 @@ +const config = require('../config'); +const fetchBaseBranch = require('./fetch-base-branch'); + +/** + * We have to take care of these patterns of commit. + * - 1. commit on some PR + * - 2. others + * - 2.1. merge commit of that PR + * - 2.2. commit directly on some branch + * - restricted by GitHub's branch protection + */ +(async function() { + const { CIRCLE_BRANCH, CIRCLE_PULL_REQUEST, GITHUB_TOKEN } = config(); + + // if pattern 2. + if (!CIRCLE_PULL_REQUEST) { + if (CIRCLE_BRANCH === 'master' || CIRCLE_BRANCH === 'staging') { + // 2.1. is OK + console.log( + `This commit may be a merge commit for branch ${CIRCLE_BRANCH}.` + ); + return process.exit(0); + } else { + // 2.2. is NG + throw new Error( + 'This commit is not associated with PR! We should enable branch protection on GitHub.' + ); + } + } + + // else pattern 1. + // eg. https://github.com/skyway/skyway-js-sdk/pull/155 + const prNo = CIRCLE_PULL_REQUEST.split('/').pop(); + + const baseBranch = await fetchBaseBranch(prNo, { GITHUB_TOKEN }); + const currentBranch = CIRCLE_BRANCH; + + console.log(`This PR will be into ${baseBranch} from ${currentBranch}`); + + // The PR matches branch names combination below are only allowed to commit. + // To commit directly is restricted by GitHub's branch protection. + switch (true) { + case baseBranch === 'master' && currentBranch === 'staging': + case baseBranch === 'master' && currentBranch.startsWith('ops/'): + case baseBranch === 'staging' && currentBranch.startsWith('dev/'): + console.log('Branch names are valid ;D'); + break; + default: + throw new Error('The name of current branch is not allowed to merge!'); + } + + process.exit(0); +})().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/release-master/index.js b/scripts/release-master/index.js new file mode 100644 index 00000000..a192a0b9 --- /dev/null +++ b/scripts/release-master/index.js @@ -0,0 +1,88 @@ +const { version } = require('../../package.json'); +const config = require('../config'); +const replaceExamplesApiKey = require('../shared/replace-examples-api-key'); +const uploadSdkToS3 = require('../shared/upload-sdk-to-s3'); +const uploadExamplesToS3 = require('../shared/upload-examples-to-s3'); +const isNewRelease = require('./is-new-release'); +const isReleaseReady = require('./is-release-ready'); +const publishToNpm = require('./publish-to-npm'); +const publishToGitHub = require('./publish-to-github'); +const notifySlack = require('./notify-slack'); + +(async function() { + const { + CIRCLE_BUILD_URL, + API_KEY, + GITHUB_TOKEN, + NPM_TOKEN, + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + S3_SDK_BUCKET, + S3_EXAMPLES_BUCKET, + NOTIFICATION_ENDOPOINT, + } = config('master'); + + console.log('# Release examples'); + console.log('## Replace API key'); + await replaceExamplesApiKey(API_KEY); + console.log(''); + + console.log('## Upload to S3:master'); + await uploadExamplesToS3(S3_EXAMPLES_BUCKET, { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + }); + console.log(''); + + console.log('# Release SDK'); + console.log(`## Check v${version} has not released yet`); + const isNew = await isNewRelease(version, { GITHUB_TOKEN }); + if (!isNew) { + console.log('## Notify to Slack'); + await notifySlack( + `The branch \`master\` updated!\nExamples are released to S3, but SDK is not.\nSee <${CIRCLE_BUILD_URL}|detail>`, + { NOTIFICATION_ENDOPOINT } + ); + + return process.exit(0); + } + + console.log(`## Check v${version} is release ready`); + const isReady = await isReleaseReady(version); + if (!isReady) { + console.log('## Notify to Slack'); + await notifySlack( + `The branch \`master\` updated!\nExamples are released to S3, but SDK is not.\nSee <${CIRCLE_BUILD_URL}|detail>`, + { NOTIFICATION_ENDOPOINT } + ); + + return process.exit(0); + } + + console.log('## Publish to npm'); + await publishToNpm({ NPM_TOKEN }); + console.log(''); + + console.log('## Publish to GitHub'); + await publishToGitHub(version, { GITHUB_TOKEN }); + console.log(''); + + console.log('## Upload to S3:master'); + await uploadSdkToS3(S3_SDK_BUCKET, { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + }); + console.log(''); + + console.log('## Notify to Slack'); + await notifySlack( + `The branch \`master\` updated!\nExamples and SDK are released to S3, SDK published to GitHub and npm.\nSee <${CIRCLE_BUILD_URL}|detail>`, + { NOTIFICATION_ENDOPOINT } + ); + console.log(''); + + process.exit(0); +})().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/release-master/is-new-release.js b/scripts/release-master/is-new-release.js new file mode 100644 index 00000000..f6e08962 --- /dev/null +++ b/scripts/release-master/is-new-release.js @@ -0,0 +1,58 @@ +const execFile = require('util').promisify(require('child_process').execFile); +const Octokit = require('@octokit/rest'); + +module.exports = async function isReleaseReady(version, { GITHUB_TOKEN }) { + console.log(`Release for v${version} not exists on GitHub?`); + const cond1 = await isNewGitHubRelease(version, { GITHUB_TOKEN }); + if (!cond1) { + console.log('=> No. abort release steps'); + console.log(''); + return false; + } + console.log('=> Yes. continue release steps'); + console.log(''); + + console.log(`Release for ${version} not exists on npm?`); + const cond2 = await isNewNpmRelease(version); + if (!cond2) { + console.log('=> No. abort release steps'); + console.log(''); + return false; + } + console.log('=> Yes. continue release steps'); + console.log(''); + + return true; +}; + +async function isNewGitHubRelease(version, { GITHUB_TOKEN }) { + const octokit = new Octokit({ auth: `token ${GITHUB_TOKEN}` }); + + let isNewRelease = false; + + try { + await octokit.repos.getReleaseByTag({ + owner: 'skyway', + repo: 'skyway-js-sdk', + tag: `v${version}`, + }); + } catch (err) { + // reject means not yet released + if (err.status === 404) { + isNewRelease = true; + } + } + + return isNewRelease; +} + +async function isNewNpmRelease(version) { + const { stdout } = await execFile('npm', [ + 'view', + 'skyway-js', + 'versions', + '--json', + ]); + const isNewRelease = JSON.parse(stdout).includes(version) === false; + return isNewRelease; +} diff --git a/scripts/release-master/is-release-ready.js b/scripts/release-master/is-release-ready.js new file mode 100644 index 00000000..af544149 --- /dev/null +++ b/scripts/release-master/is-release-ready.js @@ -0,0 +1,36 @@ +const readline = require('readline'); +const fs = require('fs'); + +module.exports = async function isReleaseReady(version) { + console.log(`Section for v${version} exists in CHANGELOG.md?`); + const cond1 = await hasChangeLog(version); + if (!cond1) { + console.log('=> No. abort release steps'); + console.log(''); + return false; + } + console.log('=> Yes. continue release steps'); + console.log(''); + + return true; +}; + +async function hasChangeLog(version) { + return new Promise(resolve => { + const rl = readline.createInterface({ + input: fs.createReadStream('./CHANGELOG.md'), + crlfDelay: Infinity, + }); + + rl.on('line', line => { + const isVersionLine = line.startsWith('## '); + const isVersionFound = line.includes(`v${version}`); + + if (isVersionLine && isVersionFound) { + return resolve(true); + } + }); + + rl.once('close', () => resolve(false)); + }); +} diff --git a/scripts/release-master/notify-slack.js b/scripts/release-master/notify-slack.js new file mode 100644 index 00000000..d0e594fd --- /dev/null +++ b/scripts/release-master/notify-slack.js @@ -0,0 +1,11 @@ +const { IncomingWebhook } = require('@slack/client'); + +module.exports = async function notifySlack(text, { NOTIFICATION_ENDOPOINT }) { + const webhook = new IncomingWebhook(NOTIFICATION_ENDOPOINT); + + console.log(text); + return webhook.send({ + text, + username: 'JS-SDK release', + }); +}; diff --git a/scripts/release-master/publish-to-github.js b/scripts/release-master/publish-to-github.js new file mode 100644 index 00000000..c6c43a8e --- /dev/null +++ b/scripts/release-master/publish-to-github.js @@ -0,0 +1,91 @@ +const readline = require('readline'); +const fs = require('fs'); +const Octokit = require('@octokit/rest'); + +module.exports = async function publishToGitHub(version, { GITHUB_TOKEN }) { + const octokit = new Octokit({ auth: `token ${GITHUB_TOKEN}` }); + + console.log('Extract section from CHANGELOG.md'); + const changeLog = await getChangeLogSection(version); + const body = [ + '```', + `https://cdn.webrtc.ecl.ntt.com/skyway-${version}.js`, + '```', + ...changeLog, + ].join('\n'); + console.log(''); + + console.log('Create new release'); + const { data: { upload_url } } = await octokit.repos.createRelease({ + owner: 'skyway', + repo: 'skyway-js-sdk', + tag_name: `v${version}`, + target_commitish: 'master', + name: `v${version}`, + body, + }); + console.log(''); + + console.log('Upload release assets'); + const sdkDev = fs.readFileSync('./dist/skyway.js'); + const sdkMin = fs.readFileSync('./dist/skyway.min.js'); + + await Promise.all([ + octokit.repos.uploadReleaseAsset({ + headers: { + 'content-length': sdkDev.length, + 'content-type': 'application/javascript', + }, + url: upload_url, + name: 'skyway.js', + file: sdkDev, + }), + octokit.repos.uploadReleaseAsset({ + headers: { + 'content-length': sdkMin.length, + 'content-type': 'application/javascript', + }, + url: upload_url, + name: 'skyway.min.js', + file: sdkMin, + }), + ]); +}; + +function getChangeLogSection(version) { + return new Promise(resolve => { + const rl = readline.createInterface({ + input: fs.createReadStream('./CHANGELOG.md'), + crlfDelay: Infinity, + }); + + const lines = []; + let isTargetStart = false; + let isTargetEnd = false; + rl.on('line', line => { + // This logic depends on writing format of CHANGELOG.md + // version line must be started with `## ` and described as `v1.0.0` + const isVersionLine = line.startsWith('## '); + const isVersionFound = line.includes(`v${version}`); + + if (isTargetEnd) { + // 4. next section found, quit + resolve(lines); + } else if (isTargetStart === false) { + // 1. target section found + isTargetStart = isVersionLine && isVersionFound; + // just set start flag + } else { + // 2. check end flag + isTargetEnd = isVersionLine && !isVersionFound; + + // 3. slice while start and not end + if (isTargetStart && !isTargetEnd) { + lines.push(line); + } + } + }); + + rl.once('close', () => resolve(lines)); + }); +} diff --git a/scripts/release-master/publish-to-npm.js b/scripts/release-master/publish-to-npm.js new file mode 100644 index 00000000..cb36575e --- /dev/null +++ b/scripts/release-master/publish-to-npm.js @@ -0,0 +1,16 @@ +const execFile = require('util').promisify(require('child_process').execFile); +const exec = require('util').promisify(require('child_process').exec); + +module.exports = async function publishToNpm({ NPM_TOKEN }) { + console.log('Add npm token > .npmrc'); + await exec(`echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc`); + console.log(''); + + const { stderr, stdout } = await execFile('npm', [ + 'publish', + '--access public', + '--tag latest', + ]); + console.log(stderr); + console.log(stdout); +}; diff --git a/scripts/release-staging/index.js b/scripts/release-staging/index.js new file mode 100644 index 00000000..ba4bd009 --- /dev/null +++ b/scripts/release-staging/index.js @@ -0,0 +1,51 @@ +const config = require('../config'); +const replaceExamplesApiKey = require('../shared/replace-examples-api-key'); +const replaceExamplesCdnDomain = require('../shared/replace-examples-cdn-domain'); +const replaceSdkServerDomain = require('../shared/replace-sdk-server-domain'); +const uploadSdkToS3 = require('../shared/upload-sdk-to-s3'); +const uploadExamplesToS3 = require('../shared/upload-examples-to-s3'); + +(async function() { + const { + API_KEY, + CDN_DOMAIN, + SERVER_DOMAIN, + S3_SDK_BUCKET, + S3_EXAMPLES_BUCKET, + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + } = config('staging'); + + console.log('# Release examples'); + console.log('## Replace API key'); + await replaceExamplesApiKey(API_KEY); + console.log(''); + + console.log('## Replace CDN domain'); + await replaceExamplesCdnDomain(CDN_DOMAIN); + console.log(''); + + console.log('## Upload to S3:staging'); + await uploadExamplesToS3(S3_EXAMPLES_BUCKET, { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + }); + console.log(''); + + console.log('# Release SDK'); + console.log('## Replace server domain'); + await replaceSdkServerDomain(SERVER_DOMAIN); + console.log(''); + + console.log('## Upload to S3:staging'); + await uploadSdkToS3(S3_SDK_BUCKET, { + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + }); + console.log(''); + + process.exit(0); +})().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/shared/replace-examples-api-key.js b/scripts/shared/replace-examples-api-key.js new file mode 100644 index 00000000..ea7b56e7 --- /dev/null +++ b/scripts/shared/replace-examples-api-key.js @@ -0,0 +1,15 @@ +const replace = require('replace-in-file'); + +module.exports = async function replaceExamplesApiKey(apiKey) { + const changes = await replace({ + files: './examples/_shared/key.js', + from: '', + to: apiKey, + }); + + if (changes.length) { + console.log('Modified', changes.join(', ')); + } else { + throw new Error('No files were modified!'); + } +}; diff --git a/scripts/shared/replace-examples-cdn-domain.js b/scripts/shared/replace-examples-cdn-domain.js new file mode 100644 index 00000000..c05b5d0c --- /dev/null +++ b/scripts/shared/replace-examples-cdn-domain.js @@ -0,0 +1,15 @@ +const replace = require('replace-in-file'); + +module.exports = async function replaceExamplesCdnDomain(domain) { + const changes = await replace({ + files: './examples/**/*.html', + from: 'cdn.webrtc.ecl.ntt.com', + to: domain, + }); + + if (changes.length) { + console.log('Modified', changes.join(', ')); + } else { + throw new Error('No files were modified!'); + } +}; diff --git a/scripts/shared/replace-sdk-server-domain.js b/scripts/shared/replace-sdk-server-domain.js new file mode 100644 index 00000000..da5adb2a --- /dev/null +++ b/scripts/shared/replace-sdk-server-domain.js @@ -0,0 +1,15 @@ +const replace = require('replace-in-file'); + +module.exports = async function replaceSdkServerDomain(domain) { + const changes = await replace({ + files: './dist/*.js', + from: 'webrtc.ecl.ntt.com', + to: domain, + }); + + if (changes.length) { + console.log('Modified', changes.join(', ')); + } else { + throw new Error('No files were modified!'); + } +}; diff --git a/scripts/shared/upload-examples-to-s3.js b/scripts/shared/upload-examples-to-s3.js new file mode 100644 index 00000000..c71e1b03 --- /dev/null +++ b/scripts/shared/upload-examples-to-s3.js @@ -0,0 +1,78 @@ +const fs = require('fs'); +const path = require('path'); +const AWS = require('aws-sdk'); + +module.exports = async function uploadSdkToS3( + bucket, + { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } +) { + const s3 = new AWS.S3({ + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + }); + + const uploads = readdirRecurseSync('./examples') + .filter(isContents) + .map(filePath => { + // eg. examples/_shared/key.js + const paths = filePath.split('/'); + paths.shift(); + + return { + Key: paths.join('/'), + Body: fs.readFileSync(`./${filePath}`), + ContentType: getContentType(filePath), + }; + }); + + await Promise.all( + uploads.map(upload => { + const params = Object.assign( + { + Bucket: bucket, + }, + upload + ); + console.log(`Uploading s3://${params.Bucket}/${params.Key}`); + return s3.upload(params).promise(); + }) + ); +}; + +function readdirRecurseSync(dName) { + let list = []; + + fs.readdirSync(dName).forEach(fName => { + const filePath = path.join(dName, fName); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + list = list.concat(readdirRecurseSync(filePath)); + } else { + list.push(filePath); + } + }); + + return list; +} + +function isContents(filePath) { + return ( + filePath.endsWith('.html') || + filePath.endsWith('.css') || + filePath.endsWith('.js') + ); +} + +function getContentType(filePath) { + let cType = ''; + if (filePath.endsWith('.html')) { + cType = 'text/html'; + } + if (filePath.endsWith('.css')) { + cType = 'text/css'; + } + if (filePath.endsWith('.js')) { + cType = 'application/javascript'; + } + return cType; +} diff --git a/scripts/shared/upload-sdk-to-s3.js b/scripts/shared/upload-sdk-to-s3.js new file mode 100644 index 00000000..6a680615 --- /dev/null +++ b/scripts/shared/upload-sdk-to-s3.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const AWS = require('aws-sdk'); +const { version } = require('../../package.json'); + +module.exports = async function uploadSdkToS3( + bucket, + { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } +) { + const s3 = new AWS.S3({ + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + }); + + const sdkDev = fs.readFileSync('./dist/skyway.js'); + const sdkMin = fs.readFileSync('./dist/skyway.min.js'); + + const uploads = [ + { Key: `skyway-${version}.js`, Body: sdkDev }, + { Key: `skyway-${version}.min.js`, Body: sdkMin }, + { Key: `skyway-latest.js`, Body: sdkDev }, + { Key: `skyway-latest.min.js`, Body: sdkMin }, + ]; + + await Promise.all( + uploads.map(upload => { + const params = Object.assign( + { + Bucket: bucket, + ContentType: 'application/javascript', + }, + upload + ); + console.log(`Uploading s3://${params.Bucket}/${params.Key}`); + return s3.upload(params).promise(); + }) + ); +};