@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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
const net = require( 'net' );
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
const {
|
|
11
|
+
isPortAvailable,
|
|
12
|
+
findAvailablePort,
|
|
13
|
+
DEFAULT_MIN_PORT,
|
|
14
|
+
DEFAULT_MAX_PORT,
|
|
15
|
+
} = require( '../port-utils' );
|
|
16
|
+
|
|
17
|
+
jest.mock( 'net' );
|
|
18
|
+
|
|
19
|
+
describe( 'port-utils', () => {
|
|
20
|
+
afterEach( () => {
|
|
21
|
+
jest.restoreAllMocks();
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Helper that configures net.createServer to return fresh mock servers.
|
|
26
|
+
* Takes a function that receives the port and returns true if available.
|
|
27
|
+
*
|
|
28
|
+
* @param {Function} isAvailable A function (port) => boolean.
|
|
29
|
+
*/
|
|
30
|
+
function mockPortAvailability( isAvailable ) {
|
|
31
|
+
net.createServer.mockImplementation( () => {
|
|
32
|
+
let errorCb, listenCb;
|
|
33
|
+
|
|
34
|
+
const server = {
|
|
35
|
+
once: jest.fn( ( event, cb ) => {
|
|
36
|
+
if ( event === 'error' ) {
|
|
37
|
+
errorCb = cb;
|
|
38
|
+
}
|
|
39
|
+
if ( event === 'listening' ) {
|
|
40
|
+
listenCb = cb;
|
|
41
|
+
}
|
|
42
|
+
} ),
|
|
43
|
+
listen: jest.fn( ( port ) => {
|
|
44
|
+
if ( isAvailable( port ) ) {
|
|
45
|
+
server.close.mockImplementation( ( cb ) => cb() );
|
|
46
|
+
listenCb();
|
|
47
|
+
} else {
|
|
48
|
+
errorCb( { code: 'EADDRINUSE' } );
|
|
49
|
+
}
|
|
50
|
+
} ),
|
|
51
|
+
close: jest.fn(),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return server;
|
|
55
|
+
} );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe( 'isPortAvailable', () => {
|
|
59
|
+
it( 'returns true for an available port', async () => {
|
|
60
|
+
mockPortAvailability( () => true );
|
|
61
|
+
const result = await isPortAvailable( 8888 );
|
|
62
|
+
expect( result ).toBe( true );
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
it( 'returns false for a port in use', async () => {
|
|
66
|
+
mockPortAvailability( () => false );
|
|
67
|
+
const result = await isPortAvailable( 8888 );
|
|
68
|
+
expect( result ).toBe( false );
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
it( 'returns false for EACCES error', async () => {
|
|
72
|
+
net.createServer.mockImplementation( () => {
|
|
73
|
+
let errorCb;
|
|
74
|
+
const server = {
|
|
75
|
+
once: jest.fn( ( event, cb ) => {
|
|
76
|
+
if ( event === 'error' ) {
|
|
77
|
+
errorCb = cb;
|
|
78
|
+
}
|
|
79
|
+
} ),
|
|
80
|
+
listen: jest.fn( () => {
|
|
81
|
+
errorCb( { code: 'EACCES' } );
|
|
82
|
+
} ),
|
|
83
|
+
close: jest.fn(),
|
|
84
|
+
};
|
|
85
|
+
return server;
|
|
86
|
+
} );
|
|
87
|
+
const result = await isPortAvailable( 80 );
|
|
88
|
+
expect( result ).toBe( false );
|
|
89
|
+
} );
|
|
90
|
+
} );
|
|
91
|
+
|
|
92
|
+
describe( 'findAvailablePort', () => {
|
|
93
|
+
it( 'returns the preferred port when available', async () => {
|
|
94
|
+
mockPortAvailability( () => true );
|
|
95
|
+
const result = await findAvailablePort( {
|
|
96
|
+
preferredPort: 8888,
|
|
97
|
+
} );
|
|
98
|
+
expect( result ).toBe( 8888 );
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
it( 'finds an alternative when preferred port is busy', async () => {
|
|
102
|
+
mockPortAvailability( ( port ) => port !== 8888 );
|
|
103
|
+
const result = await findAvailablePort( {
|
|
104
|
+
preferredPort: 8888,
|
|
105
|
+
} );
|
|
106
|
+
expect( result ).toBe( 8889 );
|
|
107
|
+
} );
|
|
108
|
+
|
|
109
|
+
it( 'excludes specified ports', async () => {
|
|
110
|
+
mockPortAvailability( () => true );
|
|
111
|
+
const result = await findAvailablePort( {
|
|
112
|
+
preferredPort: 8888,
|
|
113
|
+
exclude: [ 8888 ],
|
|
114
|
+
} );
|
|
115
|
+
expect( result ).toBe( 8889 );
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
it( 'returns a port higher than the preferred port when fallback is needed', async () => {
|
|
119
|
+
mockPortAvailability( ( port ) => port !== 8888 && port !== 8889 );
|
|
120
|
+
const result = await findAvailablePort( {
|
|
121
|
+
preferredPort: 8888,
|
|
122
|
+
} );
|
|
123
|
+
expect( result ).toBeGreaterThan( 8888 );
|
|
124
|
+
expect( result ).toBeLessThanOrEqual( DEFAULT_MAX_PORT );
|
|
125
|
+
} );
|
|
126
|
+
|
|
127
|
+
it( 'supports overriding fallback range', async () => {
|
|
128
|
+
mockPortAvailability( ( port ) => port === DEFAULT_MIN_PORT + 1 );
|
|
129
|
+
const result = await findAvailablePort( {
|
|
130
|
+
preferredPort: 8888,
|
|
131
|
+
minPort: DEFAULT_MIN_PORT,
|
|
132
|
+
} );
|
|
133
|
+
expect( result ).toBe( DEFAULT_MIN_PORT + 1 );
|
|
134
|
+
} );
|
|
135
|
+
} );
|
|
136
|
+
} );
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
const {
|
|
6
|
+
createPortResolver,
|
|
7
|
+
resolveConfigPorts,
|
|
8
|
+
} = require( '../resolve-available-ports' );
|
|
9
|
+
const { findAvailablePort, isPortAvailable } = require( '../port-utils' );
|
|
10
|
+
|
|
11
|
+
jest.mock( '../port-utils' );
|
|
12
|
+
|
|
13
|
+
describe( 'resolve-available-ports', () => {
|
|
14
|
+
afterEach( () => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
describe( 'createPortResolver', () => {
|
|
19
|
+
it( 'should resolve a port in non-strict mode', async () => {
|
|
20
|
+
findAvailablePort.mockResolvedValue( 8888 );
|
|
21
|
+
|
|
22
|
+
const resolver = createPortResolver();
|
|
23
|
+
const port = await resolver.resolve( 8888, 'env.development.port' );
|
|
24
|
+
|
|
25
|
+
expect( port ).toBe( 8888 );
|
|
26
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
27
|
+
expect.objectContaining( { preferredPort: 8888 } )
|
|
28
|
+
);
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'should fail in strict mode when port is busy', async () => {
|
|
32
|
+
isPortAvailable.mockResolvedValue( false );
|
|
33
|
+
|
|
34
|
+
const resolver = createPortResolver();
|
|
35
|
+
|
|
36
|
+
await expect(
|
|
37
|
+
resolver.resolve( 9000, 'env.development.port', true )
|
|
38
|
+
).rejects.toThrow( /Port 9000.*is busy/ );
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
it( 'should succeed in strict mode when port is available', async () => {
|
|
42
|
+
isPortAvailable.mockResolvedValue( true );
|
|
43
|
+
|
|
44
|
+
const resolver = createPortResolver();
|
|
45
|
+
const port = await resolver.resolve(
|
|
46
|
+
9000,
|
|
47
|
+
'env.development.port',
|
|
48
|
+
true
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect( port ).toBe( 9000 );
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
it( 'should fail in strict mode when port conflicts with another service', async () => {
|
|
55
|
+
isPortAvailable.mockResolvedValue( true );
|
|
56
|
+
findAvailablePort.mockResolvedValue( 8888 );
|
|
57
|
+
|
|
58
|
+
const resolver = createPortResolver();
|
|
59
|
+
// First call consumes port 8888.
|
|
60
|
+
await resolver.resolve( 8888, 'env.development.port' );
|
|
61
|
+
|
|
62
|
+
await expect(
|
|
63
|
+
resolver.resolve( 8888, 'env.tests.port', true )
|
|
64
|
+
).rejects.toThrow( /conflicts with another wp-env service/ );
|
|
65
|
+
} );
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
describe( 'resolveConfigPorts', () => {
|
|
69
|
+
it( 'should resolve null ports using preferred defaults', async () => {
|
|
70
|
+
findAvailablePort.mockResolvedValue( 8888 );
|
|
71
|
+
|
|
72
|
+
const resolver = createPortResolver();
|
|
73
|
+
const config = {
|
|
74
|
+
env: {
|
|
75
|
+
development: { port: null },
|
|
76
|
+
tests: { port: null },
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await resolveConfigPorts( config, resolver );
|
|
81
|
+
|
|
82
|
+
// Null ports should be resolved using preferred ports.
|
|
83
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
84
|
+
expect.objectContaining( { preferredPort: 8888 } )
|
|
85
|
+
);
|
|
86
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
87
|
+
expect.objectContaining( { preferredPort: 8889 } )
|
|
88
|
+
);
|
|
89
|
+
} );
|
|
90
|
+
|
|
91
|
+
it( 'should resolve explicit ports with auto-fallback', async () => {
|
|
92
|
+
findAvailablePort.mockImplementation( ( { preferredPort } ) =>
|
|
93
|
+
Promise.resolve( preferredPort )
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const resolver = createPortResolver();
|
|
97
|
+
const config = {
|
|
98
|
+
env: {
|
|
99
|
+
development: { port: 9000 },
|
|
100
|
+
tests: { port: 9001 },
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
await resolveConfigPorts( config, resolver );
|
|
105
|
+
|
|
106
|
+
expect( config.env.development.port ).toBe( 9000 );
|
|
107
|
+
expect( config.env.tests.port ).toBe( 9001 );
|
|
108
|
+
// Auto-port always uses findAvailablePort (non-strict).
|
|
109
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
110
|
+
expect.objectContaining( { preferredPort: 9000 } )
|
|
111
|
+
);
|
|
112
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
113
|
+
expect.objectContaining( { preferredPort: 9001 } )
|
|
114
|
+
);
|
|
115
|
+
} );
|
|
116
|
+
|
|
117
|
+
it( 'should resolve explicit phpmyadminPort with auto-fallback', async () => {
|
|
118
|
+
findAvailablePort.mockResolvedValue( 49152 );
|
|
119
|
+
|
|
120
|
+
const resolver = createPortResolver();
|
|
121
|
+
const config = {
|
|
122
|
+
env: {
|
|
123
|
+
development: { port: null, phpmyadminPort: 9000 },
|
|
124
|
+
tests: { port: null },
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await resolveConfigPorts( config, resolver );
|
|
129
|
+
|
|
130
|
+
// phpmyadminPort should use findAvailablePort (non-strict),
|
|
131
|
+
// not isPortAvailable (strict), even with an explicit value.
|
|
132
|
+
expect( findAvailablePort ).toHaveBeenCalledWith(
|
|
133
|
+
expect.objectContaining( { preferredPort: 9000 } )
|
|
134
|
+
);
|
|
135
|
+
expect( isPortAvailable ).not.toHaveBeenCalledWith( 9000 );
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
it( 'should skip undefined ports', async () => {
|
|
139
|
+
const resolver = createPortResolver();
|
|
140
|
+
const config = {
|
|
141
|
+
env: {
|
|
142
|
+
development: { port: null },
|
|
143
|
+
tests: { port: null },
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
findAvailablePort.mockResolvedValue( 8888 );
|
|
148
|
+
await resolveConfigPorts( config, resolver );
|
|
149
|
+
|
|
150
|
+
// phpmyadminPort is undefined so should not trigger any resolution.
|
|
151
|
+
expect( config.env.development.phpmyadminPort ).toBeUndefined();
|
|
152
|
+
expect( config.env.tests.phpmyadminPort ).toBeUndefined();
|
|
153
|
+
} );
|
|
154
|
+
} );
|
|
155
|
+
} );
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/env",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.1.0",
|
|
4
4
|
"description": "A zero-config, self contained local WordPress environment for development and testing.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"scripts": {
|
|
64
64
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "8bfc179b9aed74c0a6dd6e8edf7a49e40e4f87cc"
|
|
67
67
|
}
|