fix(dev): refuse a frontend port with no registered OAuth callback #257
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| pull_request: | |
| # Least-privilege: nothing here needs write access to the repo. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Root-level tooling only (package.json at the repo root) - none of this | |
| # needs PHP/Composer or nuxt/'s own dependencies. | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 2 | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| # 22, not this repo's pinned 16 (or the other jobs' 20) - the | |
| # lint tools themselves need it: cspell requires >=22.18.0, | |
| # markdownlint-cli2 >=22, commitlint >=22.12.0. This job only | |
| # runs root-level tooling, never touches the app's own runtime. | |
| node-version: '22' | |
| cache: 'npm' | |
| - run: npm install | |
| - name: ESLint (scripts/) | |
| run: npm run lint:js | |
| - name: Prettier | |
| run: npm run lint:format | |
| - name: cspell | |
| run: npm run lint:cspell | |
| - name: markdownlint | |
| run: npm run lint:md | |
| - name: knip (unused/unlisted dependencies) | |
| run: npm run lint:knip | |
| - name: npm audit (production dependencies) | |
| run: npm run lint:audit | |
| - name: renovate-config-validator | |
| run: npm run lint:renovate | |
| # This repository is public and is what people copy to start a | |
| # site, so a URL only the author can reach is a defect in the | |
| # published artefact, not a stray comment. | |
| - name: Private host references | |
| run: npm run lint:private | |
| # Only checks the most recent commit - good enough to catch the | |
| # common case (a single non-conforming commit message) without the | |
| # complexity of resolving a full PR commit range. | |
| - name: commitlint | |
| run: npx commitlint --from HEAD~1 --to HEAD --verbose | |
| - name: JSON sweep | |
| run: | | |
| rc=0; count=0 | |
| while IFS= read -r -d '' f; do | |
| count=$((count + 1)) | |
| if python3 -m json.tool "$f" > /dev/null 2>&1; then | |
| echo " [PASS] $f" | |
| else | |
| echo " [FAIL] $f" | |
| rc=1 | |
| fi | |
| done < <(find . -name '*.json' -not -path '*/.git/*' -not -path '*/node_modules/*' \ | |
| -not -path '*/nuxt/*' -not -path '*/drupal/*' -not -path '*/.vscode/*' -not -path '*/.devcontainer/*' -print0) | |
| echo "--- $count JSON file(s) checked ---" | |
| exit "$rc" | |
| - name: Shell syntax sweep | |
| run: | | |
| rc=0; count=0 | |
| while IFS= read -r -d '' f; do | |
| count=$((count + 1)) | |
| if bash -n "$f" 2>/dev/null; then | |
| echo " [PASS] $f" | |
| else | |
| echo " [FAIL] $f" | |
| rc=1 | |
| fi | |
| done < <(find . -name '*.sh' -not -path '*/.git/*' -not -path '*/node_modules/*' \ | |
| -not -path '*/nuxt/*' -not -path '*/drupal/*' -print0) | |
| echo "--- $count shell file(s) checked ---" | |
| exit "$rc" | |
| - name: yamllint | |
| run: | | |
| pip install yamllint -q | |
| yamllint -d "{extends: default, rules: {line-length: {max: 200, level: warning}}}" .gitlab-ci.yml .github/workflows/ci.yml | |
| # Prose-quality lint for README.md - see .vale.ini for scoping/exceptions. | |
| - name: Install Vale | |
| run: | | |
| vale_version="3.17.1" | |
| vale_sha256="db947f89f2292e6a0381a61de155f6a5f5cb4cb460ca178ea412ef605559cefd" | |
| curl -sL --max-time 60 "https://github.com/vale-cli/vale/releases/download/v${vale_version}/vale_${vale_version}_Linux_64-bit.tar.gz" -o /tmp/vale.tar.gz | |
| echo "${vale_sha256} /tmp/vale.tar.gz" | sha256sum -c - || { echo "vale checksum mismatch" >&2; exit 1; } | |
| sudo tar -xzf /tmp/vale.tar.gz -C /usr/local/bin vale | |
| sudo chmod +x /usr/local/bin/vale | |
| - name: Install ai-tells style package | |
| run: | | |
| ai_tells_version="1.31.0" | |
| ai_tells_sha256="bc1267248f13e65928475c439ad7ae1bf806a20d09254c08d7d8c4a9c8b811f0" | |
| curl -sL --max-time 60 "https://github.com/tbhb/vale-ai-tells/releases/download/v${ai_tells_version}/ai-tells.zip" -o /tmp/ai-tells.zip | |
| echo "${ai_tells_sha256} /tmp/ai-tells.zip" | sha256sum -c - || { echo "ai-tells checksum mismatch" >&2; exit 1; } | |
| python3 -c " | |
| import zipfile, os | |
| with zipfile.ZipFile('/tmp/ai-tells.zip') as z: | |
| for name in z.namelist(): | |
| if name.startswith('ai-tells/styles/') and not name.endswith('/'): | |
| target = os.path.join('styles', name[len('ai-tells/styles/'):]) | |
| os.makedirs(os.path.dirname(target), exist_ok=True) | |
| with open(target, 'wb') as f: | |
| f.write(z.read(name)) | |
| " | |
| - name: Vale | |
| run: vale README.md | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.4' | |
| extensions: mbstring, pdo_sqlite, intl, gd, xml, zip, opcache | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: .nvmrc | |
| # setup-node v5 turned caching on by itself when package.json | |
| # names a package manager, and keys it on the root lockfile | |
| # alone. This job installs from nuxt/, so name both. | |
| cache: npm | |
| cache-dependency-path: | | |
| package-lock.json | |
| nuxt/package-lock.json | |
| - name: Validate and install Composer dependencies | |
| working-directory: drupal | |
| run: composer validate --strict && composer install --no-interaction --no-progress | |
| # Not `npm run build` - Druxt fetches the JSON:API index at build | |
| # time, so building needs a live Drupal backend. That happens in | |
| # test_e2e, via .devtools/. | |
| - name: Install Nuxt dependencies | |
| working-directory: nuxt | |
| run: npm install | |
| - name: Create .env | |
| run: cp .env.example .env | |
| # Lint + unit tests were ported from the old auto-running | |
| # test-preview.yml, which is now a manual-only preview workflow. | |
| - name: Lint code | |
| working-directory: nuxt | |
| run: npm run lint | |
| - name: Run unit tests | |
| working-directory: nuxt | |
| run: npm run test:unit | |
| # fail_ci_if_error is false (the old workflow had true): tokenless | |
| # uploads for public repos are rate-limited and shouldn't fail CI on | |
| # a starter kit. The token-less integration itself is unchanged. | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| files: ./nuxt/coverage/clover.xml | |
| name: codecov-umbrella | |
| fail_ci_if_error: false | |
| # Protected branches reject tokenless uploads ("Token required | |
| # because branch is protected"), which is what leaves the README | |
| # coverage badge empty. Needs a CODECOV_TOKEN repository secret. | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| # The root scripts run on the app's Node 16, but coverage reporting | |
| # needs Node 20+, so the tests run on a modern Node here. They use only | |
| # node:test and node:assert, so nothing is installed. | |
| test_scripts: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| - name: Run the script tests with coverage | |
| run: npm run test:scripts:coverage | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| files: ./coverage/scripts-lcov.info | |
| name: scripts | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| test_e2e: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.4' | |
| extensions: mbstring, pdo_sqlite, intl, gd, xml, zip, opcache | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: .nvmrc | |
| # setup-node v5 turned caching on by itself when package.json | |
| # names a package manager, and keys it on the root lockfile | |
| # alone. This job installs from nuxt/, so name both. | |
| cache: npm | |
| cache-dependency-path: | | |
| package-lock.json | |
| nuxt/package-lock.json | |
| - name: Assemble, provision, and start the Drupal backend | |
| working-directory: drupal | |
| run: | | |
| .devtools/assemble | |
| .devtools/provision | |
| .devtools/start | |
| env: | |
| WEBSERVER_HOST: 127.0.0.1 | |
| WEBSERVER_PORT: 8888 | |
| # Anonymous JSON:API never touches OAuth, so nothing else here | |
| # notices a consumer the backend cannot look up. | |
| - name: OAuth consumer is recognised | |
| run: npm run check:oauth | |
| - name: Install Nuxt dependencies | |
| working-directory: nuxt | |
| run: npm install | |
| - name: Install Cypress binary | |
| working-directory: nuxt | |
| run: npx cypress install | |
| - name: Run e2e tests | |
| working-directory: nuxt | |
| run: npm run test:e2e | |
| - name: Upload Cypress artifacts on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: cypress-artifacts | |
| path: | | |
| nuxt/cypress/screenshots | |
| nuxt/cypress/videos | |
| # The documented consumer flow (README: `npx giget ... --install`) is | |
| # not what the jobs above exercise - they work from the git checkout | |
| # and call .devtools/ directly. This job consumes the same artifact a | |
| # giget user gets: a tarball of this exact commit, no .git, no | |
| # preinstalled dependencies - and lets the root postinstall stand the | |
| # whole site up. | |
| test_giget: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.4' | |
| extensions: mbstring, pdo_sqlite, intl, gd, xml, zip, opcache | |
| # giget needs a modern Node, so it runs on the runner's default; | |
| # the site itself gets the repo's pinned Node below, same as a | |
| # consumer following the README's mise/nvm instructions. | |
| - name: Fetch this commit the way giget consumers do | |
| env: | |
| # The branch tip can move between trigger and job start; the | |
| # SHA cannot. Fork PRs need the head repo, where that SHA | |
| # actually lives. | |
| REPO: ${{ github.event.pull_request.head.repo.full_name || github.repository }} | |
| SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| GIGET_AUTH: ${{ github.token }} | |
| # Pinned: this step runs with GIGET_AUTH, so an unpinned npx giget | |
| # would hand a future registry release the workflow token. | |
| run: npx giget@3.3.1 "gh:${REPO}#${SHA}" site | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: site/.nvmrc | |
| # `env -u CI`: postinstall deliberately steps aside on CI machines | |
| # - this job's whole point is to behave like a consumer machine. | |
| - name: Root npm install (giget's --install step) | |
| working-directory: site | |
| run: env -u CI npm install | |
| - name: Backend is up and provisioned | |
| working-directory: site | |
| run: | | |
| . ./.env | |
| curl -sf "${BASE_URL}/jsonapi" | grep -q '"jsonapi"' | |
| - name: OAuth consumer is recognised | |
| working-directory: site | |
| run: npm run check:oauth | |
| - name: Frontend builds against the live backend | |
| working-directory: site/nuxt | |
| run: npm run build | |
| # What the root install promises on machines without a working PHP: a | |
| # giget consumer's `npm install` must never fail, `npm run setup` must | |
| # fail loudly, and a too-old PHP must be rejected by the preflight. | |
| test_install_guardrails: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: .nvmrc | |
| - name: Hide the runner's preinstalled PHP | |
| run: while command -v php >/dev/null 2>&1; do sudo mv "$(command -v php)" "$(command -v php).hidden"; done | |
| - name: npm install without PHP succeeds with guidance | |
| run: | | |
| env -u CI npm install > /tmp/install.log 2>&1 | |
| grep -q 'The backend needs PHP' /tmp/install.log | |
| - name: npm run setup without PHP fails with guidance | |
| run: | | |
| if npm run setup > /tmp/setup.log 2>&1; then echo "setup should have failed"; exit 1; fi | |
| grep -q 'Missing required tools' /tmp/setup.log | |
| - name: Old PHP is rejected by the version preflight | |
| run: | | |
| SHIM=$(mktemp -d) | |
| printf '#!/bin/sh\necho 8.2.29\n' > "$SHIM/php" | |
| printf '#!/bin/sh\nexit 0\n' > "$SHIM/composer" | |
| chmod +x "$SHIM/php" "$SHIM/composer" | |
| if PATH="$SHIM:$PATH" npm run setup > /tmp/old-php.log 2>&1; then echo "setup should have failed"; exit 1; fi | |
| grep -q 'too old' /tmp/old-php.log |