From da74c22802d6305e24f1a164fddbc1a7cab4d2a0 Mon Sep 17 00:00:00 2001 From: Viktor Podzigun Date: Mon, 31 Jan 2022 15:58:39 +0100 Subject: [PATCH] Added GitHub CI action (#5) --- .github/workflows/ci.yml | 89 +++++++++++++++++++++++ .travis.yml | 44 ----------- README.md | 2 +- pgp.sbt | 9 +-- project/plugins.sbt | 4 +- project/src/main/scala/common/Libs.scala | 6 +- travis/secrets.tar.enc | Bin 5136 -> 0 bytes 7 files changed, 99 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml delete mode 100644 travis/secrets.tar.enc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..38a7c99 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + push: + branches: [ master ] + tags: + - '*' + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - name: Git checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '12' + + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'adopt' + + - name: Cache sbt + uses: actions/cache@v2 + with: + path: | + ~/.sbt + ~/.ivy2/cache + ~/.coursier/cache/v1 + ~/.cache/coursier/v1 + key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt', 'project/**/*.scala') }} + + - name: Cache node_modules + uses: actions/cache@v2 + with: + path: | + ~/.npm + ~/.nvm + ~/work/scommons-client/scommons-client/showcase/target/scala-2.13/scalajs-bundler/test/node_modules + ~/work/scommons-client/scommons-client/showcase/target/scala-2.13/scalajs-bundler/test/package-lock.json + ~/work/scommons-client/scommons-client/ui/target/scala-2.13/scalajs-bundler/test/node_modules + ~/work/scommons-client/scommons-client/ui/target/scala-2.13/scalajs-bundler/test/package-lock.json + key: ${{ runner.os }}-node_modules-cache-v2-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node_modules-cache-v2- + + - name: Extract Tag Name + run: echo "TAG_NAME=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV + if: ${{ startsWith(github.ref, 'refs/tags') }} + + - name: Run tests + run: | + sbt coverage test + sbt coverageAggregate coveralls + if: ${{ env.TAG_NAME == '' }} + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + + - name: Publish SNAPSHOT + run: sbt clean publish + if: ${{ !github.event.pull_request && env.TAG_NAME == '' }} + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + + - name: Publish RELEASE + run: | + mkdir ./keys + echo $PGP_PUBLIC | base64 --decode > ./keys/pubring.gpg + echo $PGP_SECRET | base64 --decode > ./keys/secring.gpg + VERSION="$(echo "$TAG_NAME" | cut -d'v' -f 2)" + echo "Publish a release version=$VERSION for tag $TAG_NAME" + version=$VERSION sbt clean publishSigned sonatypeBundleRelease + if: ${{ env.TAG_NAME != '' }} + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + PGP_PUBLIC: ${{ secrets.PGP_PUBLIC }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + PGP_PASS: ${{ secrets.PGP_PASS }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ac30660..0000000 --- a/.travis.yml +++ /dev/null @@ -1,44 +0,0 @@ -language: scala -sudo: false -jdk: -- oraclejdk8 -dist: trusty -env: - global: - - TRAVIS_SBT_VERSION="1.2.8" - - secure: mNYTA9y/VA5dhG4Ukg24uRfYEC/s5kMEJKaVg9YdikA9Kad8GlxUVuCmg49BJjGen446NqM9DgrF9/+UTyUkywyC5vqCA+ILI9J3POvkiOhy5A99Ymip5VrYdJEBqTr3SszxkFtZ0nSR8xxN6ypiQ/DDCUMHyYweeX4ZruTcOZKMnTXD9fw/q4WHxL/leO5mSbEVYDh5nhMOxg9etmtezTfW6VRUfe1D7t7odIkIViDuhzIxqHx1J1CqWTC52x6DzbOiEeBrl1ZLl3r+weQA8HtxjTtqC+HutsrsRGwS/0ew+A9jOuOcHSoNIiC1JbSEvTFkvin1jwNqDjj8WPu2vZvGVSukvFQtaH2I5SEBPKBnteoV/4TSGvU6aZ4WdaJIULg3gXeoOSLxzC5WEyqYMBiWWtH3VkAQy6+CknD+TfKuBU0u2EsXxY0ni1mTZPWldoErzPQlDMs4cIfRZ5iaSwoXvC7tLVCGHJfaOKhKwslcspaufUWZcOvgrrNjlnjqghrqQa8GDnLCXcJwal6EMH0RJqgWymZ0JdZUB61pQ9ZD2AylpCdQSUqUDq1gFQyGpN+QIWTNYgteO6zKrZMeKknDh7uCEcovFI9ZmZYoF0NtA2lPGgBg3Zeb41+MUKwVSzQG3QocVzLcHSA66ZV/F4MMzrmZ05jIp7yHxCztmzg= - - secure: ArRCYDfNlBMAQJpRBtFedg6Bw43nvxfkh7Eec0oFUdxrO9z1zf9ezpNeyx67lnJ29E8i9eEWtaT+XE1W8KpewQJHogxq84A1PoVI84VMtzf5NAwjZBN+TV3mPbmme8rADmFAFWBD7zF5eSpNtQkDK2BUjC4uwrGVP0/0K5VhunKTBBg4jW2r+cogncLYJ9KTEpMx9HhO3hOvdnR0v8oqOptlCla6Nmnb8DjyVVbO1fg8pRW2XQ4wE012e7MHRnw0CaRge1kaj8yrRMJhdxFgrY0OdetKFS1DJ1tSeQCT8AdCXErNN8M6lGRiYELpIddEd479NsJ4QDTZ0dU2tHfWHJCakb4CfehJ0Qm6Q4NVQafIAlwLfIROV3TaoZkjS1ZuLhV0az3pdnzNGJGkLmjV41otk1DoboV4lON9rjiyO04Gj53M+ZpmsbjxDQiCU07E5fSrT6Hdnt0Z0RbLBPE/ge21UNK8WBUElD3XMtsFSPvOziJu6iUVxw+CNx1yQVu2ENEDMRKf/qdulx4H2Ee3TIXYtYqMvYnxP2NN/BVJ2cxp+YiNpnzJBl3o6rnvPoF+vTbEKBEqf4cpuITVBADHUHwHsRaSFdTAdcT3/6VhltCcYuPOHWzONlMfQZJLQhi25PTO7L8eImatFKpccPR1Aqido57AYUtzajk9m9pRDDo= - - secure: wRICZjpieMeuW6/Od8l+DqzpLgo5wxofbSGM7TUqP1NaSDBvmCYv9fKzZPsVQqyEdet4Q+xNw+28ymbGo9toToAcdLSvClfCPglpLmg9HoiB3y1xp4cy84/i+riHYz1NbOgFrIf5TsG53IMqd74PAtblbh4JGvQEzILupk45XPGydp7L5sEE5JOibZj6YrFkixbadfCWWFEmwx7SJp83kSOeaq12h3AWoaBmfza4BOYe+NDa2oGaITcz7iWxZUGDdPaWSdZyqOx0lPqprCPMdRwpe+VihrpucH8Qa0aWSxJNCAYNJI8vk95FWbtRnycMRluVdm3v5WWvczNrjyj0aU3n4irtjeV8Gfter1F9bvis7LVfH5i+xTUmqkcrIciIyuODTAdsUiMWBGg9wqCEOrvcAmH+gVC/2a8X/VO0rusE4buGBrQsxCXUGAyswGjaMZHbN6mVdMXHwNPXG6s4nJdUAFGu6ThzeTzuByYUpXj80u6AtbUgmvcAd57lLiLYFfQv8IIqxQ8pf8oRojB/imvXKrouLkxZO8jVoiVvMFy5R8Pcb+UWYUbTW4ylHScOQPkdo4riMa+3qlBcO5g07u7EXSu0GHO6y/ci4scLNFkwdCoVjcf42oeFZOlrKJ4VV4VVBBA4uQ7mflRlSX0l3PlUg4A+dY5J6CiMjE5z2z4= -before_install: -- nvm install 12 && - nvm use 12 -- node --version && - npm --version -- if [ $TRAVIS_PULL_REQUEST = 'false' ]; then - openssl aes-256-cbc -K $encrypted_298c6ae3b350_key -iv $encrypted_298c6ae3b350_iv -in travis/secrets.tar.enc -out travis/secrets.tar -d; - tar xv -C travis -f travis/secrets.tar; - fi -script: -- sbt clean coverage test && - sbt coverageAggregate coveralls && - if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" -o "$TRAVIS_BRANCH" == "$TRAVIS_TAG" ]; then - if [ -z "$TRAVIS_TAG" ]; then - echo "Publish a snapshot"; - sbt clean publish; - else - echo "Publish a release version=$TRAVIS_TAG"; - version=$TRAVIS_TAG sbt clean publishSigned sonatypeBundleRelease; - fi - else - echo "This is not a master branch commit. Skipping the publish/release step"; - fi -cache: - directories: - - ~/.npm - - ~/.nvm - - "$HOME/.ivy2/cache" - - "$HOME/.sbt" -before_cache: -# Cleanup the cached directories to avoid unnecessary cache updates -- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete -- find $HOME/.sbt -name "*.lock" -print -delete diff --git a/README.md b/README.md index 973558d..db55117 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Build Status](https://travis-ci.com/scommons/scommons-client.svg?branch=master)](https://travis-ci.com/scommons/scommons-client) +[![CI](https://github.com/scommons/scommons-client/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/scommons/scommons-client/actions/workflows/ci.yml?query=workflow%3Aci+branch%3Amaster) [![Coverage Status](https://coveralls.io/repos/github/scommons/scommons-client/badge.svg?branch=master)](https://coveralls.io/github/scommons/scommons-client?branch=master) [![scala-index](https://index.scala-lang.org/scommons/scommons-client/scommons-client-ui/latest.svg)](https://index.scala-lang.org/scommons/scommons-client/scommons-client-ui) [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.1.0.svg)](https://www.scala-js.org) diff --git a/pgp.sbt b/pgp.sbt index b25983a..5485218 100644 --- a/pgp.sbt +++ b/pgp.sbt @@ -1,7 +1,6 @@ +import java.util.Base64 //see: https://www.scala-sbt.org/sbt-pgp/usage.html - -useGpg := false -pgpPublicRing := file("./travis/pubring.gpg") -pgpSecretRing := file("./travis/secring.gpg") -pgpPassphrase := sys.env.get("PGP_PASS").map(_.toArray) +pgpPublicRing := file("./keys/pubring.gpg") +pgpSecretRing := file("./keys/secring.gpg") +pgpPassphrase := sys.env.get("PGP_PASS").map(p => new String(Base64.getDecoder.decode(p)).toArray) diff --git a/project/plugins.sbt b/project/plugins.sbt index f243d50..42ae223 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ //resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/" -addSbtPlugin(("org.scommons.sbt" % "sbt-scommons-plugin" % "0.8.0-SNAPSHOT").changing()) -//addSbtPlugin("org.scommons.sbt" % "sbt-scommons-plugin" % "0.8.0") +//addSbtPlugin(("org.scommons.sbt" % "sbt-scommons-plugin" % "0.8.0-SNAPSHOT").changing()) +addSbtPlugin("org.scommons.sbt" % "sbt-scommons-plugin" % "0.8.0") addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.4.3") diff --git a/project/src/main/scala/common/Libs.scala b/project/src/main/scala/common/Libs.scala index 8037849..aef07a4 100644 --- a/project/src/main/scala/common/Libs.scala +++ b/project/src/main/scala/common/Libs.scala @@ -6,9 +6,9 @@ import scommons.sbtplugin.project.CommonLibs object Libs extends CommonLibs { - val scommonsNodejsVersion = "0.7.0" - val scommonsReactVersion = "1.0.0-SNAPSHOT" - private val scommonsApiVersion = "0.7.0" + val scommonsNodejsVersion = "0.8.0" + val scommonsReactVersion = "0.8.0" + private val scommonsApiVersion = "0.8.0" private val sjsReactJsVer = "0.17.1" lazy val scommonsApiXhr = Def.setting("org.scommons.api" %%% "scommons-api-xhr" % scommonsApiVersion) diff --git a/travis/secrets.tar.enc b/travis/secrets.tar.enc deleted file mode 100644 index fecaaf999d46538df21163646747ff2a14906569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5136 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