Skip to content

fix(menu): fetch the menu again when the store is flushed while it loads #821

fix(menu): fetch the menu again when the store is flushed while it loads

fix(menu): fetch the menu again when the store is flushed while it loads #821

Workflow file for this run

name: CI
on:
push:
branches: [develop, main]
pull_request:
# No branch filter on purpose. The filter matches the pull request's base,
# so a stacked pull request based on anything unlisted ran no jobs at all,
# and showed no checks rather than failing ones. Listing prefixes only moved
# the hole: feature/** was checked while perf/** and fix/** were not.
# Stacking is only useful if the upper pull request is verified before its
# base merges, and that has to hold for every branch name.
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Classifies what a pull request touches, so the slow jobs and steps run
# only when their subject changed. Push events (develop/main, the
# production path) report everything as changed, so full coverage always
# holds where it matters. If the file listing fails, everything defaults
# to changed: fail open, never silently skip.
changes:
runs-on: ubuntu-latest
outputs:
deps: ${{ steps.classify.outputs.deps }}
packages: ${{ steps.classify.outputs.packages }}
steps:
- id: classify
env:
GH_TOKEN: ${{ github.token }}
run: |
deps=true; packages=true
if [ "${{ github.event_name }}" = "pull_request" ]; then
if files=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --paginate --jq '.[].filename'); then
deps=false; packages=false
echo "$files" | grep -qE '(^|/)(yarn\.lock|package\.json|\.yarnrc\.yml)$' && deps=true
echo "$files" | grep -qE '^packages/' && packages=true
else
echo "file listing failed; running everything" >&2
fi
fi
{
echo "deps=$deps"
echo "packages=$packages"
} >> "$GITHUB_OUTPUT"
# Every commit in a pull request: Conventional Commits, made by the person
# contributing it, with no AI attribution in the message. The commit-msg
# and pre-push hooks run the same checks. This catches a commit made
# without them.
commits:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
# A pull request cannot loosen the policy that checks its own commits.
# commitlint runs from a worktree of the base commit, with the base's
# package.json, lockfile and commitlint.config.js, so the pull request
# can't swap the script, the dependencies or the rules.
- name: Check out the base branch's commit policy
run: git worktree add .policy "$BASE"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .policy/.yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('.policy/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
working-directory: .policy
# Conventional Commits and no AI attribution lines (commitlint.config.js).
- name: Commit messages
run: yarn lint:commit --from "$BASE" --to "$HEAD" --verbose
working-directory: .policy
# Author and committer are the contributor, not an AI agent. The pull
# request's list is used only where the base has none yet.
- name: Commit identity
run: |
identities=.policy/.husky/agent-identities
[ -f "$identities" ] || identities=.husky/agent-identities
if git log --format='%an <%ae>%n%cn <%ce>' "$BASE..$HEAD" | grep -iEf "$identities"; then
echo "::error::A commit above is made under an AI agent's identity. See AGENTS.md."
exit 1
fi
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- run: yarn build
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: build-output
path: |
packages/*/dist
packages/*/CHANGELOG.md
retention-days: 1
lint:
needs: [build, changes]
runs-on: ubuntu-latest
permissions:
contents: read
statuses: write # Bundlewatch posts a commit status via the Statuses API.
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- name: ESLint
run: yarn lint --format ./node_modules/eslint-junit/index.js
env:
ESLINT_JUNIT_OUTPUT: ./reports/junit/eslint.xml
- name: markdownlint
run: yarn lint:md
- name: CSpell
run: yarn lint:cspell
- name: Vale (AI-tell prose lint)
run: yarn lint:prose
- name: Em-dash check (hand-authored prose)
run: |
! grep -rn "\xe2\x80\x94" packages/druxt/README.md packages/docgen/README.md packages/test-utils/README.md CONTRIBUTING.md --include='*.md' --include='*.vue' --include='*.js' --include='*.mjs' --include='*.css'
- name: Renovate config validator
run: yarn lint:renovate
# The contributor skills, AGENTS.md and agent config, checked by agnix
# (spec fields, names, links). The live skill evals spend model tokens,
# so they run locally only (yarn skills:eval), never in CI.
- name: Skills (agnix)
run: yarn lint:skills
- name: Dependency audit (full report, includes devDependencies)
if: needs.changes.outputs.deps == 'true'
env:
YARN_HTTP_TIMEOUT: '180000'
run: yarn lint:audit:full
continue-on-error: true
# The registry's audit endpoint times out often enough to matter, so the
# blocking pass retries before it fails anyone's pipeline.
- name: Dependency audit (production, blocking)
if: needs.changes.outputs.deps == 'true'
env:
YARN_HTTP_TIMEOUT: '180000'
run: |
for attempt in 1 2 3; do
yarn lint:audit && exit 0
status=$?
if [ "$attempt" -lt 3 ]; then
echo "audit attempt $attempt failed (exit $status); retrying in 30s" >&2
sleep 30
fi
done
exit "$status"
- name: Knip (unused/unlisted dependencies)
if: needs.changes.outputs.deps == 'true'
run: yarn lint:knip
- if: needs.changes.outputs.deps == 'true' || needs.changes.outputs.packages == 'true'
run: yarn build
- name: Bundlewatch
if: needs.changes.outputs.deps == 'true' || needs.changes.outputs.packages == 'true'
run: yarn bundlewatch
env:
BUNDLEWATCH_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GITHUB_SHA on a pull_request event is a synthetic merge commit, not
# the PR's actual head commit - a status posted against it never
# shows up on the PR. Force bundlewatch onto the real head SHA.
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
continue-on-error: true
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: lint-results
path: ./reports/junit/
test-unit:
needs: build
runs-on: ubuntu-latest
env:
# Job-level, not step-level: a step's `if:` can't see env set within
# that same step, so the Codecov step's own `if:` needs this here.
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- run: yarn build
- name: Run unit tests
run: yarn test:unit --reporters=jest-junit --runInBand
env:
NODE_OPTIONS: --max_old_space_size=8192
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: test-results
path: ./reports/junit/
# A pull request from a fork gets no secrets, so the token is empty there.
# Codecov accepts uploads without a token for pull requests into a public
# repository, so upload for those too. A fork's own pushes, with no token
# and no upstream pull request, skip the upload rather than fail.
- name: Upload coverage to Codecov
id: codecov
if: ${{ env.CODECOV_TOKEN != '' || github.event.pull_request.base.repo.full_name == 'druxt/druxt.js' }}
# A Codecov outage warns through the next step instead of failing the job.
continue-on-error: true
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
directory: ./coverage
- name: Report a failed coverage upload
if: ${{ steps.codecov.outcome == 'failure' }}
run: |
echo "::warning title=Coverage not uploaded::The Codecov upload failed, so this run has no coverage report. See the Upload coverage to Codecov step."
echo "Coverage not uploaded: the Codecov upload failed, so this run has no coverage report." >> "$GITHUB_STEP_SUMMARY"
# The three themed showcase examples (daisyui, tailwind, bootstrapvue) run
# as a parallel matrix - each gets its own backend and dev server, which
# keeps any single app's failure attributable and the wall time down.
# druxt-site's e2e stays in test-e2e below alongside Storybook and the
# docs site; it was running there before this suite existed.
test-examples:
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example: [druxt-daisyui, druxt-tailwind, druxt-bootstrapvue]
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- run: yarn build
# Same docker-free backend as test-e2e: PHP built-in server + SQLite,
# not DDEV. See examples/drupal/README.md and .devtools/.
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.3'
extensions: pdo_sqlite, sqlite3
- name: Assemble, provision, and start the Drupal backend
working-directory: examples/drupal
run: |
.devtools/assemble
.devtools/provision
.devtools/start
env:
WEBSERVER_HOST: 127.0.0.1
WEBSERVER_PORT: 8888
- run: npx cypress install
- name: ${{ matrix.example }} e2e
env:
BASE_URL: http://127.0.0.1:8888
run: yarn example:${{ matrix.example }}:test
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: cypress-${{ matrix.example }}
path: |
examples/${{ matrix.example }}/test/cypress/screenshots
examples/${{ matrix.example }}/test/cypress/videos
# Backend-free: the druxt-inspect CLI suite replays recorded JSON:API
# fixtures (examples/node-client/test/adapter.js), so it needs neither the
# Drupal backend nor Cypress - just the built workspace packages.
test-node-client:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- run: yarn build
- name: druxt-inspect CLI tests
run: yarn test:node-client
test-e2e:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 16.20.1
- run: corepack enable
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --immutable
- run: yarn build
# Docker-free: PHP built-in server + SQLite, not DDEV. See
# examples/drupal/README.md and .devtools/ for the reasoning.
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.3'
extensions: pdo_sqlite, sqlite3
- name: Assemble, provision, and start the Drupal backend
working-directory: examples/drupal
run: |
.devtools/assemble
.devtools/provision
.devtools/start
env:
WEBSERVER_HOST: 127.0.0.1
WEBSERVER_PORT: 8888
- run: npx cypress install
- name: DruxtSite e2e
env:
BASE_URL: http://127.0.0.1:8888
# The examples backend ships no translated demo content (see
# examples/drupal/README.md) - the specs that assert translated
# content skip on this variant and run against the full
# druxtjs.org site backend's own pipeline instead.
CYPRESS_backendVariant: minimal
run: |
yarn start-server-and-test 'yarn example:druxt-site' http://localhost:3000 \
'npx cypress run --project examples/druxt-site/test --spec "examples/druxt-site/test/cypress/e2e/nuxt/**/*.cy.js"'
- name: DruxtSite Storybook e2e
env:
BASE_URL: http://127.0.0.1:8888
run: |
yarn start-server-and-test 'yarn example:druxt-site:storybook --port 3000' http://localhost:3000 \
'npx cypress run --project examples/druxt-site/test --spec "examples/druxt-site/test/cypress/e2e/storybook/*.cy.js"'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: failure()
with:
name: cypress-screenshots-videos
path: |
examples/druxt-site/test/cypress/screenshots
examples/druxt-site/test/cypress/videos