@wordpress/env 11.0.2-next.v.202602241322.0 → 11.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/lib/cli.js +5 -0
- package/lib/commands/start.js +17 -12
- package/lib/config/load-config.js +25 -4
- package/lib/config/parse-config.js +11 -0
- package/lib/config/post-process-config.js +30 -8
- package/lib/config/test/parse-config.js +68 -0
- package/lib/config/test/post-process-config.js +56 -18
- package/lib/port-utils.js +87 -0
- package/lib/resolve-available-ports.js +136 -0
- package/lib/runtime/docker/{init-config.js → docker-config.js} +50 -92
- package/lib/runtime/docker/index.js +53 -72
- package/lib/runtime/playground/index.js +12 -16
- package/lib/test/cli.js +14 -1
- package/lib/test/port-utils.js +136 -0
- package/lib/test/resolve-available-ports.js +155 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -363,7 +363,10 @@ Options:
|
|
|
363
363
|
with a built-in web UI. See
|
|
364
364
|
https://github.com/NoiseByNorthwest/php-spx for more information.
|
|
365
365
|
[string]
|
|
366
|
-
--scripts
|
|
366
|
+
--scripts Execute any configured lifecycle scripts.
|
|
367
|
+
[boolean] [default: true]
|
|
368
|
+
--auto-port Automatically find available ports when configured ports are
|
|
369
|
+
busy. [boolean]
|
|
367
370
|
```
|
|
368
371
|
|
|
369
372
|
### `wp-env stop`
|
|
@@ -591,8 +594,10 @@ You can customize the WordPress installation, plugins and themes that the develo
|
|
|
591
594
|
| `"phpVersion"` | `string\|null` | `null` | The PHP version to use. If `null` is specified, `wp-env` will use the default version used with production release of WordPress. |
|
|
592
595
|
| `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. |
|
|
593
596
|
| `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. |
|
|
594
|
-
| `"port"` | `integer` | `8888` | The
|
|
597
|
+
| `"port"` | `integer` | `8888` | The port number to use for the installation. |
|
|
595
598
|
| `"testsEnvironment"` | `boolean` | `false` | _Deprecated._ Whether to create a separate test environment with its own database and containers. Use `--config` with a separate config file instead. |
|
|
599
|
+
| `"testsPort"` | `integer` | `8889` | The port number for the test site. |
|
|
600
|
+
| `"autoPort"` | `boolean` | `false` | Whether to automatically find available HTTP ports when configured ports are busy. |
|
|
596
601
|
| `"config"` | `Object` | See below. | Mapping of wp-config.php constants to their desired values. |
|
|
597
602
|
| `"mappings"` | `Object` | `"{}"` | Mapping of WordPress directories to local directories to be mounted in the WordPress instance. |
|
|
598
603
|
| `"mysqlPort"` | `integer` | `null` (randomly assigned) | The MySQL port number to expose. |
|
|
@@ -603,6 +608,18 @@ You can customize the WordPress installation, plugins and themes that the develo
|
|
|
603
608
|
|
|
604
609
|
_Note: the port number environment variable (`WP_ENV_PORT`) takes precedence over the .wp-env.json value._
|
|
605
610
|
|
|
611
|
+
### Automatic Port Selection
|
|
612
|
+
|
|
613
|
+
By default, `wp-env` uses fixed ports (`8888` for development, `8889` for tests). If a port is busy, Docker will report an error at start time.
|
|
614
|
+
|
|
615
|
+
To opt in to automatic port selection, pass the `--auto-port` flag:
|
|
616
|
+
|
|
617
|
+
```sh
|
|
618
|
+
wp-env start --auto-port
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
When `--auto-port` (or `"autoPort": true`) is enabled and a configured port is busy, `wp-env` scans upward from the configured port to find the next available one (for example: `8888`, `8889`, `8890`, ...). Automatic port selection is disabled when `CI` is set.
|
|
622
|
+
|
|
606
623
|
Several types of strings can be passed into the `core`, `plugins`, `themes`, and `mappings` fields.
|
|
607
624
|
|
|
608
625
|
| Type | Format | Example(s) |
|
|
@@ -785,7 +802,8 @@ You can tell `wp-env` to use a custom port number so that your instance does not
|
|
|
785
802
|
|
|
786
803
|
These can also be set via environment variables:
|
|
787
804
|
|
|
788
|
-
- `WP_ENV_PORT` to override the web server's port.
|
|
805
|
+
- `WP_ENV_PORT` to override the development environment's web server's port.
|
|
806
|
+
- `WP_ENV_TESTS_PORT` to override the testing environment's web server's port.
|
|
789
807
|
- phpMyAdmin is not enabled by default. Enable it with `"phpmyadmin": true` in `.wp-env.json`. The Docker runtime port can also be overridden via `WP_ENV_PHPMYADMIN_PORT`.
|
|
790
808
|
- By default, MySQL isn't exposed to the host, which means no chance of port conflicts. But this can also be overridden via `WP_ENV_MYSQL_PORT`.
|
|
791
809
|
|
package/lib/cli.js
CHANGED
|
@@ -151,6 +151,11 @@ module.exports = function cli() {
|
|
|
151
151
|
choices: getAvailableRuntimes(),
|
|
152
152
|
default: 'docker',
|
|
153
153
|
} );
|
|
154
|
+
args.option( 'auto-port', {
|
|
155
|
+
type: 'boolean',
|
|
156
|
+
describe:
|
|
157
|
+
'Automatically find available ports when configured ports are busy. Overrides the .wp-env.json "autoPort" setting.',
|
|
158
|
+
} );
|
|
154
159
|
},
|
|
155
160
|
withSpinner( env.start )
|
|
156
161
|
);
|
package/lib/commands/start.js
CHANGED
|
@@ -22,14 +22,15 @@ const { getRuntime, getSavedRuntime, saveRuntime } = require( '../runtime' );
|
|
|
22
22
|
* Starts the development server.
|
|
23
23
|
*
|
|
24
24
|
* @param {Object} options
|
|
25
|
-
* @param {Object} options.spinner
|
|
26
|
-
* @param {boolean} options.update
|
|
27
|
-
* @param {string} options.xdebug
|
|
28
|
-
* @param {string} options.spx
|
|
29
|
-
* @param {boolean} options.scripts
|
|
30
|
-
* @param {boolean} options.debug
|
|
31
|
-
* @param {string} options.runtime
|
|
32
|
-
* @param {
|
|
25
|
+
* @param {Object} options.spinner A CLI spinner which indicates progress.
|
|
26
|
+
* @param {boolean} options.update If true, update sources.
|
|
27
|
+
* @param {string} options.xdebug The Xdebug mode to set.
|
|
28
|
+
* @param {string} options.spx The SPX mode to set.
|
|
29
|
+
* @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
|
|
30
|
+
* @param {boolean} options.debug True if debug mode is enabled.
|
|
31
|
+
* @param {string} options.runtime The runtime to use ('docker' or 'playground').
|
|
32
|
+
* @param {boolean} options.autoPort If true, automatically find available ports when configured ports are busy.
|
|
33
|
+
* @param {string|null} options.config Path to a custom .wp-env.json configuration file.
|
|
33
34
|
*/
|
|
34
35
|
module.exports = async function start( {
|
|
35
36
|
spinner,
|
|
@@ -39,6 +40,7 @@ module.exports = async function start( {
|
|
|
39
40
|
scripts,
|
|
40
41
|
debug,
|
|
41
42
|
runtime: runtimeName = 'docker',
|
|
43
|
+
autoPort,
|
|
42
44
|
config: customConfigPath,
|
|
43
45
|
} ) {
|
|
44
46
|
spinner.text = 'Reading configuration.';
|
|
@@ -50,8 +52,14 @@ module.exports = async function start( {
|
|
|
50
52
|
await checkForLegacyInstall( spinner );
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
const config = await loadConfig( path.resolve( '.' ), customConfigPath
|
|
55
|
+
const config = await loadConfig( path.resolve( '.' ), customConfigPath, {
|
|
56
|
+
resolvePorts: true,
|
|
57
|
+
autoPort,
|
|
58
|
+
spinner,
|
|
59
|
+
} );
|
|
54
60
|
config.debug = debug;
|
|
61
|
+
config.xdebug = xdebug;
|
|
62
|
+
config.spx = spx;
|
|
55
63
|
|
|
56
64
|
// Check if switching runtimes and prompt user to destroy old environment first.
|
|
57
65
|
const savedRuntime = await getSavedRuntime( config.workDirectoryPath );
|
|
@@ -109,9 +117,6 @@ module.exports = async function start( {
|
|
|
109
117
|
result = await runtime.start( config, {
|
|
110
118
|
spinner,
|
|
111
119
|
update,
|
|
112
|
-
xdebug,
|
|
113
|
-
spx,
|
|
114
|
-
debug,
|
|
115
120
|
} );
|
|
116
121
|
|
|
117
122
|
// Save the runtime type after successful start.
|
|
@@ -13,6 +13,7 @@ const md5 = require( '../md5' );
|
|
|
13
13
|
const { parseConfig, getConfigFilePath } = require( './parse-config' );
|
|
14
14
|
const { ValidationError } = require( './validate-config' );
|
|
15
15
|
const postProcessConfig = require( './post-process-config' );
|
|
16
|
+
const { createPortResolver } = require( '../resolve-available-ports' );
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* @typedef {import('./parse-config').WPRootConfig} WPRootConfig
|
|
@@ -36,14 +37,19 @@ const postProcessConfig = require( './post-process-config' );
|
|
|
36
37
|
/**
|
|
37
38
|
* Loads any configuration from a given directory.
|
|
38
39
|
*
|
|
39
|
-
* @param {string} configDirectoryPath
|
|
40
|
-
* @param {string|null} customConfigPath
|
|
40
|
+
* @param {string} configDirectoryPath The directory we want to load the config from.
|
|
41
|
+
* @param {string|null} customConfigPath Optional custom config file path.
|
|
42
|
+
* @param {Object} options Options for loading the config.
|
|
43
|
+
* @param {boolean} options.resolvePorts Whether HTTP ports should be resolved for this command.
|
|
44
|
+
* @param {boolean} options.autoPort CLI override for automatic port selection.
|
|
45
|
+
* @param {Object} options.spinner A CLI spinner used by the port resolver.
|
|
41
46
|
*
|
|
42
47
|
* @return {Promise<WPConfig>} The config object we've loaded.
|
|
43
48
|
*/
|
|
44
49
|
module.exports = async function loadConfig(
|
|
45
50
|
configDirectoryPath,
|
|
46
|
-
customConfigPath = null
|
|
51
|
+
customConfigPath = null,
|
|
52
|
+
{ resolvePorts = false, autoPort, spinner } = {}
|
|
47
53
|
) {
|
|
48
54
|
const configFilePath = getConfigFilePath(
|
|
49
55
|
configDirectoryPath,
|
|
@@ -75,10 +81,25 @@ module.exports = async function loadConfig(
|
|
|
75
81
|
customConfigPath
|
|
76
82
|
);
|
|
77
83
|
|
|
84
|
+
let portResolver;
|
|
85
|
+
if ( resolvePorts ) {
|
|
86
|
+
let shouldAutoPort =
|
|
87
|
+
autoPort !== undefined ? autoPort : config.autoPort;
|
|
88
|
+
|
|
89
|
+
// Automatic port selection is undesirable in CI where determinism matters.
|
|
90
|
+
if ( process.env.CI ) {
|
|
91
|
+
shouldAutoPort = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ( shouldAutoPort ) {
|
|
95
|
+
portResolver = createPortResolver( spinner );
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
78
99
|
// Make sure to perform any additional post-processing that
|
|
79
100
|
// may be needed before the config object is ready for
|
|
80
101
|
// consumption elsewhere in the tool.
|
|
81
|
-
config = postProcessConfig( config );
|
|
102
|
+
config = await postProcessConfig( config, { portResolver } );
|
|
82
103
|
|
|
83
104
|
return {
|
|
84
105
|
name: path.basename( configDirectoryPath ),
|
|
@@ -35,6 +35,7 @@ const mergeConfigs = require( './merge-configs' );
|
|
|
35
35
|
* @typedef WPRootConfigOptions
|
|
36
36
|
* @property {number} port The port to use in the development environment.
|
|
37
37
|
* @property {number} testsPort The port to use in the tests environment.
|
|
38
|
+
* @property {boolean} autoPort Whether to automatically select a nearby available HTTP port.
|
|
38
39
|
* @property {Object.<string, string|null>} lifecycleScripts The scripts to run at certain points in the command lifecycle.
|
|
39
40
|
* @property {Object.<string, string|null>} lifecycleScripts.afterStart The script to run after the "start" command has completed.
|
|
40
41
|
* @property {Object.<string, string|null>} lifecycleScripts.afterClean The script to run after the "clean" command has completed.
|
|
@@ -89,6 +90,7 @@ const DEFAULT_ENVIRONMENT_CONFIG = {
|
|
|
89
90
|
themes: [],
|
|
90
91
|
port: 8888,
|
|
91
92
|
testsPort: 8889,
|
|
93
|
+
autoPort: false,
|
|
92
94
|
mysqlPort: null,
|
|
93
95
|
phpmyadmin: false,
|
|
94
96
|
phpmyadminPort: null,
|
|
@@ -385,6 +387,14 @@ async function parseRootConfig( configFile, rawConfig, options ) {
|
|
|
385
387
|
checkPort( configFile, `testsPort`, rawConfig.testsPort );
|
|
386
388
|
parsedConfig.testsPort = rawConfig.testsPort;
|
|
387
389
|
}
|
|
390
|
+
if ( rawConfig.autoPort !== undefined ) {
|
|
391
|
+
if ( typeof rawConfig.autoPort !== 'boolean' ) {
|
|
392
|
+
throw new ValidationError(
|
|
393
|
+
`Invalid ${ configFile }: "autoPort" must be a boolean.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
parsedConfig.autoPort = rawConfig.autoPort;
|
|
397
|
+
}
|
|
388
398
|
if ( rawConfig.testsEnvironment !== undefined ) {
|
|
389
399
|
if ( typeof rawConfig.testsEnvironment !== 'boolean' ) {
|
|
390
400
|
throw new ValidationError(
|
|
@@ -470,6 +480,7 @@ async function parseEnvironmentConfig(
|
|
|
470
480
|
// configuration options that we will parse.
|
|
471
481
|
switch ( key ) {
|
|
472
482
|
case 'testsPort':
|
|
483
|
+
case 'autoPort':
|
|
473
484
|
case 'testsEnvironment':
|
|
474
485
|
case 'lifecycleScripts':
|
|
475
486
|
case 'env': {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const mergeConfigs = require( './merge-configs' );
|
|
6
6
|
const addOrReplacePort = require( './add-or-replace-port' );
|
|
7
7
|
const { ValidationError } = require( './validate-config' );
|
|
8
|
+
const { resolveConfigPorts } = require( '../resolve-available-ports' );
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @typedef {import('./parse-config').WPRootConfig} WPRootConfig
|
|
@@ -14,15 +15,27 @@ const { ValidationError } = require( './validate-config' );
|
|
|
14
15
|
/**
|
|
15
16
|
* Performs any additional post-processing on the config object.
|
|
16
17
|
*
|
|
17
|
-
* @param {WPEnvironmentConfig} config
|
|
18
|
+
* @param {WPEnvironmentConfig} config The config to process.
|
|
19
|
+
* @param {Object} options Options for post-processing.
|
|
20
|
+
* @param {Object} options.portResolver An optional port resolver for automatic port selection.
|
|
18
21
|
*
|
|
19
|
-
* @return {WPEnvironmentConfig} The config after post-processing.
|
|
22
|
+
* @return {Promise<WPEnvironmentConfig>} The config after post-processing.
|
|
20
23
|
*/
|
|
21
|
-
module.exports = function postProcessConfig(
|
|
24
|
+
module.exports = async function postProcessConfig(
|
|
25
|
+
config,
|
|
26
|
+
{ portResolver } = {}
|
|
27
|
+
) {
|
|
22
28
|
// Make sure that we're operating on a config object that has
|
|
23
29
|
// complete environment configs for convenience.
|
|
24
30
|
config = mergeRootToEnvironments( config );
|
|
25
31
|
|
|
32
|
+
// Resolve ports after merging (so environment ports are finalized)
|
|
33
|
+
// but before appending ports to URLs. This way appendPortToWPConfigs
|
|
34
|
+
// uses the resolved ports and we don't need to fix URLs afterward.
|
|
35
|
+
if ( portResolver ) {
|
|
36
|
+
config = await resolveConfigPorts( config, portResolver );
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
config = appendPortToWPConfigs( config );
|
|
27
40
|
|
|
28
41
|
validate( config );
|
|
@@ -66,6 +79,10 @@ function mergeRootToEnvironments( config ) {
|
|
|
66
79
|
delete config.testsPort;
|
|
67
80
|
}
|
|
68
81
|
}
|
|
82
|
+
if ( config.autoPort !== undefined ) {
|
|
83
|
+
removedRootOptions.autoPort = config.autoPort;
|
|
84
|
+
delete config.autoPort;
|
|
85
|
+
}
|
|
69
86
|
if ( config.lifecycleScripts !== undefined ) {
|
|
70
87
|
removedRootOptions.lifecycleScripts = config.lifecycleScripts;
|
|
71
88
|
delete config.lifecycleScripts;
|
|
@@ -117,7 +134,10 @@ function appendPortToWPConfigs( config ) {
|
|
|
117
134
|
continue;
|
|
118
135
|
}
|
|
119
136
|
|
|
120
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
config.env[ env ].port === undefined ||
|
|
139
|
+
config.env[ env ].port === null
|
|
140
|
+
) {
|
|
121
141
|
continue;
|
|
122
142
|
}
|
|
123
143
|
|
|
@@ -157,10 +177,12 @@ function validatePortUniqueness( config ) {
|
|
|
157
177
|
if ( env === 'tests' && testsDisabled ) {
|
|
158
178
|
continue;
|
|
159
179
|
}
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
if (
|
|
181
|
+
config.env[ env ].port === undefined ||
|
|
182
|
+
config.env[ env ].port === null
|
|
183
|
+
) {
|
|
184
|
+
// Null ports will be resolved later; skip validation.
|
|
185
|
+
continue;
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
environmentPorts[ env ] = config.env[ env ].port;
|
|
@@ -21,6 +21,7 @@ jest.mock( '../../wordpress', () => ( {
|
|
|
21
21
|
const DEFAULT_CONFIG = {
|
|
22
22
|
port: 8888,
|
|
23
23
|
testsPort: 8889,
|
|
24
|
+
autoPort: false,
|
|
24
25
|
mysqlPort: null,
|
|
25
26
|
phpmyadmin: false,
|
|
26
27
|
phpmyadminPort: null,
|
|
@@ -192,6 +193,19 @@ describe( 'parseConfig', () => {
|
|
|
192
193
|
expect( parsed ).toEqual( expected );
|
|
193
194
|
} );
|
|
194
195
|
|
|
196
|
+
it( 'should accept autoPort as a boolean', async () => {
|
|
197
|
+
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
198
|
+
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
199
|
+
return {
|
|
200
|
+
autoPort: true,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
} );
|
|
204
|
+
|
|
205
|
+
const parsed = await parseConfig( '/test/gutenberg', '/cache' );
|
|
206
|
+
expect( parsed.autoPort ).toEqual( true );
|
|
207
|
+
} );
|
|
208
|
+
|
|
195
209
|
it( 'should parse core, plugin, theme, and mapping sources', async () => {
|
|
196
210
|
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
197
211
|
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
@@ -377,6 +391,24 @@ describe( 'parseConfig', () => {
|
|
|
377
391
|
);
|
|
378
392
|
} );
|
|
379
393
|
|
|
394
|
+
it( 'throws for non-boolean autoPort', async () => {
|
|
395
|
+
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
396
|
+
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
397
|
+
return {
|
|
398
|
+
autoPort: 'true',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
} );
|
|
402
|
+
|
|
403
|
+
await expect(
|
|
404
|
+
parseConfig( '/test/gutenberg', '/cache' )
|
|
405
|
+
).rejects.toEqual(
|
|
406
|
+
new ValidationError(
|
|
407
|
+
`Invalid /test/gutenberg/.wp-env.json: "autoPort" must be a boolean.`
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
} );
|
|
411
|
+
|
|
380
412
|
it( 'throws for root-only config options', async () => {
|
|
381
413
|
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
382
414
|
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
@@ -406,6 +438,42 @@ describe( 'parseConfig', () => {
|
|
|
406
438
|
);
|
|
407
439
|
} );
|
|
408
440
|
|
|
441
|
+
it( 'throws when port is null', async () => {
|
|
442
|
+
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
443
|
+
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
444
|
+
return {
|
|
445
|
+
port: null,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
} );
|
|
449
|
+
|
|
450
|
+
await expect(
|
|
451
|
+
parseConfig( '/test/gutenberg', '/cache' )
|
|
452
|
+
).rejects.toEqual(
|
|
453
|
+
new ValidationError(
|
|
454
|
+
`Invalid /test/gutenberg/.wp-env.json: "port" must be an integer.`
|
|
455
|
+
)
|
|
456
|
+
);
|
|
457
|
+
} );
|
|
458
|
+
|
|
459
|
+
it( 'throws when testsPort is null', async () => {
|
|
460
|
+
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
461
|
+
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
462
|
+
return {
|
|
463
|
+
testsPort: null,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
} );
|
|
467
|
+
|
|
468
|
+
await expect(
|
|
469
|
+
parseConfig( '/test/gutenberg', '/cache' )
|
|
470
|
+
).rejects.toEqual(
|
|
471
|
+
new ValidationError(
|
|
472
|
+
`Invalid /test/gutenberg/.wp-env.json: "testsPort" must be an integer.`
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
} );
|
|
476
|
+
|
|
409
477
|
it( 'should parse phpmyadmin configuration for a given environment', async () => {
|
|
410
478
|
readRawConfigFile.mockImplementation( async ( configFile ) => {
|
|
411
479
|
if ( configFile === '/test/gutenberg/.wp-env.json' ) {
|
|
@@ -10,8 +10,8 @@ describe( 'postProcessConfig', () => {
|
|
|
10
10
|
jest.clearAllMocks();
|
|
11
11
|
} );
|
|
12
12
|
|
|
13
|
-
it( 'should merge relevant root options into environment options', () => {
|
|
14
|
-
const processed = postProcessConfig( {
|
|
13
|
+
it( 'should merge relevant root options into environment options', async () => {
|
|
14
|
+
const processed = await postProcessConfig( {
|
|
15
15
|
port: 123,
|
|
16
16
|
testsPort: 456,
|
|
17
17
|
coreSource: {
|
|
@@ -152,8 +152,8 @@ describe( 'postProcessConfig', () => {
|
|
|
152
152
|
} );
|
|
153
153
|
} );
|
|
154
154
|
|
|
155
|
-
it( 'should not merge some root options into environment options', () => {
|
|
156
|
-
const processed = postProcessConfig( {
|
|
155
|
+
it( 'should not merge some root options into environment options', async () => {
|
|
156
|
+
const processed = await postProcessConfig( {
|
|
157
157
|
port: 8888,
|
|
158
158
|
testsPort: 8889,
|
|
159
159
|
lifecycleScripts: {
|
|
@@ -183,8 +183,8 @@ describe( 'postProcessConfig', () => {
|
|
|
183
183
|
} );
|
|
184
184
|
|
|
185
185
|
describe( 'appendPortToWPConfigs', () => {
|
|
186
|
-
it( 'should add port to certain environment config options', () => {
|
|
187
|
-
const processed = postProcessConfig( {
|
|
186
|
+
it( 'should add port to certain environment config options', async () => {
|
|
187
|
+
const processed = await postProcessConfig( {
|
|
188
188
|
port: 123,
|
|
189
189
|
config: {
|
|
190
190
|
WP_TESTS_DOMAIN: 'localhost',
|
|
@@ -231,8 +231,8 @@ describe( 'postProcessConfig', () => {
|
|
|
231
231
|
} );
|
|
232
232
|
} );
|
|
233
233
|
|
|
234
|
-
it( 'should not overwrite port in WP_HOME', () => {
|
|
235
|
-
const processed = postProcessConfig( {
|
|
234
|
+
it( 'should not overwrite port in WP_HOME', async () => {
|
|
235
|
+
const processed = await postProcessConfig( {
|
|
236
236
|
env: {
|
|
237
237
|
development: {
|
|
238
238
|
port: 123,
|
|
@@ -274,11 +274,35 @@ describe( 'postProcessConfig', () => {
|
|
|
274
274
|
},
|
|
275
275
|
} );
|
|
276
276
|
} );
|
|
277
|
+
|
|
278
|
+
it( 'should not append port to URLs when port is null', async () => {
|
|
279
|
+
const processed = await postProcessConfig( {
|
|
280
|
+
port: null,
|
|
281
|
+
testsPort: null,
|
|
282
|
+
config: {
|
|
283
|
+
WP_TESTS_DOMAIN: 'localhost',
|
|
284
|
+
WP_SITEURL: 'http://localhost',
|
|
285
|
+
WP_HOME: 'http://localhost',
|
|
286
|
+
},
|
|
287
|
+
env: {
|
|
288
|
+
development: {},
|
|
289
|
+
tests: {},
|
|
290
|
+
},
|
|
291
|
+
} );
|
|
292
|
+
|
|
293
|
+
// Null ports should not be appended to URLs.
|
|
294
|
+
expect( processed.env.development.config.WP_SITEURL ).toEqual(
|
|
295
|
+
'http://localhost'
|
|
296
|
+
);
|
|
297
|
+
expect( processed.env.tests.config.WP_SITEURL ).toEqual(
|
|
298
|
+
'http://localhost'
|
|
299
|
+
);
|
|
300
|
+
} );
|
|
277
301
|
} );
|
|
278
302
|
|
|
279
303
|
describe( 'validatePortUniqueness', () => {
|
|
280
|
-
it( 'should fail when two environments have the same port', () => {
|
|
281
|
-
expect(
|
|
304
|
+
it( 'should fail when two environments have the same port', async () => {
|
|
305
|
+
await expect(
|
|
282
306
|
postProcessConfig( {
|
|
283
307
|
env: {
|
|
284
308
|
development: {
|
|
@@ -288,16 +312,16 @@ describe( 'postProcessConfig', () => {
|
|
|
288
312
|
port: 123,
|
|
289
313
|
},
|
|
290
314
|
},
|
|
291
|
-
} )
|
|
292
|
-
|
|
315
|
+
} )
|
|
316
|
+
).rejects.toThrow(
|
|
293
317
|
new ValidationError(
|
|
294
318
|
'The "development" and "tests" environments may not have the same port.'
|
|
295
319
|
)
|
|
296
320
|
);
|
|
297
321
|
} );
|
|
298
322
|
|
|
299
|
-
it( 'should skip port validation for disabled tests environment', () => {
|
|
300
|
-
expect(
|
|
323
|
+
it( 'should skip port validation for disabled tests environment', async () => {
|
|
324
|
+
await expect(
|
|
301
325
|
postProcessConfig( {
|
|
302
326
|
testsEnvironment: false,
|
|
303
327
|
port: 123,
|
|
@@ -305,14 +329,28 @@ describe( 'postProcessConfig', () => {
|
|
|
305
329
|
development: {},
|
|
306
330
|
tests: {},
|
|
307
331
|
},
|
|
308
|
-
} )
|
|
309
|
-
|
|
332
|
+
} )
|
|
333
|
+
).resolves.toBeDefined();
|
|
334
|
+
} );
|
|
335
|
+
|
|
336
|
+
it( 'should not fail when both environments have null ports', async () => {
|
|
337
|
+
const processed = await postProcessConfig( {
|
|
338
|
+
port: null,
|
|
339
|
+
testsPort: null,
|
|
340
|
+
env: {
|
|
341
|
+
development: {},
|
|
342
|
+
tests: {},
|
|
343
|
+
},
|
|
344
|
+
} );
|
|
345
|
+
|
|
346
|
+
expect( processed.env.development.port ).toEqual( null );
|
|
347
|
+
expect( processed.env.tests.port ).toEqual( null );
|
|
310
348
|
} );
|
|
311
349
|
} );
|
|
312
350
|
|
|
313
351
|
describe( 'testsEnvironment', () => {
|
|
314
|
-
it( 'should ignore env overrides entirely when testsEnvironment is false', () => {
|
|
315
|
-
const processed = postProcessConfig( {
|
|
352
|
+
it( 'should ignore env overrides entirely when testsEnvironment is false', async () => {
|
|
353
|
+
const processed = await postProcessConfig( {
|
|
316
354
|
testsEnvironment: false,
|
|
317
355
|
port: 123,
|
|
318
356
|
testsPort: 456,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
const net = require( 'net' );
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MIN_PORT = 49152;
|
|
8
|
+
const DEFAULT_MAX_PORT = 65535;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a port is available for use.
|
|
12
|
+
*
|
|
13
|
+
* @param {number} port The port to check.
|
|
14
|
+
* @return {Promise<boolean>} True if the port is available, false otherwise.
|
|
15
|
+
*/
|
|
16
|
+
async function isPortAvailable( port ) {
|
|
17
|
+
return new Promise( ( resolve ) => {
|
|
18
|
+
const server = net.createServer();
|
|
19
|
+
|
|
20
|
+
server.once( 'error', ( err ) => {
|
|
21
|
+
if ( err.code === 'EADDRINUSE' || err.code === 'EACCES' ) {
|
|
22
|
+
resolve( false );
|
|
23
|
+
} else {
|
|
24
|
+
// For other errors, assume port is not available
|
|
25
|
+
resolve( false );
|
|
26
|
+
}
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
server.once( 'listening', () => {
|
|
30
|
+
server.close( () => {
|
|
31
|
+
resolve( true );
|
|
32
|
+
} );
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
server.listen( port, '0.0.0.0' );
|
|
36
|
+
} );
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Finds an available port, starting with the preferred port.
|
|
41
|
+
* Falls back to scanning upward from the preferred port.
|
|
42
|
+
*
|
|
43
|
+
* @param {Object} options Options for finding a port.
|
|
44
|
+
* @param {number} options.preferredPort The preferred port to try first.
|
|
45
|
+
* @param {number} options.minPort Minimum port for fallback scanning.
|
|
46
|
+
* @param {number} options.maxPort Maximum port for fallback scanning.
|
|
47
|
+
* @param {number[]} options.exclude Ports to exclude from selection.
|
|
48
|
+
* @return {Promise<number>} An available port number.
|
|
49
|
+
* @throws {Error} If no available port is found within the range.
|
|
50
|
+
*/
|
|
51
|
+
async function findAvailablePort( {
|
|
52
|
+
preferredPort,
|
|
53
|
+
minPort = preferredPort,
|
|
54
|
+
maxPort = DEFAULT_MAX_PORT,
|
|
55
|
+
exclude = [],
|
|
56
|
+
} ) {
|
|
57
|
+
// Try the preferred port first if it's not excluded
|
|
58
|
+
if ( ! exclude.includes( preferredPort ) ) {
|
|
59
|
+
const isAvailable = await isPortAvailable( preferredPort );
|
|
60
|
+
if ( isAvailable ) {
|
|
61
|
+
return preferredPort;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If preferred port is not available, scan upward from the selected minimum.
|
|
66
|
+
const startPort = Math.max( minPort, preferredPort + 1 );
|
|
67
|
+
for ( let port = startPort; port <= maxPort; port++ ) {
|
|
68
|
+
if ( exclude.includes( port ) ) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const isAvailable = await isPortAvailable( port );
|
|
72
|
+
if ( isAvailable ) {
|
|
73
|
+
return port;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error(
|
|
78
|
+
`No available port found in range ${ startPort }-${ maxPort }.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
isPortAvailable,
|
|
84
|
+
findAvailablePort,
|
|
85
|
+
DEFAULT_MIN_PORT,
|
|
86
|
+
DEFAULT_MAX_PORT,
|
|
87
|
+
};
|