Skip to content

Commit acc730a

Browse files
wp-env: Add cleanup command and force flag (#75045)
Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
1 parent aec311f commit acc730a

15 files changed

Lines changed: 350 additions & 46 deletions

File tree

packages/env/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
### New Features
2020

2121
- Add experimental WordPress Playground runtime support. Use `--runtime=playground` flag to start wp-env with Playground instead of Docker.
22+
- Add `cleanup` command to remove environment-specific resources (containers, volumes, networks, and local files) while preserving Docker images for faster re-starts.
23+
- Add `--force` flag to both `destroy` and `cleanup` commands to skip the confirmation prompt.
24+
- Rename `clean` command to `reset` for clarity. The `clean` command is now deprecated but still works as an alias.
25+
26+
### Deprecation
27+
28+
- The `clean` command is deprecated. Use `reset` instead. The `afterClean` lifecycle script is also deprecated in favor of `afterReset`.
2229

2330
## 10.38.0 (2026-01-16)
2431

packages/env/README.md

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ To reset the database:
186186
**⚠️ WARNING: This will permanently delete any posts, pages, media, etc. in the local WordPress installation.**
187187

188188
```sh
189-
$ wp-env clean all
189+
$ wp-env reset all
190190
$ wp-env start
191191
```
192192

@@ -340,15 +340,15 @@ Options:
340340
--debug Enable debug output. [boolean] [default: false]
341341
```
342342

343-
### `wp-env clean [environment]`
343+
### `wp-env reset [environment]`
344344

345345
```sh
346-
wp-env clean [environment]
346+
wp-env reset [environment]
347347

348-
Cleans the WordPress databases.
348+
Resets the WordPress databases.
349349

350350
Positionals:
351-
environment Which environments' databases to clean.
351+
environment Which environments' databases to reset.
352352
[string] [choices: "all", "development", "tests"] [default: "tests"]
353353
354354
Options:
@@ -469,17 +469,32 @@ To set the permalink to the year, month, and post name:
469469
wp-env run cli "wp rewrite structure /%year%/%monthnum%/%postname%/"
470470
```
471471

472+
### `wp-env cleanup`
473+
474+
```sh
475+
wp-env cleanup
476+
477+
Cleanup the WordPress environment. Removes docker containers, volumes, networks,
478+
and local files, but preserves docker images for faster re-starts.
479+
480+
Options:
481+
--debug Enable debug output. [boolean] [default: false]
482+
--scripts Execute any configured lifecycle scripts. [boolean] [default: true]
483+
--force Skip the confirmation prompt. [boolean] [default: false]
484+
```
485+
472486
### `wp-env destroy`
473487

474488
```sh
475489
wp-env destroy
476490

477-
Destroy the WordPress environment. Deletes docker containers, volumes, and
478-
networks associated with the WordPress environment and removes local files.
491+
Destroy the WordPress environment. Deletes docker containers, volumes, networks,
492+
and images associated with the WordPress environment and removes local files.
479493

480494
Options:
481495
--debug Enable debug output. [boolean] [default: false]
482496
--scripts Execute any configured lifecycle scripts. [boolean] [default: true]
497+
--force Skip the confirmation prompt. [boolean] [default: false]
483498
```
484499

485500
### `wp-env logs [environment]`
@@ -629,7 +644,8 @@ example, `WP_ENV_LIFECYCLE_SCRIPT_AFTER_START`. Keep in mind that these will be
629644
build won't break on subsequent executions.
630645

631646
* `afterStart`: Runs after `wp-env start` has finished setting up the environment.
632-
* `afterClean`: Runs after `wp-env clean` has finished cleaning the environment.
647+
* `afterReset`: Runs after `wp-env reset` has finished resetting the environment.
648+
* `afterCleanup`: Runs after `wp-env cleanup` has cleaned up the environment.
633649
* `afterDestroy`: Runs after `wp-env destroy` has destroyed the environment.
634650

635651
## Examples

packages/env/lib/cli.js

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,31 @@ module.exports = function cli() {
162162
() => {},
163163
withSpinner( env.stop )
164164
);
165+
yargs.command(
166+
'reset [environment]',
167+
wpYellow( 'Resets the WordPress databases.' ),
168+
( args ) => {
169+
args.positional( 'environment', {
170+
type: 'string',
171+
describe: "Which environments' databases to reset.",
172+
choices: [ 'all', 'development', 'tests' ],
173+
default: 'tests',
174+
} );
175+
args.option( 'scripts', {
176+
type: 'boolean',
177+
describe: 'Execute any configured lifecycle scripts.',
178+
default: true,
179+
} );
180+
},
181+
withSpinner( env.reset )
182+
);
165183
yargs.command(
166184
'clean [environment]',
167-
wpYellow( 'Cleans the WordPress databases.' ),
185+
chalk.gray( '[Deprecated: use reset] Resets the WordPress databases.' ),
168186
( args ) => {
169187
args.positional( 'environment', {
170188
type: 'string',
171-
describe: "Which environments' databases to clean.",
189+
describe: "Which environments' databases to reset.",
172190
choices: [ 'all', 'development', 'tests' ],
173191
default: 'tests',
174192
} );
@@ -246,17 +264,41 @@ module.exports = function cli() {
246264
yargs.command(
247265
'destroy',
248266
wpRed(
249-
'Destroy the WordPress environment. Deletes docker containers, volumes, and networks associated with the WordPress environment and removes local files.'
267+
'Destroy the WordPress environment. Deletes docker containers, volumes, networks, and images associated with the WordPress environment and removes local files.'
250268
),
251269
( args ) => {
252270
args.option( 'scripts', {
253271
type: 'boolean',
254272
describe: 'Execute any configured lifecycle scripts.',
255273
default: true,
256274
} );
275+
args.option( 'force', {
276+
type: 'boolean',
277+
describe: 'Skip the confirmation prompt.',
278+
default: false,
279+
} );
257280
},
258281
withSpinner( env.destroy )
259282
);
283+
yargs.command(
284+
'cleanup',
285+
wpYellow(
286+
'Cleanup the WordPress environment. Removes docker containers, volumes, networks, and local files, but preserves docker images for faster re-starts.'
287+
),
288+
( args ) => {
289+
args.option( 'scripts', {
290+
type: 'boolean',
291+
describe: 'Execute any configured lifecycle scripts.',
292+
default: true,
293+
} );
294+
args.option( 'force', {
295+
type: 'boolean',
296+
describe: 'Skip the confirmation prompt.',
297+
default: false,
298+
} );
299+
},
300+
withSpinner( env.cleanup )
301+
);
260302
yargs.command(
261303
'status',
262304
'Get the status of the wp-env environment including URLs, ports, and configuration.',

packages/env/lib/commands/clean.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ const { getRuntime, detectRuntime } = require( '../runtime' );
1717
*/
1818

1919
/**
20-
* Wipes the development server's database, the tests server's database, or both.
20+
* @deprecated Use `reset` instead.
21+
*
22+
* Resets the development server's database, the tests server's database, or both.
2123
*
2224
* @param {Object} options
23-
* @param {WPEnvironmentSelection} options.environment The environment to clean. Either 'development', 'tests', or 'all'.
25+
* @param {WPEnvironmentSelection} options.environment The environment to reset. Either 'development', 'tests', or 'all'.
2426
* @param {Object} options.spinner A CLI spinner which indicates progress.
2527
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
2628
* @param {boolean} options.debug True if debug mode is enabled.
@@ -31,11 +33,14 @@ module.exports = async function clean( {
3133
scripts,
3234
debug,
3335
} ) {
36+
spinner.warn( 'The `clean` command is deprecated. Use `reset` instead.' );
37+
3438
const config = await loadConfig( path.resolve( '.' ) );
3539
const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
3640

3741
await runtime.clean( config, { environment, spinner, debug } );
3842

43+
// Execute afterClean for backwards compatibility.
3944
if ( scripts ) {
4045
await executeLifecycleScript( 'afterClean', config, spinner );
4146
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
/**
3+
* External dependencies
4+
*/
5+
const fs = require( 'fs' ).promises;
6+
const path = require( 'path' );
7+
const { confirm } = require( '@inquirer/prompts' );
8+
9+
/**
10+
* Internal dependencies
11+
*/
12+
const { loadConfig } = require( '../config' );
13+
const { executeLifecycleScript } = require( '../execute-lifecycle-script' );
14+
const { getRuntime, detectRuntime } = require( '../runtime' );
15+
16+
/**
17+
* Cleanup the development server.
18+
*
19+
* Removes Docker containers, volumes, networks, and local files,
20+
* but preserves Docker images for faster re-starts.
21+
*
22+
* @param {Object} options
23+
* @param {Object} options.spinner A CLI spinner which indicates progress.
24+
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
25+
* @param {boolean} options.force If true, skips the confirmation prompt.
26+
* @param {boolean} options.debug True if debug mode is enabled.
27+
*/
28+
module.exports = async function cleanup( { spinner, scripts, force, debug } ) {
29+
const config = await loadConfig( path.resolve( '.' ) );
30+
31+
try {
32+
await fs.readdir( config.workDirectoryPath );
33+
} catch {
34+
spinner.text = 'Could not find any files to remove.';
35+
return;
36+
}
37+
38+
const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
39+
40+
spinner.info( runtime.getCleanupWarningMessage() );
41+
42+
let yesDelete = force;
43+
if ( ! force ) {
44+
try {
45+
yesDelete = await confirm( {
46+
message: 'Are you sure you want to continue?',
47+
default: false,
48+
} );
49+
} catch ( error ) {
50+
if ( error.name === 'ExitPromptError' ) {
51+
console.log( 'Cancelled.' );
52+
process.exit( 1 );
53+
}
54+
throw error;
55+
}
56+
}
57+
58+
spinner.start();
59+
60+
if ( ! yesDelete ) {
61+
spinner.text = 'Cancelled.';
62+
return;
63+
}
64+
65+
await runtime.cleanup( config, { spinner, debug } );
66+
67+
if ( scripts ) {
68+
await executeLifecycleScript( 'afterCleanup', config, spinner );
69+
}
70+
};

packages/env/lib/commands/destroy.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ const { getRuntime, detectRuntime } = require( '../runtime' );
1919
* @param {Object} options
2020
* @param {Object} options.spinner A CLI spinner which indicates progress.
2121
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
22+
* @param {boolean} options.force If true, skips the confirmation prompt.
2223
* @param {boolean} options.debug True if debug mode is enabled.
2324
*/
24-
module.exports = async function destroy( { spinner, scripts, debug } ) {
25+
module.exports = async function destroy( { spinner, scripts, force, debug } ) {
2526
const config = await loadConfig( path.resolve( '.' ) );
2627

2728
try {
@@ -35,18 +36,20 @@ module.exports = async function destroy( { spinner, scripts, debug } ) {
3536

3637
spinner.info( runtime.getDestroyWarningMessage() );
3738

38-
let yesDelete = false;
39-
try {
40-
yesDelete = await confirm( {
41-
message: 'Are you sure you want to continue?',
42-
default: false,
43-
} );
44-
} catch ( error ) {
45-
if ( error.name === 'ExitPromptError' ) {
46-
console.log( 'Cancelled.' );
47-
process.exit( 1 );
39+
let yesDelete = force;
40+
if ( ! force ) {
41+
try {
42+
yesDelete = await confirm( {
43+
message: 'Are you sure you want to continue?',
44+
default: false,
45+
} );
46+
} catch ( error ) {
47+
if ( error.name === 'ExitPromptError' ) {
48+
console.log( 'Cancelled.' );
49+
process.exit( 1 );
50+
}
51+
throw error;
4852
}
49-
throw error;
5053
}
5154

5255
spinner.start();

packages/env/lib/commands/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
*/
55
const start = require( './start' );
66
const stop = require( './stop' );
7+
const reset = require( './reset' );
78
const clean = require( './clean' );
89
const run = require( './run' );
910
const destroy = require( './destroy' );
11+
const cleanup = require( './cleanup' );
1012
const logs = require( './logs' );
1113
const status = require( './status' );
1214

1315
module.exports = {
1416
start,
1517
stop,
18+
reset,
1619
clean,
1720
run,
1821
destroy,
22+
cleanup,
1923
logs,
2024
status,
2125
};

packages/env/lib/commands/reset.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
/**
3+
* External dependencies
4+
*/
5+
const path = require( 'path' );
6+
7+
/**
8+
* Internal dependencies
9+
*/
10+
const { executeLifecycleScript } = require( '../execute-lifecycle-script' );
11+
const { loadConfig } = require( '../config' );
12+
const { getRuntime, detectRuntime } = require( '../runtime' );
13+
14+
/**
15+
* @typedef {import('../wordpress').WPEnvironment} WPEnvironment
16+
* @typedef {import('../wordpress').WPEnvironmentSelection} WPEnvironmentSelection
17+
*/
18+
19+
/**
20+
* Resets the development server's database, the tests server's database, or both.
21+
*
22+
* @param {Object} options
23+
* @param {WPEnvironmentSelection} options.environment The environment to reset. Either 'development', 'tests', or 'all'.
24+
* @param {Object} options.spinner A CLI spinner which indicates progress.
25+
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
26+
* @param {boolean} options.debug True if debug mode is enabled.
27+
*/
28+
module.exports = async function reset( {
29+
environment,
30+
spinner,
31+
scripts,
32+
debug,
33+
} ) {
34+
const config = await loadConfig( path.resolve( '.' ) );
35+
const runtime = getRuntime( detectRuntime( config.workDirectoryPath ) );
36+
37+
await runtime.clean( config, { environment, spinner, debug } );
38+
39+
if ( scripts ) {
40+
await executeLifecycleScript( 'afterReset', config, spinner );
41+
}
42+
};

packages/env/lib/config/get-config-from-environment-vars.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ function getLifecycleScriptOverrides() {
108108
const lifecycleEnvironmentVars = {
109109
WP_ENV_LIFECYCLE_SCRIPT_AFTER_START: 'afterStart',
110110
WP_ENV_LIFECYCLE_SCRIPT_AFTER_CLEAN: 'afterClean',
111+
WP_ENV_LIFECYCLE_SCRIPT_AFTER_RESET: 'afterReset',
112+
WP_ENV_LIFECYCLE_SCRIPT_AFTER_CLEANUP: 'afterCleanup',
111113
WP_ENV_LIFECYCLE_SCRIPT_AFTER_DESTROY: 'afterDestroy',
112114
};
113115
for ( const envVar in lifecycleEnvironmentVars ) {

packages/env/lib/config/parse-config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ async function getDefaultConfig(
235235
lifecycleScripts: {
236236
afterStart: null,
237237
afterClean: null,
238+
afterReset: null,
239+
afterCleanup: null,
238240
afterDestroy: null,
239241
},
240242
env: {

0 commit comments

Comments
 (0)