diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..61a46821 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# Changes under .github/ require code-owner review: workflow files decide what +# runs in CI and with which credentials. +/.github/ @livingdocsIO/devops @livingdocsIO/admins diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a85b301e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,109 @@ +# Replaces .drone.yml. +# +# GitHub-hosted runners on purpose: this repo is public, and self-hosted +# runners must never be reachable from public repositories (a fork PR can run +# arbitrary code). GitHub-hosted minutes are free for public repos. +name: CI + +on: + push: + branches: ['**'] + tags: ['**'] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +permissions: + contents: read + +jobs: + test: + name: Lint, build and test + runs-on: ubuntu-latest + timeout-minutes: 20 + # Chrome for the karma suite comes from the image, not the runner host. + container: + image: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build -s + + # karma.conf.js already runs ChromeHeadlessNoSandbox, single-run + - name: Test + run: npm run test:ci -s + + release: + name: Semantic release + # Only publishes once the whole suite is green on the default branch. + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + needs: test + runs-on: ubuntu-latest + timeout-minutes: 15 + # npm rejects concurrent publishes of the same package, so a repo-wide lock + concurrency: + group: release + cancel-in-progress: false + permissions: + contents: read # the App token below does the writing + steps: + # Tags pushed with the built-in GITHUB_TOKEN never trigger workflows, so + # semantic-release pushes with a livingdocs-release App token instead — + # that is what makes the tag fire the gh-pages job below. + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ vars.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + permission-contents: write + permission-issues: write + permission-pull-requests: write + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # semantic-release needs full history and tags + token: ${{ steps.app-token.outputs.token }} + # CRITICAL: without this the persisted default GITHUB_TOKEN wins the + # push, the tag is attributed to github-actions[bot], and the + # tag-triggered gh-pages job never fires. + persist-credentials: false + + - name: Release + uses: docker://livingdocs/semantic-release:v3.0.3 + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_PUBLISH }} + + publish-docs: + name: Publish examples to gh-pages + if: startsWith(github.ref, 'refs/tags/') + needs: test + runs-on: ubuntu-latest + timeout-minutes: 15 + container: + image: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1 + permissions: + contents: write # pushes the built docs to the gh-pages branch + steps: + - uses: actions/checkout@v4 + + - run: npm ci + + - name: Build docs + run: npm run build -s + + - name: Publish to gh-pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./examples + keep_files: false