Caching composer and node_modules in CI, correctly

Caching the installed directories means a stale cache silently ships the wrong dependency versions; caching the package manager’s download cache does not.

cache:
  key:
    files: [composer.lock, package-lock.json]
  paths:
    - .composer-cache/
    - .npm/

before_script:
  - composer config cache-dir .composer-cache
  - composer install --no-interaction --prefer-dist
  - npm ci --cache .npm --prefer-offline

Keying on the lock files means a dependency change gets a fresh cache automatically rather than needing a manual clear. npm ci deletes node_modules and installs from the lock file exactly, so caching the download directory speeds it up without any risk of a stale tree — which is the distinction that makes caching safe. Caching node_modules itself is faster and is how a CI run ends up with a package nobody declared.