diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..284470c76b2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: '/' + schedule: + interval: daily + commit-message: + # prevent netlify build + prefix: "[skip ci]" + open-pull-requests-limit: 99 + versioning-strategy: increase-if-necessary diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000000..5271f79dcfb --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,13 @@ +name: Auto Merge Dependency Updates + +on: + - pull_request + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: tjenkinson/gh-action-auto-merge-dependency-updates@1ff3f19 + with: + repo-token: ${{ secrets.CI_GITHUB_TOKEN }} + allowed-actors: dependabot[bot] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..988e7041a7a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,427 @@ +name: Build + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + +jobs: + config: + runs-on: ubuntu-latest + outputs: + canUseSauce: ${{ steps.check_sauce_access.outputs.result == 'true' }} + tag: ${{ steps.extract_tag.outputs.result }} + isMainBranch: ${{ github.ref == 'refs/heads/master' }} + steps: + - name: check sauce access + id: check_sauce_access + run: | + if ! [[ -z "$SAUCE_USERNAME" ]] && ! [[ -z "$SAUCE_ACCESS_KEY" ]]; then + echo "::set-output name=result::true" + fi + env: + CI: true + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + - name: extract tag + id: extract_tag + uses: actions/github-script@v3 + with: + script: | + const prefix = 'refs/tags/'; + const ref = context.ref; + return ref.startsWith(prefix) ? ref.substring(prefix.length) : ''; + result-encoding: string + + build: + needs: config + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: install + run: | + npm ci + env: + CI: true + + - name: set version + run: | + node ./scripts/set-package-version.js + env: + CI: true + TAG: ${{ needs.config.outputs.tag }} + + - name: build + run: | + npm run lint + npm run type-check + npm run build + npm run docs + # check that hls.js doesn't error if requiring in node + # see https://github.com/video-dev/hls.js/pull/1642 + node -e 'require("./" + require("./package.json").main)' + env: + CI: true + + - name: upload build + uses: actions/upload-artifact@v2 + with: + name: build + path: | + ** + !**/[.]*/** + !**/node_modules/ + + test_unit: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: install + run: | + npm ci + env: + CI: true + + - name: run unit tests + run: | + npm run test:unit + env: + CI: true + + netlify: + needs: [config, test_unit] + if: needs.config.outputs.tag || needs.config.outputs.isMainBranch == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: install + run: | + npm ci + env: + CI: true + + - name: build netlify + run: | + ./scripts/build-netlify.sh + env: + CI: true + + - name: deploy netlify + run: | + ./scripts/deploy-netlify.sh + env: + CI: true + GITHUB_TOKEN: ${{ secrets.CI_GITHUB_TOKEN }} + NETLIFY_ACCESS_TOKEN: ${{ secrets.NETLIFY_ACCESS_TOKEN }} + + release_github: + needs: [config, test_unit] + if: needs.config.outputs.tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: build release zip + run: | + zip -r dist.zip dist + + - name: create github release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: upload assets to github release + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist.zip + asset_name: release.zip + asset_content_type: application/zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release_npm: + needs: [config, test_unit] + if: needs.config.outputs.tag || needs.config.outputs.isMainBranch == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: install + run: | + npm ci + env: + CI: true + + - name: publish to npm + run: | + ./scripts/publish-npm.sh + env: + CI: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + TAG: ${{ needs.config.outputs.tag }} + + test_functional_required: + needs: [config, test_unit] + if: needs.config.outputs.canUseSauce == 'true' + runs-on: ubuntu-latest + strategy: + fail-fast: true + max-parallel: 8 + matrix: + config: [chrome-win_10] + + include: + - config: chrome-win_10 + ua: chrome + os: Windows 10 + + steps: + - uses: actions/checkout@v2 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: start SauceConnect tunnel + uses: saucelabs/sauce-connect-action@a0930f1 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ github.run_id }}-${{ matrix.config }} + + - name: install + run: | + npm ci + env: + CI: true + + - name: run functional tests + run: | + npm run test:func + env: + CI: true + SAUCE_TUNNEL_ID: ${{ github.run_id }}-${{ matrix.config }} + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + UA: ${{ matrix.ua }} + UA_VERSION: ${{ matrix.uaVersion }} + OS: ${{ matrix.os }} + + test_functional_optional: + needs: test_functional_required + runs-on: ubuntu-latest + continue-on-error: true + strategy: + fail-fast: false + max-parallel: 8 + matrix: + config: + - safari-osx_10.15 + - firefox-win_10 + - chrome-osx_10.11-79.0 + - internet_explorer-win_10 + - internet_explorer-win_8.1-11.0 + - chrome-win_7-69.0 + - safari-osx_10.12-10.1 + + include: + - config: safari-osx_10.15 + ua: safari + os: OS X 10.15 + - config: firefox-win_10 + ua: firefox + os: Windows 10 + - config: chrome-osx_10.11-79.0 + ua: chrome + os: OS X 10.11 + uaVersion: "79.0" + - config: internet_explorer-win_10 + ua: internet explorer + os: Windows 10 + - config: internet_explorer-win_8.1-11.0 + ua: internet explorer + os: Windows 8.1 + uaVersion: "11.0" + - config: chrome-win_7-69.0 + ua: chrome + os: Windows 7 + uaVersion: "69.0" + - config: safari-osx_10.12-10.1 + ua: safari + os: OS X 10.12 + uaVersion: "10.1" + + steps: + - uses: actions/checkout@v2 + + - name: cache node_modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: use Node.js + uses: actions/setup-node@v1 + with: + node-version: "12" + + - name: download build + uses: actions/download-artifact@v2 + with: + name: build + + - name: start SauceConnect tunnel + uses: saucelabs/sauce-connect-action@a0930f1 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ github.run_id }}-${{ matrix.config }} + + - name: install + run: | + npm ci + env: + CI: true + + - name: run functional tests + run: | + npm run test:func + env: + CI: true + SAUCE_TUNNEL_ID: ${{ github.run_id }}-${{ matrix.config }} + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + UA: ${{ matrix.ua }} + UA_VERSION: ${{ matrix.uaVersion }} + OS: ${{ matrix.os }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..69a320d8581 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,31 @@ +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + - cron: '0 3 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['javascript'] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/scripts/deploy-netlify.sh b/scripts/deploy-netlify.sh index c0d6398d66f..43d281a7fa1 100755 --- a/scripts/deploy-netlify.sh +++ b/scripts/deploy-netlify.sh @@ -1,14 +1,9 @@ #!/bin/bash set -e -# GITHUB_TOKEN and NETLIFY_ACCESS_TOKEN set in travis - -# ensure we have fetched origin/master -git remote set-branches origin master -git fetch +# GITHUB_TOKEN and NETLIFY_ACCESS_TOKEN required currentCommit=$(git rev-parse HEAD) -masterLatestCommit=$(git rev-parse origin/master) id=$currentCommit root="./netlify" diff --git a/scripts/netlify.sh b/scripts/netlify.sh new file mode 100755 index 00000000000..331cb5340be --- /dev/null +++ b/scripts/netlify.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +if [[ $(git rev-parse --is-shallow-repository) = "true" ]]; then + # make sure everything is fetched + git fetch --unshallow +fi + +npm ci +node ./scripts/set-package-version.js +npm run lint +npm run type-check +npm run build:ci +npm run docs +./scripts/build-netlify.sh diff --git a/scripts/publish-npm.sh b/scripts/publish-npm.sh new file mode 100755 index 00000000000..50b92440ea5 --- /dev/null +++ b/scripts/publish-npm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +if [[ $(node ./scripts/check-already-published.js) = "not published" ]]; then + # write the token to config + # see https://docs.npmjs.com/private-modules/ci-server-config + echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc + if [[ -z "$TAG" ]]; then + npm publish --tag canary + echo "Published canary." + curl https://purge.jsdelivr.net/npm/hls.js@canary + curl https://purge.jsdelivr.net/npm/hls.js@canary/dist/hls-demo.js + echo "Cleared jsdelivr cache." + else + tag=$(node ./scripts/get-version-tag.js) + if [ "${tag}" = "canary" ]; then + # canary is blocked because this is handled separately on every commit + echo "canary not supported as explicit tag" + exit 1 + fi + echo "Publishing tag: ${tag}" + npm publish --tag "${tag}" + curl "https://purge.jsdelivr.net/npm/hls.js@${tag}" + echo "Published." + fi +else + echo "Already published." +fi diff --git a/scripts/set-package-version.js b/scripts/set-package-version.js index 71951050ede..ac7984022a9 100755 --- a/scripts/set-package-version.js +++ b/scripts/set-package-version.js @@ -5,34 +5,33 @@ const versionParser = require('./version-parser.js'); const packageJson = require('../package.json'); const { isValidStableVersion, incrementPatch } = require('./version-parser.js'); -const TRAVIS_MODE = process.env.TRAVIS_MODE; const latestVersion = getLatestVersionTag(); let newVersion = ''; try { - if (TRAVIS_MODE === 'release') { + if (process.env.TAG) { // write the version field in the package json to the version in the git tag - const tag = process.env.TRAVIS_TAG; + const tag = process.env.TAG; if (!versionParser.isValidVersion(tag)) { throw new Error(`Unsupported tag for release: "${tag}"`); } // remove v newVersion = tag.substring(1); - if (!versionParser.isDefinitelyGreaterThanAlphas(newVersion)) { - // 1.2.3-0.alpha.500 - // 1.2.3-0.alpha.501 - // 1.2.3-0.aaalpha.custom => bad - // 1.2.3-0.aaalpha.custom.0.alpha.503 => now lower than 1.2.3-0.alpha.501 - throw new Error(`It's possible that "${newVersion}" has a lower precedense than an existing alpha version which is not allowed.`); + if (!versionParser.isDefinitelyGreaterThanCanaries(newVersion)) { + // 1.2.3-0.canary.500 + // 1.2.3-0.canary.501 + // 1.2.3-0.caaanary.custom => bad + // 1.2.3-0.caaanary.custom.0.canary.503 => now lower than 1.2.3-0.canary.501 + throw new Error(`It's possible that "${newVersion}" has a lower precedense than an existing canary version which is not allowed.`); } - } else if (TRAVIS_MODE === 'releaseAlpha' || TRAVIS_MODE === 'netlifyPr' || TRAVIS_MODE === 'netlifyBranch') { + } else { // bump patch in version from latest git tag let intermediateVersion = latestVersion; const isStable = isValidStableVersion(intermediateVersion); // if last git tagged version is a prerelease we should append `.0..` // if the last git tagged version is a stable version then we should append `-0..` and increment the patch - // `type` can be `pr`, `branch`, or `alpha` + // `type` can be `pr`, `branch`, or `canary` if (isStable) { intermediateVersion = incrementPatch(intermediateVersion); } @@ -40,15 +39,13 @@ try { // remove v intermediateVersion = intermediateVersion.substring(1); - const suffix = TRAVIS_MODE === 'netlifyPr' + const suffix = process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview' ? `pr.${process.env.REVIEW_ID/* set by netlify */}.${getCommitHash().substr(0, 8)}` - : TRAVIS_MODE === 'netlifyBranch' + : process.env.NETLIFY && process.env.CONTEXT === 'branch-deploy' ? `branch.${process.env.BRANCH/* set by netlify */.replace(/[^a-zA-Z0-9]/g, '-')}.${getCommitHash().substr(0, 8)}` - : `0.alpha.${getCommitNum()}`; + : `0.canary.${getCommitNum()}`; newVersion = `${intermediateVersion}${isStable ? '-' : '.'}${suffix}`; - } else { - throw new Error('Unsupported travis mode: ' + TRAVIS_MODE); } if (!versionParser.isGreaterOrEqual(newVersion, latestVersion)) { diff --git a/scripts/version-parser.js b/scripts/version-parser.js index 0ee70d25a1f..b04f3c7c94b 100644 --- a/scripts/version-parser.js +++ b/scripts/version-parser.js @@ -23,17 +23,17 @@ module.exports = { return semver.gte(newVersion, previousVersion); }, // returns true if the provided version is definitely greater than any existing - // auto generated alpha versions - isDefinitelyGreaterThanAlphas: (version) => { + // auto generated canary versions + isDefinitelyGreaterThanCanaries: (version) => { const parsed = semver.parse(version, { loose: false, includePrerelease: true }); if (!parsed) { throw new Error('Error parsing version.'); } - // anything after a part of `0` must be greater than `alpha` + // anything after a part of `0` must be greater than `canary` let hadZero = false; return parsed.prerelease.every((part) => { - if (hadZero && part <= 'alpha') { + if (hadZero && part <= 'canary') { return false; } else { hadZero = false;