Skip to content

Repository files navigation

mediawiki-ci-workflows

Reusable GitHub Actions CI workflows for MediaWiki skins and extensions. Call them from your project's workflow file — no need to copy and maintain CI logic yourself.

Quick start

Add a .github/workflows/ci.yml to your project:

name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  lint:
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/lint.yml@main
    with:
      lint-php: true
      lint-js: true

  test-js:
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-js.yml@main

  test-php:
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-php.yml@main
    with:
      project-type: skin       # or 'extension'
      project-name: MySkin     # your project's directory name

Your project needs:

  • PHP: composer.json with test (PHPCS), phan, and PHPUnit scripts
  • JS: package.json with lint:js, lint:styles, lint:i18n, lint:md scripts and Vitest
  • Coverage (optional): Vitest outputs to coverage/js/lcov.info; PHPUnit outputs Clover XML

Not every project needs all of these — only include the workflows and inputs relevant to your stack.

Workflows

lint.yml — Linting

Runs PHP, JS, style, i18n, markdown and type-check linters. Enable only the ones you need.

Input Type Default Description
lint-php boolean false Run composer test
lint-js boolean false Run npm run lint:js
lint-styles boolean false Run npm run lint:styles
lint-i18n boolean false Run npm run lint:i18n
lint-md boolean false Run npm run lint:md
lint-types boolean false Run npm run lint:types
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)
lint:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/lint.yml@main
  with:
    lint-php: true
    lint-js: true
    lint-styles: true
    lint-i18n: true
    lint-md: true
    lint-types: true

analyze-php.yml — Phan static analysis

Downloads a MediaWiki branch, installs your project into it, and runs composer phan.

Input Type Default Description
project-type string required skin or extension
project-name string required Directory name (e.g. Citizen, TabberNeue)
mw-branch string REL1_43 MediaWiki branch
php-version string 8.2 PHP version
extra-extensions string "" Space-separated extra extensions for type resolution (e.g. Scribunto)
skip-cache boolean false Skip MW cache (for nightly runs)
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)
analyze-php:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/analyze-php.yml@main
  with:
    project-type: skin
    project-name: Citizen

test-js.yml — JavaScript tests

Runs Vitest with coverage and caches the results for SonarQube.

Input Type Default Description
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)
test-js:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-js.yml@main

test-php.yml — PHPUnit tests

Runs PHPUnit across a matrix of MediaWiki branches and PHP versions.

Input Type Default Description
project-type string required skin or extension
project-name string required Directory name (e.g. Citizen, TabberNeue)
matrix string (see below) JSON array of matrix entries
extra-extensions string "" Space-separated extra extensions to download and load (e.g. Scribunto)
skip-cache boolean false Skip MW cache (for nightly runs)
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)

Default matrix:

[
  {"mw": "REL1_43", "php": "8.2", "coverage": "xdebug", "experimental": false},
  {"mw": "REL1_44", "php": "8.3", "coverage": "none",   "experimental": false},
  {"mw": "REL1_45", "php": "8.4", "coverage": "none",   "experimental": false},
  {"mw": "REL1_46", "php": "8.5", "coverage": "none",   "experimental": false},
  {"mw": "master",  "php": "8.5", "coverage": "none",   "experimental": true}
]
Field Description
mw MediaWiki branch
php PHP version
coverage xdebug to generate coverage, none to skip
experimental true allows failure without failing the workflow
test-php:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-php.yml@main
  with:
    project-type: skin
    project-name: Citizen

To customize the matrix:

test-php:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-php.yml@main
  with:
    project-type: extension
    project-name: TabberNeue
    matrix: >
      [
        {"mw": "REL1_43", "php": "8.2", "coverage": "xdebug", "experimental": false},
        {"mw": "REL1_44", "php": "8.3", "coverage": "none",   "experimental": false}
      ]

test-parser.yml — MediaWiki parser tests

Runs MediaWiki's parserTests.php against every .txt file in your project's tests/parser/ directory across a matrix of MediaWiki branches and PHP versions. The job is a no-op if the directory has no parser tests, so it's safe to wire up unconditionally.

Input Type Default Description
project-type string required skin or extension
project-name string required Directory name (e.g. Citizen, TabberNeue)
matrix string (see below) JSON array of matrix entries
extra-extensions string "" Space-separated extra extensions to download and load (e.g. Scribunto)
skip-cache boolean false Skip MW cache (for nightly runs)
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)

Default matrix:

[
  {"mw": "REL1_43", "php": "8.2", "experimental": false},
  {"mw": "REL1_44", "php": "8.3", "experimental": false},
  {"mw": "REL1_45", "php": "8.4", "experimental": false},
  {"mw": "REL1_46", "php": "8.5", "experimental": false},
  {"mw": "master",  "php": "8.5", "experimental": true}
]
Field Description
mw MediaWiki branch
php PHP version
experimental true allows failure without failing the workflow
test-parser:
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-parser.yml@main
  with:
    project-type: extension
    project-name: TabberNeue

sonarqube.yml — SonarQube analysis

Runs a SonarQube scan with coverage data from test-js and test-php. Coverage is cached per branch, so SonarQube always has data for both JS and PHP even when only one test suite ran.

Input Type Default Description
has-js-coverage boolean false Whether JS coverage was generated this run
has-php-coverage boolean false Whether PHP coverage was generated this run
enabled boolean true Set false to skip
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)

Requires a SONAR_TOKEN secret (optional — the scan is skipped automatically if the token is unavailable, e.g. on fork PRs). Set it up at SonarCloud > Your project > Administration > Analysis Method.

Your project also needs a sonar-project.properties file. See SonarCloud docs.

sonarqube:
  needs: [test-js, test-php]
  if: always() && !failure()
  uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/sonarqube.yml@main
  with:
    has-js-coverage: ${{ needs.test-js.result == 'success' }}
    has-php-coverage: ${{ needs.test-php.result == 'success' }}
  secrets:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

update-foreign-resources.yml — Foreign resources updates

Checks npm for newer versions of the packages pinned in your project's foreign-resources.yaml, regenerates the bundled files with MediaWiki's manageForeignResources maintenance script, and opens a pull request with the result — a scheduled Dependabot-style bot for foreign resources, which Dependabot itself cannot track.

Only components whose src points at registry.npmjs.org are updated; others are skipped with a notice. Comments and formatting in the yaml are preserved.

Input Type Default Description
project-type string extension skin or extension
project-name string required Directory name (e.g. FloatingUI, AGGrid)
foreign-resources-dir string modules/lib Directory containing foreign-resources.yaml, relative to the project root
extra-extensions string "" Space-separated extra extensions to download and load (e.g. Scribunto), for projects whose extension.json requires them
mw-branch string REL1_46 MediaWiki branch used to run manageForeignResources
php-version string 8.4 PHP version
pr-branch string update-foreign-resources Head branch for the update pull request
pr-title-prefix string build(deps) Conventional-commit prefix for the PR title and commit message; the rest is generated from the bumped packages (e.g. build(deps): bump @floating-ui/dom from 1.7.6 to 1.8.0)
runner string ubuntu-latest Runner image (e.g. ubuntu-24.04-arm for ARM)

Notes:

  • Trigger it from the caller on schedule and workflow_dispatch — it is not meant to run on pushes or pull requests.
  • Unlike the test workflows, this one does not cache the MediaWiki install, so it takes no skip-cache input. The project is checked out inside mediawiki/, so caching that tree would also cache the project's .git and carry git state between runs. Reinstalling costs about 35s, which is not worth optimising on a weekly job.
  • The caller job must grant contents: write and pull-requests: write permissions.
  • With the default GITHUB_TOKEN, the repository setting Allow GitHub Actions to create and approve pull requests (Settings → Actions → General) must be enabled, and the created PR will not trigger pull_request CI workflows. Pass a PR_TOKEN secret (a PAT or GitHub App token) to lift both limitations.
  • If your project cuts releases from conventional commits (e.g. release-please), override pr-title-prefix with a release-triggering type such as fix(deps).
  • Reference this workflow @main. The bump script is checked out from this repository's default branch at run time, so pinning the caller to a tag or SHA would still run the latest script — pinning gives no version isolation here.
name: Update foreign resources

on:
  schedule:
    - cron: "0 6 * * 1"
  workflow_dispatch:

jobs:
  update:
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/update-foreign-resources.yml@main
    permissions:
      contents: write
      pull-requests: write
    with:
      project-type: extension
      project-name: FloatingUI
    # Empty until the org defines PR_TOKEN; the workflow then falls back
    # to GITHUB_TOKEN.
    secrets:
      PR_TOKEN: ${{ secrets.PR_TOKEN }}

Full example

A complete caller workflow with change detection, conditional jobs, and nightly cache refresh:

Show full example
name: CI

on:
  schedule:
    - cron: "0 0 * * *"
  push:
    branches: [main]
  pull_request:
    branches: ["**"]

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  changes:
    name: Detect changes
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      php: ${{ steps.filter.outputs.php_any_changed }}
      script: ${{ steps.filter.outputs.script_any_changed }}
      stylesheet: ${{ steps.filter.outputs.stylesheet_any_changed }}
      i18n: ${{ steps.filter.outputs.i18n_any_changed }}
      markdown: ${{ steps.filter.outputs.markdown_any_changed }}
    steps:
      - uses: actions/checkout@v6
      - uses: tj-actions/changed-files@v47
        id: filter
        with:
          files_yaml: |
            php:
              - includes/**/*.php
              - tests/**/*.php
              - skin.json        # or extension.json
              - composer.json
              - composer.lock
              - .phan/config.php
              - .github/workflows/ci.yml
            script:
              - resources/**/*.js
              - tests/vitest/**
              - vitest.config.js
              - package.json
              - package-lock.json
              - .eslintrc.json
              - .github/workflows/ci.yml
            stylesheet:
              - resources/**/*.css
              - resources/**/*.less
              - skinStyles/**/*.css
              - skinStyles/**/*.less
              - package.json
              - package-lock.json
              - .github/workflows/ci.yml
            i18n:
              - i18n/*.json
              - package.json
              - package-lock.json
              - .github/workflows/ci.yml
            markdown:
              - "*.md"
              - .markdownlint.json
              - .github/workflows/ci.yml

  lint:
    needs: changes
    if: >-
      needs.changes.outputs.php == 'true' ||
      needs.changes.outputs.script == 'true' ||
      needs.changes.outputs.stylesheet == 'true' ||
      needs.changes.outputs.i18n == 'true' ||
      needs.changes.outputs.markdown == 'true' ||
      github.event_name == 'schedule'
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/lint.yml@main
    with:
      lint-php: ${{ needs.changes.outputs.php == 'true' || github.event_name == 'schedule' }}
      lint-js: ${{ needs.changes.outputs.script == 'true' || github.event_name == 'schedule' }}
      lint-styles: ${{ needs.changes.outputs.stylesheet == 'true' || github.event_name == 'schedule' }}
      lint-i18n: ${{ needs.changes.outputs.i18n == 'true' || github.event_name == 'schedule' }}
      lint-md: ${{ needs.changes.outputs.markdown == 'true' || github.event_name == 'schedule' }}

  analyze-php:
    needs: changes
    if: needs.changes.outputs.php == 'true' || github.event_name == 'schedule'
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/analyze-php.yml@main
    with:
      project-type: skin       # or 'extension'
      project-name: MySkin     # your project's directory name
      skip-cache: ${{ github.event_name == 'schedule' }}

  test-js:
    needs: changes
    if: needs.changes.outputs.script == 'true' || github.event_name == 'schedule'
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-js.yml@main

  test-php:
    needs: changes
    if: needs.changes.outputs.php == 'true' || github.event_name == 'schedule'
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/test-php.yml@main
    with:
      project-type: skin       # or 'extension'
      project-name: MySkin     # your project's directory name
      skip-cache: ${{ github.event_name == 'schedule' }}

  sonarqube:
    needs: [test-js, test-php]
    if: always() && !failure()
    uses: StarCitizenTools/mediawiki-ci-workflows/.github/workflows/sonarqube.yml@main
    with:
      has-js-coverage: ${{ needs.test-js.result == 'success' }}
      has-php-coverage: ${{ needs.test-php.result == 'success' }}
    secrets:
      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Caching

What Cache key Shared across Refreshed by
MediaWiki installation mw-<branch>-php<version> test-php, test-parser, and analyze-php skip-cache: true (nightly)
Composer packages composer-php<version> All MW branches Automatic (Composer)
JS coverage coverage-js-<branch> SonarQube runs Each test-js run
PHP coverage coverage-php-<branch> SonarQube runs Each test-php run

Development

Requires mise. Install dev tools and enable the pre-commit hook:

mise install
mise run setup

This installs actionlint, yamlfmt, lefthook, and Go (for the scripts/ tooling, checked with gofmt and go vet), then configures lefthook to run the checks automatically before each commit.

About

Reusable GitHub Actions CI workflows for MediaWiki skins and extensions

Resources

Stars

2 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages