A CI job that only runs when the files it cares about changed

A monorepo pipeline that runs every job on every push spends most of its minutes proving that unchanged code still works.

frontend:tests:
  stage: test
  script: [npm ci, npm test]
  rules:
    - changes:
        - resources/js/**/*
        - package-lock.json

api:tests:
  stage: test
  script: [composer install, vendor/bin/phpunit]
  rules:
    - changes: [app/**/*, composer.lock, tests/**/*]

The trap is that changes compares against the previous commit on a branch and against the target on a merge request, so a job can be skipped on a push and run on the merge — which is usually the safe direction and is worth knowing before someone reports it as a bug. Anything that gates a deploy should not use this: a skipped job counts as successful, so a required check that never ran passes. Keeping the rule on test jobs and off deployment jobs is the line.