My notes on how to get a local development environment up and running for MediaWiki development. Written from a Windows and VS Code perspective.

Local development environments are essential for the patch writing process. They allow you to instantly test your changes, before submitting your patch. They are also essential for the debugging process, since it allows you to step debug the issue if it's reproducible.

🔎TODO: I'm currently running things in like 4 different containers (PowerShell, Ubuntu, MediaWiki Docker, Fresh), and switching between the consoles. Should probably get absolutely everything running in 1 container such as Fresh, then rewrite these directions. Would simplify things.

Things to do at the start of every session

Some pessimistic advice

Expect to spend more time setting up your dev environment than you do coding, until you've got it set up perfectly on all your computers, and you've mastered the ins and out of this work instruction. Can take months to become fluent. MediaWiki has a complicated toolchain.

Windows Subsystem for Linux (WSL)

This is the Windows Subsystem for Linux (WSL) version of this work instruction. The no-WSL version is located at User:Novem Linguae/Essays/Docker tutorial for Windows (no-WSL).

Why use WSL?

Docker

Docker is a fancy XAMPP. It lets whatever codebase you're working on pick what OS, what version of PHP/Python/Node, what database, etc. to use instead of depending on whatever version of XAMPP you happened to install. Then it automates the installation of everything for you.

If you try to use PHP 8.1 with a repo that is using a bunch of PHP 7.4 dependencies, for example, you may not be able to get a dev environment up and running, even if you do composer update instead of composer install. You'll get a bunch of errors. You'd be forced to uninstall XAMPP 8.1 and install XAMPP 7.4, which is a pain. Maybe you need XAMPP 8.1 for your other project, so would have to do this all over again when switching projects. Docker automates all this.

Install WSL

Install useful software (composer, git, etc.)

Main article: mw:Manual:Running MediaWiki on Windows Subsystem for Linux

Eliminate password prompts

Install MediaWiki core

Automatically

Consider wiping out your localhost and installing fresh via this script once a week, and/or when you get unexpected exceptions. Unexpected exceptions are often from alpha versions getting out of sync. For example, maybe you git pull MediaWiki core to be this week's version, but you forget to git pull skins/Vector, leaving you on last week's skins/Vector, which is incompatible.

Manually

Install MediaWiki core (1)

MW_SCRIPT_PATH=/w
MW_SERVER=http://localhost:8080
MW_DOCKER_PORT=8080
MEDIAWIKI_USER=Admin
MEDIAWIKI_PASSWORD=dockerpass
XDEBUG_ENABLE=true
XDEBUG_CONFIG='mode=debug start_with_request=yes client_host=host.docker.internal client_port=9003 idekey=VSCODE'
XDEBUG_MODE=debug,coverage
XHPROF_ENABLE=true
PHPUNIT_LOGS=0
PHPUNIT_USE_NORMAL_TABLES=1
MW_DOCKER_UID=
MW_DOCKER_GID=

Start Docker

Install MediaWiki core (2)

Install MediaWiki extensions and skins

Automatically

Manually

Installing complicated extensions

Adiutor

CentralAuth

DiscussionTools

FlaggedRevs

FlaggedRevs is packed with features, so it is important to get its settings right, so that it behaves the way you expect it to. It is basically like two extensions in one and has two major modes: override and protection.

enwiki (override = true, protection = true)

// InitializeSettings.php
$wgFlaggedRevsOverride = false;
$wgFlaggedRevsProtection = true;
$wgSimpleFlaggedRevsUI = true;
$wgFlaggedRevsHandleIncludes = 0;
$wgFlaggedRevsAutoReview = 3;
$wgFlaggedRevsLowProfile = true;
// CommonSettings.php
$wgAvailableRights[] = 'autoreview';
$wgAvailableRights[] = 'autoreviewrestore';
$wgAvailableRights[] = 'movestable';
$wgAvailableRights[] = 'review';
$wgAvailableRights[] = 'stablesettings';
$wgAvailableRights[] = 'unreviewedpages';
$wgAvailableRights[] = 'validate';
$wgGrantPermissions['editprotected']['movestable'] = true;
// flaggedrevs.php
wfLoadExtension( 'FlaggedRevs' );
$wgFlaggedRevsAutopromote = false;
$wgHooks['MediaWikiServices'][] = static function () {
	global $wgAddGroups, $wgDBname, $wgDefaultUserOptions,
		$wgFlaggedRevsNamespaces, $wgFlaggedRevsRestrictionLevels,
		$wgFlaggedRevsTags, $wgFlaggedRevsTagsRestrictions,
		$wgGroupPermissions, $wgRemoveGroups;

	$wgFlaggedRevsNamespaces[] = 828; // NS_MODULE
	$wgFlaggedRevsTags = [ 'accuracy' => [ 'levels' => 2 ] ];
	$wgFlaggedRevsTagsRestrictions = [
		'accuracy' => [ 'review' => 1, 'autoreview' => 1 ],
	];
	$wgGroupPermissions['autoconfirmed']['movestable'] = true; // T16166
	$wgGroupPermissions['sysop']['stablesettings'] = false; // -aaron 3/20/10
	$allowSysopsAssignEditor = true;

	$wgFlaggedRevsNamespaces = [ NS_MAIN, NS_PROJECT ];
	# We have only one tag with one level
	$wgFlaggedRevsTags = [ 'status' => [ 'levels' => 1 ] ];
	# Restrict autoconfirmed to flagging semi-protected
	$wgFlaggedRevsTagsRestrictions = [
		'status' => [ 'review' => 1, 'autoreview' => 1 ],
	];
	# Restriction levels for auto-review/review rights
	$wgFlaggedRevsRestrictionLevels = [ 'autoconfirmed' ];
	# Group permissions for autoconfirmed
	$wgGroupPermissions['autoconfirmed']['autoreview'] = true;
	# Group permissions for sysops
	$wgGroupPermissions['sysop']['review'] = true;
	$wgGroupPermissions['sysop']['stablesettings'] = true;
	# Use 'reviewer' group
	$wgAddGroups['sysop'][] = 'reviewer';
	$wgRemoveGroups['sysop'][] = 'reviewer';
	# Remove 'editor' and 'autoreview' (T91934) user groups
	unset( $wgGroupPermissions['editor'], $wgGroupPermissions['autoreview'] );

	# Rights for Bureaucrats (b/c)
	if ( isset( $wgGroupPermissions['reviewer'] ) ) {
		if ( !in_array( 'reviewer', $wgAddGroups['bureaucrat'] ?? [] ) ) {
			// promote to full reviewers
			$wgAddGroups['bureaucrat'][] = 'reviewer';
		}
		if ( !in_array( 'reviewer', $wgRemoveGroups['bureaucrat'] ?? [] ) ) {
			// demote from full reviewers
			$wgRemoveGroups['bureaucrat'][] = 'reviewer';
		}
	}
	# Rights for Sysops
	if ( isset( $wgGroupPermissions['editor'] ) && $allowSysopsAssignEditor ) {
		if ( !in_array( 'editor', $wgAddGroups['sysop'] ) ) {
			// promote to basic reviewer (established editors)
			$wgAddGroups['sysop'][] = 'editor';
		}
		if ( !in_array( 'editor', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic reviewer (established editors)
			$wgRemoveGroups['sysop'][] = 'editor';
		}
	}
	if ( isset( $wgGroupPermissions['autoreview'] ) ) {
		if ( !in_array( 'autoreview', $wgAddGroups['sysop'] ) ) {
			// promote to basic auto-reviewer (semi-trusted users)
			$wgAddGroups['sysop'][] = 'autoreview';
		}
		if ( !in_array( 'autoreview', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic auto-reviewer (semi-trusted users)
			$wgRemoveGroups['sysop'][] = 'autoreview';
		}
	}
};

dewiki (override = true, protection = false)

// InitializeSettings.php
$wgFlaggedRevsOverride = true;
$wgFlaggedRevsProtection = false;
$wgSimpleFlaggedRevsUI = true;
$wgFlaggedRevsHandleIncludes = 2;
$wgFlaggedRevsAutoReview = 3;
$wgFlaggedRevsLowProfile = true;
// CommonSettings.php
$wgAvailableRights[] = 'autoreview';
$wgAvailableRights[] = 'autoreviewrestore';
$wgAvailableRights[] = 'movestable';
$wgAvailableRights[] = 'review';
$wgAvailableRights[] = 'stablesettings';
$wgAvailableRights[] = 'unreviewedpages';
$wgAvailableRights[] = 'validate';
$wgGrantPermissions['editprotected']['movestable'] = true;
// flaggedrevs.php
wfLoadExtension( 'FlaggedRevs' );
$wgFlaggedRevsAutopromote = false;
call_user_func( static function () {
	global $wgDBname,
		$wgFlaggedRevsAutopromote, $wgFlaggedRevsAutoconfirm;

	$wmgStandardAutoPromote = [
		'days'                  => 60, # days since registration
		'edits'                 => 250, # total edit count
		'excludeLastDays'       => 1, # exclude the last X days of edits from below edit counts
		'benchmarks'            => 15, # number of "spread out" edits
		'spacing'               => 3, # number of days between these edits (the "spread")
		'totalContentEdits'     => 300, # edits to pages in $wgContentNamespaces
		'totalCheckedEdits'     => 200, # edits before the stable version of pages
		'uniqueContentPages'    => 14, # unique pages in $wgContentNamespaces edited
		'editComments'          => 50, # number of manual edit summaries used
		'userpageBytes'         => 0, # size of userpage (use 0 to not require a userpage)
		'neverBlocked'          => true, # username was never blocked before?
		'maxRevertedEditRatio'  => 0.03, # max fraction of edits reverted via "rollback"/"undo"
	];

	$wgFlaggedRevsAutopromote = $wmgStandardAutoPromote;
	$wgFlaggedRevsAutopromote['edits'] = 300;
	$wgFlaggedRevsAutopromote['editComments'] = 30;

	$wgFlaggedRevsAutoconfirm = [
		'days'                => 30, # days since registration
		'edits'               => 50, # total edit count
		'spacing'             => 3, # spacing of edit intervals
		'benchmarks'          => 7, # how many edit intervals are needed?
		'excludeLastDays'     => 2, # exclude the last X days of edits from edit counts
		// Either totalContentEdits reqs OR totalCheckedEdits requirements needed
		'totalContentEdits'   => 150, # $wgContentNamespaces edits OR...
		'totalCheckedEdits'   => 50, # ...Edits before the stable version of pages
		'uniqueContentPages'  => 8, # $wgContentNamespaces unique pages edited
		'editComments'        => 20, # how many edit comments used?
		'email'               => false, # user must be emailconfirmed?
		'neverBlocked'        => true, # Can users that were blocked be promoted?
	];
} );
$wgHooks['MediaWikiServices'][] = static function () {
	global $wgAddGroups, $wgDBname, $wgDefaultUserOptions,
		$wgFlaggedRevsNamespaces, $wgFlaggedRevsRestrictionLevels,
		$wgFlaggedRevsTags, $wgFlaggedRevsTagsRestrictions,
		$wgGroupPermissions, $wgRemoveGroups;

	$wgFlaggedRevsNamespaces[] = 828; // NS_MODULE
	$wgFlaggedRevsTags = [ 'accuracy' => [ 'levels' => 2 ] ];
	$wgFlaggedRevsTagsRestrictions = [
		'accuracy' => [ 'review' => 1, 'autoreview' => 1 ],
	];
	$wgGroupPermissions['autoconfirmed']['movestable'] = true; // T16166
	$wgGroupPermissions['sysop']['stablesettings'] = false; // -aaron 3/20/10
	$allowSysopsAssignEditor = true;
	
	$wgFlaggedRevsNamespaces[] = NS_CATEGORY;
	$wgFlaggedRevsTags['accuracy']['levels'] = 1;

	$wgGroupPermissions['sysop']['stablesettings'] = true; // -aaron 3/20/10
	
	# Rights for Bureaucrats (b/c)
	if ( isset( $wgGroupPermissions['reviewer'] ) ) {
		if ( !in_array( 'reviewer', $wgAddGroups['bureaucrat'] ?? [] ) ) {
			// promote to full reviewers
			$wgAddGroups['bureaucrat'][] = 'reviewer';
		}
		if ( !in_array( 'reviewer', $wgRemoveGroups['bureaucrat'] ?? [] ) ) {
			// demote from full reviewers
			$wgRemoveGroups['bureaucrat'][] = 'reviewer';
		}
	}
	# Rights for Sysops
	if ( isset( $wgGroupPermissions['editor'] ) && $allowSysopsAssignEditor ) {
		if ( !in_array( 'editor', $wgAddGroups['sysop'] ) ) {
			// promote to basic reviewer (established editors)
			$wgAddGroups['sysop'][] = 'editor';
		}
		if ( !in_array( 'editor', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic reviewer (established editors)
			$wgRemoveGroups['sysop'][] = 'editor';
		}
	}
	if ( isset( $wgGroupPermissions['autoreview'] ) ) {
		if ( !in_array( 'autoreview', $wgAddGroups['sysop'] ) ) {
			// promote to basic auto-reviewer (semi-trusted users)
			$wgAddGroups['sysop'][] = 'autoreview';
		}
		if ( !in_array( 'autoreview', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic auto-reviewer (semi-trusted users)
			$wgRemoveGroups['sysop'][] = 'autoreview';
		}
	}
};

plwiki (override = true, protection = false)

// InitializeSettings.php
$wgFlaggedRevsOverride = true;
$wgFlaggedRevsProtection = false;
$wgSimpleFlaggedRevsUI = true;
$wgFlaggedRevsHandleIncludes = 2;
$wgFlaggedRevsAutoReview = 3;
$wgFlaggedRevsLowProfile = true;
// CommonSettings.php
$wgAvailableRights[] = 'autoreview';
$wgAvailableRights[] = 'autoreviewrestore';
$wgAvailableRights[] = 'movestable';
$wgAvailableRights[] = 'review';
$wgAvailableRights[] = 'stablesettings';
$wgAvailableRights[] = 'unreviewedpages';
$wgAvailableRights[] = 'validate';
$wgGrantPermissions['editprotected']['movestable'] = true;
// flaggedrevs.php
wfLoadExtension( 'FlaggedRevs' );
$wgFlaggedRevsAutopromote = false;
call_user_func( static function () {
	global $wgDBname,
		$wgFlaggedRevsAutopromote, $wgFlaggedRevsAutoconfirm;

	$wmgStandardAutoPromote = [
		'days'                  => 60, # days since registration
		'edits'                 => 250, # total edit count
		'excludeLastDays'       => 1, # exclude the last X days of edits from below edit counts
		'benchmarks'            => 15, # number of "spread out" edits
		'spacing'               => 3, # number of days between these edits (the "spread")
		'totalContentEdits'     => 300, # edits to pages in $wgContentNamespaces
		'totalCheckedEdits'     => 200, # edits before the stable version of pages
		'uniqueContentPages'    => 14, # unique pages in $wgContentNamespaces edited
		'editComments'          => 50, # number of manual edit summaries used
		'userpageBytes'         => 0, # size of userpage (use 0 to not require a userpage)
		'neverBlocked'          => true, # username was never blocked before?
		'maxRevertedEditRatio'  => 0.03, # max fraction of edits reverted via "rollback"/"undo"
	];

	$wgFlaggedRevsAutopromote = $wmgStandardAutoPromote;
	$wgFlaggedRevsAutopromote['days'] = 90;
	$wgFlaggedRevsAutopromote['edits'] = 500;
	$wgFlaggedRevsAutopromote['spacing'] = 3;
	$wgFlaggedRevsAutopromote['benchmarks'] = 15;
	$wgFlaggedRevsAutopromote['totalContentEdits'] = 500;
	$wgFlaggedRevsAutopromote['uniqueContentPages'] = 10;
	$wgFlaggedRevsAutopromote['editComments'] = 500;
} );
$wgHooks['MediaWikiServices'][] = static function () {
	global $wgAddGroups, $wgDBname, $wgDefaultUserOptions,
		$wgFlaggedRevsNamespaces, $wgFlaggedRevsRestrictionLevels,
		$wgFlaggedRevsTags, $wgFlaggedRevsTagsRestrictions,
		$wgGroupPermissions, $wgRemoveGroups;

	$wgFlaggedRevsNamespaces[] = 828; // NS_MODULE
	$wgFlaggedRevsTags = [ 'accuracy' => [ 'levels' => 2 ] ];
	$wgFlaggedRevsTagsRestrictions = [
		'accuracy' => [ 'review' => 1, 'autoreview' => 1 ],
	];
	$wgGroupPermissions['autoconfirmed']['movestable'] = true; // T16166
	$wgGroupPermissions['sysop']['stablesettings'] = false; // -aaron 3/20/10
	$allowSysopsAssignEditor = true;
	
	$wgFlaggedRevsNamespaces = [ NS_MAIN, NS_TEMPLATE, NS_CATEGORY, NS_HELP, 100, 828 ];
	$wgFlaggedRevsTags['accuracy']['levels'] = 1;
	
	# Rights for Bureaucrats (b/c)
	if ( isset( $wgGroupPermissions['reviewer'] ) ) {
		if ( !in_array( 'reviewer', $wgAddGroups['bureaucrat'] ?? [] ) ) {
			// promote to full reviewers
			$wgAddGroups['bureaucrat'][] = 'reviewer';
		}
		if ( !in_array( 'reviewer', $wgRemoveGroups['bureaucrat'] ?? [] ) ) {
			// demote from full reviewers
			$wgRemoveGroups['bureaucrat'][] = 'reviewer';
		}
	}
	# Rights for Sysops
	if ( isset( $wgGroupPermissions['editor'] ) && $allowSysopsAssignEditor ) {
		if ( !in_array( 'editor', $wgAddGroups['sysop'] ) ) {
			// promote to basic reviewer (established editors)
			$wgAddGroups['sysop'][] = 'editor';
		}
		if ( !in_array( 'editor', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic reviewer (established editors)
			$wgRemoveGroups['sysop'][] = 'editor';
		}
	}
	if ( isset( $wgGroupPermissions['autoreview'] ) ) {
		if ( !in_array( 'autoreview', $wgAddGroups['sysop'] ) ) {
			// promote to basic auto-reviewer (semi-trusted users)
			$wgAddGroups['sysop'][] = 'autoreview';
		}
		if ( !in_array( 'autoreview', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic auto-reviewer (semi-trusted users)
			$wgRemoveGroups['sysop'][] = 'autoreview';
		}
	}
};

ruwiki (override = false, protection = false)

Note: You also need to add yourself to the "editor" group to review pages.

// InitializeSettings.php
$wgFlaggedRevsOverride = false;
$wgFlaggedRevsProtection = false;
$wgSimpleFlaggedRevsUI = true;
$wgFlaggedRevsHandleIncludes = 0;
$wgFlaggedRevsAutoReview = 3;
$wgFlaggedRevsLowProfile = true;
// CommonSettings.php
$wgAvailableRights[] = 'autoreview';
$wgAvailableRights[] = 'autoreviewrestore';
$wgAvailableRights[] = 'movestable';
$wgAvailableRights[] = 'review';
$wgAvailableRights[] = 'stablesettings';
$wgAvailableRights[] = 'unreviewedpages';
$wgAvailableRights[] = 'validate';
$wgGrantPermissions['editprotected']['movestable'] = true;
// flaggedrevs.php
wfLoadExtension( 'FlaggedRevs' );
$wgFlaggedRevsAutopromote = false;
$wgHooks['MediaWikiServices'][] = static function () {
	global $wgAddGroups, $wgDBname, $wgDefaultUserOptions,
		$wgFlaggedRevsNamespaces, $wgFlaggedRevsRestrictionLevels,
		$wgFlaggedRevsTags, $wgFlaggedRevsTagsRestrictions,
		$wgGroupPermissions, $wgRemoveGroups;

	$wgFlaggedRevsNamespaces[] = 828; // NS_MODULE
	$wgFlaggedRevsTags = [ 'accuracy' => [ 'levels' => 2 ] ];
	$wgFlaggedRevsTagsRestrictions = [
		'accuracy' => [ 'review' => 1, 'autoreview' => 1 ],
	];
	$wgGroupPermissions['autoconfirmed']['movestable'] = true; // T16166
	$wgGroupPermissions['sysop']['stablesettings'] = false; // -aaron 3/20/10
	$allowSysopsAssignEditor = true;
	
	// T39675, T49337
	$wgFlaggedRevsNamespaces = [ NS_MAIN, NS_FILE, NS_TEMPLATE, NS_CATEGORY, 100, 828 ];
	$wgFlaggedRevsTags['accuracy']['levels'] = 1;
	$wgGroupPermissions['sysop']['stablesettings'] = true; // -aaron 3/20/10
	$wgGroupPermissions['sysop']['review'] = false; // T275811
	# Remove reviewer group
	unset( $wgGroupPermissions['reviewer'] );
	
	# Rights for Bureaucrats (b/c)
	if ( isset( $wgGroupPermissions['reviewer'] ) ) {
		if ( !in_array( 'reviewer', $wgAddGroups['bureaucrat'] ?? [] ) ) {
			// promote to full reviewers
			$wgAddGroups['bureaucrat'][] = 'reviewer';
		}
		if ( !in_array( 'reviewer', $wgRemoveGroups['bureaucrat'] ?? [] ) ) {
			// demote from full reviewers
			$wgRemoveGroups['bureaucrat'][] = 'reviewer';
		}
	}
	# Rights for Sysops
	if ( isset( $wgGroupPermissions['editor'] ) && $allowSysopsAssignEditor ) {
		if ( !in_array( 'editor', $wgAddGroups['sysop'] ) ) {
			// promote to basic reviewer (established editors)
			$wgAddGroups['sysop'][] = 'editor';
		}
		if ( !in_array( 'editor', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic reviewer (established editors)
			$wgRemoveGroups['sysop'][] = 'editor';
		}
	}
	if ( isset( $wgGroupPermissions['autoreview'] ) ) {
		if ( !in_array( 'autoreview', $wgAddGroups['sysop'] ) ) {
			// promote to basic auto-reviewer (semi-trusted users)
			$wgAddGroups['sysop'][] = 'autoreview';
		}
		if ( !in_array( 'autoreview', $wgRemoveGroups['sysop'] ) ) {
			// demote from basic auto-reviewer (semi-trusted users)
			$wgRemoveGroups['sysop'][] = 'autoreview';
		}
	}
};

MassMessage

$wgGroupPermissions['user']['editcontentmodel'] = false;
$wgGroupPermissions['sysop']['editcontentmodel'] = true;
$wgGroupPermissions['massmessage-sender']['massmessage'] = true;

ORES

Main article: mw:Extension:PageTriage § ORES

$wgPageTriageEnableOresFilters = true;
$wgOresWikiId = 'enwiki';
$wgOresModels = [
	'articlequality' => [ 'enabled' => true, 'namespaces' => [ 0 ], 'cleanParent' => true ],
	'draftquality' => [ 'enabled' => true, 'namespaces' => [ 0 ], 'types' => [ 1 ] ]
];

PageTriage

Config settings:

wfLoadExtension( 'PageTriage' );
	$wgPageTriageDraftNamespaceId = 118;
	$wgExtraNamespaces[ $wgPageTriageDraftNamespaceId ] = 'Draft';
	$wgExtraNamespaces[ $wgPageTriageDraftNamespaceId + 1 ] = 'Draft_talk';
	$wgPageTriageNoIndexUnreviewedNewArticles = true;
	// Special:NewPagesFeed has some code that puts "created by new editor" if they are not autoconfirmed. But autoconfirmed needs to be turned on.
	$wgAutoConfirmCount = 10;
	$wgAutoConfirmAge = 4;
	$wgPageTriageEnableCopyvio = true;
wfLoadExtension( 'ORES' );
	$wgPageTriageEnableOresFilters = true;
	$wgOresWikiId = 'enwiki';
	$wgOresModels = [
		'articlequality' => [ 'enabled' => true, 'namespaces' => [ 0 ], 'cleanParent' => true ],
		'draftquality' => [ 'enabled' => true, 'namespaces' => [ 0 ], 'types' => [ 1 ] ]
	];
wfLoadExtension( 'Echo' );
wfLoadExtension( 'WikiLove' );

ProofreadPage

Scribunto (Modules, Lua)

SyntaxHighlight

VisualEditor

Wikibase (Wikidata)

Main article: mw:Wikibase/Installation

VS Code

First time

Window #1 - Open the mediawiki folder in VS Code

Window #2 - Open the extension folder in VS Code

{
    "intelephense.environment.includePaths": [
        "../../"
    ]
}

Linters

Other extensions

Debugging

PHP step debugging: XDebug

Main articles: mw:MediaWiki-Docker/Configuration recipes/Xdebug and mw:MediaWiki-Docker/Configuration_recipes/Xdebug_config_for_VS_Code

XDEBUG_CONFIG='mode=debug start_with_request=yes client_host=host.docker.internal client_port=9003 idekey=VSCODE' 
XDEBUG_MODE=debug,coverage
{
	// Use IntelliSense to learn about possible attributes.
	// Hover to view descriptions of existing attributes.
	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
	"version": "0.2.0",
	"configurations": [
		{
			"name": "Listen for XDebug",
			"type": "php",
			"request": "launch",
			"hostname": "0.0.0.0",
			"port": 9003,
			"pathMappings": {
			  "/var/www/html/w": "${workspaceFolder}"
			}
		},
		{
			"name": "Launch currently open script",
			"type": "php",
			"request": "launch",
			"program": "${file}",
			"cwd": "${fileDirname}",
			"port": 9003
		}
	]
}

JavaScript step debugging: Google Chrome devtools

Vue debugging: Vue devtools (browser extension)

Running tests

How to run an extension's tests:

PHPUnit

Jest

QUnit

Selenium

Parser tests

Code coverage

How to generate code coverage reports:

Generating documentation

PHP

JavaScript

Running maintenance scripts

SQL database

SQLite or MariaDB?

Viewing and modifying the database: HeidiSQL

LocalSettings.php

Miscellaneous

Troubleshooting

Notes

  1. ^ a b Do not use git clone https://gerrit.wikimedia.org/r/mediawiki/core.git mediawiki. This will mess up Gerrit / Git Review when submitting patches.
  2. ^ In my case, not running this inside the Docker shell will use XAMPP instead of Docker, and my XAMPP is on PHP 7.4 instead of PHP 8.1, so I will get PHP version errors when trying to run it.