@wordpress/env 5.16.0 → 7.0.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 +189 -120
- package/lib/build-docker-compose-config.js +67 -96
- package/lib/cache.js +1 -0
- package/lib/cli.js +23 -3
- package/lib/commands/clean.js +14 -1
- package/lib/commands/destroy.js +12 -37
- package/lib/commands/index.js +1 -0
- package/lib/commands/install-path.js +1 -0
- package/lib/commands/logs.js +1 -0
- package/lib/commands/run.js +79 -15
- package/lib/commands/start.js +32 -10
- package/lib/commands/stop.js +1 -0
- package/lib/config/add-or-replace-port.js +41 -0
- package/lib/config/db-env.js +1 -0
- package/lib/config/detect-directory-type.js +1 -1
- package/lib/config/get-cache-directory.js +39 -0
- package/lib/config/get-config-from-environment-vars.js +86 -0
- package/lib/config/index.js +7 -5
- package/lib/config/load-config.js +92 -0
- package/lib/config/merge-configs.js +104 -0
- package/lib/config/parse-config.js +418 -157
- package/lib/config/parse-source-string.js +164 -0
- package/lib/config/post-process-config.js +202 -0
- package/lib/config/read-raw-config-file.js +7 -33
- package/lib/config/test/__snapshots__/config-integration.js.snap +271 -0
- package/lib/config/test/add-or-replace-port.js +81 -0
- package/lib/config/test/config-integration.js +143 -0
- package/lib/config/test/get-cache-directory.js +57 -0
- package/lib/config/test/merge-configs.js +111 -0
- package/lib/config/test/parse-config.js +342 -0
- package/lib/config/test/parse-source-string.js +154 -0
- package/lib/config/test/post-process-config.js +295 -0
- package/lib/config/test/read-raw-config-file.js +19 -63
- package/lib/config/test/validate-config.js +305 -0
- package/lib/config/validate-config.js +103 -40
- package/lib/env.js +2 -0
- package/lib/execute-after-setup.js +51 -0
- package/lib/get-host-user.js +31 -0
- package/lib/init-config.js +181 -63
- package/lib/md5.js +1 -0
- package/lib/parse-xdebug-mode.js +1 -0
- package/lib/retry.js +1 -0
- package/lib/test/__snapshots__/md5.js.snap +9 -0
- package/lib/test/build-docker-compose-config.js +134 -0
- package/lib/test/cache.js +154 -0
- package/lib/test/cli.js +149 -0
- package/lib/test/execute-after-setup.js +66 -0
- package/lib/test/md5.js +35 -0
- package/lib/test/parse-xdebug-mode.js +41 -0
- package/lib/wordpress.js +5 -33
- package/package.json +2 -2
- package/lib/config/config.js +0 -357
- package/lib/config/test/__snapshots__/config.js.snap +0 -83
- package/lib/config/test/config.js +0 -1203
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
const buildDockerComposeConfig = require( '../build-docker-compose-config' );
|
|
6
|
+
const getHostUser = require( '../get-host-user' );
|
|
7
|
+
|
|
8
|
+
// The basic config keys which build docker compose config requires.
|
|
9
|
+
const CONFIG = {
|
|
10
|
+
mappings: {},
|
|
11
|
+
pluginSources: [],
|
|
12
|
+
themeSources: [],
|
|
13
|
+
port: 8888,
|
|
14
|
+
configDirectoryPath: '/path/to/config',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
jest.mock( '../get-host-user', () => jest.fn() );
|
|
18
|
+
getHostUser.mockImplementation( () => {
|
|
19
|
+
return {
|
|
20
|
+
name: 'test',
|
|
21
|
+
uid: 1,
|
|
22
|
+
gid: 2,
|
|
23
|
+
fullUser: '1:2',
|
|
24
|
+
};
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
describe( 'buildDockerComposeConfig', () => {
|
|
28
|
+
it( 'should map directories before individual sources', () => {
|
|
29
|
+
const envConfig = {
|
|
30
|
+
...CONFIG,
|
|
31
|
+
mappings: {
|
|
32
|
+
'wp-content/plugins': {
|
|
33
|
+
path: '/path/to/wp-plugins',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
pluginSources: [
|
|
37
|
+
{ path: '/path/to/local/plugin', basename: 'test-name' },
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
const dockerConfig = buildDockerComposeConfig( {
|
|
41
|
+
workDirectoryPath: '/path',
|
|
42
|
+
env: { development: envConfig, tests: envConfig },
|
|
43
|
+
} );
|
|
44
|
+
const { volumes } = dockerConfig.services.wordpress;
|
|
45
|
+
expect( volumes ).toEqual( [
|
|
46
|
+
'wordpress:/var/www/html', // WordPress root.
|
|
47
|
+
'/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit', // WordPress test library,
|
|
48
|
+
'user-home:/home/test',
|
|
49
|
+
'/path/to/wp-plugins:/var/www/html/wp-content/plugins', // Mapped plugins root.
|
|
50
|
+
'/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name', // Mapped plugin.
|
|
51
|
+
] );
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
it( 'should add all specified sources to tests, dev, and cli services', () => {
|
|
55
|
+
const envConfig = {
|
|
56
|
+
...CONFIG,
|
|
57
|
+
mappings: {
|
|
58
|
+
'wp-content/plugins': {
|
|
59
|
+
path: '/path/to/wp-plugins',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
pluginSources: [
|
|
63
|
+
{ path: '/path/to/local/plugin', basename: 'test-name' },
|
|
64
|
+
],
|
|
65
|
+
themeSources: [
|
|
66
|
+
{ path: '/path/to/local/theme', basename: 'test-theme' },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
const dockerConfig = buildDockerComposeConfig( {
|
|
70
|
+
workDirectoryPath: '/path',
|
|
71
|
+
env: { development: envConfig, tests: envConfig },
|
|
72
|
+
} );
|
|
73
|
+
const devVolumes = dockerConfig.services.wordpress.volumes;
|
|
74
|
+
const cliVolumes = dockerConfig.services.cli.volumes;
|
|
75
|
+
expect( devVolumes ).toEqual( cliVolumes );
|
|
76
|
+
|
|
77
|
+
const testsVolumes = dockerConfig.services[ 'tests-wordpress' ].volumes;
|
|
78
|
+
const testsCliVolumes = dockerConfig.services[ 'tests-cli' ].volumes;
|
|
79
|
+
expect( testsVolumes ).toEqual( testsCliVolumes );
|
|
80
|
+
|
|
81
|
+
let localSources = [
|
|
82
|
+
'/path/to/wp-plugins:/var/www/html/wp-content/plugins',
|
|
83
|
+
'/path/WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
|
|
84
|
+
'user-home:/home/test',
|
|
85
|
+
'/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
|
|
86
|
+
'/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
|
|
87
|
+
];
|
|
88
|
+
expect( devVolumes ).toEqual( expect.arrayContaining( localSources ) );
|
|
89
|
+
|
|
90
|
+
localSources = [
|
|
91
|
+
'/path/to/wp-plugins:/var/www/html/wp-content/plugins',
|
|
92
|
+
'/path/tests-WordPress-PHPUnit/tests/phpunit:/wordpress-phpunit',
|
|
93
|
+
'tests-user-home:/home/test',
|
|
94
|
+
'/path/to/local/plugin:/var/www/html/wp-content/plugins/test-name',
|
|
95
|
+
'/path/to/local/theme:/var/www/html/wp-content/themes/test-theme',
|
|
96
|
+
];
|
|
97
|
+
expect( testsVolumes ).toEqual(
|
|
98
|
+
expect.arrayContaining( localSources )
|
|
99
|
+
);
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
it( 'should create "wordpress" and "tests-wordpress" volumes if they are needed by containers', () => {
|
|
103
|
+
// CONFIG has no coreSource entry, so there are no core sources on the
|
|
104
|
+
// local filesystem, so a volume should be created to contain core
|
|
105
|
+
// sources.
|
|
106
|
+
const dockerConfig = buildDockerComposeConfig( {
|
|
107
|
+
workDirectoryPath: '/path',
|
|
108
|
+
env: { development: CONFIG, tests: CONFIG },
|
|
109
|
+
} );
|
|
110
|
+
|
|
111
|
+
expect( dockerConfig.volumes.wordpress ).not.toBe( undefined );
|
|
112
|
+
expect( dockerConfig.volumes[ 'tests-wordpress' ] ).not.toBe(
|
|
113
|
+
undefined
|
|
114
|
+
);
|
|
115
|
+
} );
|
|
116
|
+
|
|
117
|
+
it( 'should NOT create "wordpress" and "tests-wordpress" volumes if they are not needed by containers', () => {
|
|
118
|
+
const envConfig = {
|
|
119
|
+
...CONFIG,
|
|
120
|
+
coreSource: {
|
|
121
|
+
path: '/some/random/path',
|
|
122
|
+
local: true,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const dockerConfig = buildDockerComposeConfig( {
|
|
127
|
+
workDirectoryPath: '/path',
|
|
128
|
+
env: { development: envConfig, tests: envConfig },
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
expect( dockerConfig.volumes.wordpress ).toBe( undefined );
|
|
132
|
+
expect( dockerConfig.volumes[ 'tests-wordpress' ] ).toBe( undefined );
|
|
133
|
+
} );
|
|
134
|
+
} );
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
const { readFile, writeFile } = require( 'fs' ).promises;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
const {
|
|
11
|
+
didCacheChange,
|
|
12
|
+
setCache,
|
|
13
|
+
getCache,
|
|
14
|
+
getCacheFile,
|
|
15
|
+
} = require( '../cache' );
|
|
16
|
+
|
|
17
|
+
jest.mock( 'fs', () => ( {
|
|
18
|
+
promises: {
|
|
19
|
+
readFile: jest.fn(),
|
|
20
|
+
writeFile: jest.fn(),
|
|
21
|
+
},
|
|
22
|
+
} ) );
|
|
23
|
+
|
|
24
|
+
const cacheOptions = {
|
|
25
|
+
workDirectoryPath: '/a/b/c',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function deleteCacheFile() {
|
|
29
|
+
readFile.mockImplementation( () => Promise.reject( 'No file!' ) );
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function setCacheFile( data ) {
|
|
33
|
+
readFile.mockImplementation( () =>
|
|
34
|
+
Promise.resolve( JSON.stringify( data ) )
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setupWriteFile() {
|
|
39
|
+
writeFile.mockImplementation( ( fileName, data ) => {
|
|
40
|
+
readFile.mockClear();
|
|
41
|
+
readFile.mockImplementation( () => Promise.resolve( data ) );
|
|
42
|
+
} );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe( 'cache file', () => {
|
|
46
|
+
beforeEach( () => {
|
|
47
|
+
jest.clearAllMocks();
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
describe( 'didCacheChange', () => {
|
|
51
|
+
it( 'returns true if the existing cache value is different', async () => {
|
|
52
|
+
setCacheFile( { test: 'test1' } );
|
|
53
|
+
const result = await didCacheChange( 'test', 'nope', cacheOptions );
|
|
54
|
+
expect( result ).toBe( true );
|
|
55
|
+
} );
|
|
56
|
+
it( 'returns false if the existing cache value is the same', async () => {
|
|
57
|
+
setCacheFile( { test: 'test1' } );
|
|
58
|
+
const result = await didCacheChange(
|
|
59
|
+
'test',
|
|
60
|
+
'test1',
|
|
61
|
+
cacheOptions
|
|
62
|
+
);
|
|
63
|
+
expect( result ).toBe( false );
|
|
64
|
+
} );
|
|
65
|
+
it( 'returns true if the existing cache value does not exist', async () => {
|
|
66
|
+
expect.assertions( 2 );
|
|
67
|
+
|
|
68
|
+
setCacheFile( { howdy: 'test1' } );
|
|
69
|
+
const result = await didCacheChange( 'test', 'nope', cacheOptions );
|
|
70
|
+
expect( result ).toBe( true );
|
|
71
|
+
|
|
72
|
+
deleteCacheFile();
|
|
73
|
+
const result2 = await didCacheChange(
|
|
74
|
+
'test',
|
|
75
|
+
'nope',
|
|
76
|
+
cacheOptions
|
|
77
|
+
);
|
|
78
|
+
expect( result2 ).toBe( true );
|
|
79
|
+
} );
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
describe( 'setCache', () => {
|
|
83
|
+
it( 'saves a new cache value to the file', async () => {
|
|
84
|
+
setupWriteFile();
|
|
85
|
+
await setCache( 'test', 'abc', cacheOptions );
|
|
86
|
+
const result = await getCacheFile( cacheOptions );
|
|
87
|
+
expect( result ).toEqual( { test: 'abc' } );
|
|
88
|
+
} );
|
|
89
|
+
it( 'overwrites an existing key', async () => {
|
|
90
|
+
expect.assertions( 2 );
|
|
91
|
+
setCacheFile( { test: 'abc' } );
|
|
92
|
+
const result = await getCacheFile( cacheOptions );
|
|
93
|
+
expect( result ).toEqual( { test: 'abc' } );
|
|
94
|
+
|
|
95
|
+
await setCache( 'test', '123', cacheOptions );
|
|
96
|
+
const result2 = await getCacheFile( cacheOptions );
|
|
97
|
+
expect( result2 ).toEqual( { test: '123' } );
|
|
98
|
+
} );
|
|
99
|
+
it( 'does not overwrite other keys', async () => {
|
|
100
|
+
setCacheFile( { test: 'abc' } );
|
|
101
|
+
|
|
102
|
+
await setCache( 'test2', 1234, cacheOptions );
|
|
103
|
+
const result = await getCacheFile( cacheOptions );
|
|
104
|
+
expect( result ).toEqual( { test: 'abc', test2: 1234 } );
|
|
105
|
+
} );
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
describe( 'getCache', () => {
|
|
109
|
+
it( 'returns the cache value associated with the key', async () => {
|
|
110
|
+
const value = 'test1';
|
|
111
|
+
setCacheFile( { test: value } );
|
|
112
|
+
const result = await getCache( 'test', cacheOptions );
|
|
113
|
+
expect( result ).toBe( value );
|
|
114
|
+
} );
|
|
115
|
+
it( 'returns undefined if there is no existing value', async () => {
|
|
116
|
+
setCacheFile( { anotherValue: 'hello' } );
|
|
117
|
+
const result = await getCache( 'test', cacheOptions );
|
|
118
|
+
expect( result ).toBe( undefined );
|
|
119
|
+
} );
|
|
120
|
+
it( 'returns undefined if the file does not exist', async () => {
|
|
121
|
+
deleteCacheFile();
|
|
122
|
+
const result = await getCache( 'test', cacheOptions );
|
|
123
|
+
expect( result ).toBe( undefined );
|
|
124
|
+
} );
|
|
125
|
+
} );
|
|
126
|
+
|
|
127
|
+
describe( 'getCacheFile', () => {
|
|
128
|
+
it( 'returns the stored JSON data as an Object', async () => {
|
|
129
|
+
const testData = {
|
|
130
|
+
a: 'test',
|
|
131
|
+
b: 1,
|
|
132
|
+
c: true,
|
|
133
|
+
d: null,
|
|
134
|
+
e: {
|
|
135
|
+
foo: 'test2',
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
setCacheFile( testData );
|
|
139
|
+
|
|
140
|
+
const result = await getCacheFile( cacheOptions );
|
|
141
|
+
expect( result ).toEqual( testData );
|
|
142
|
+
} );
|
|
143
|
+
it( 'returns an empty object if the file does not exist', async () => {
|
|
144
|
+
deleteCacheFile();
|
|
145
|
+
const result = await getCacheFile( cacheOptions );
|
|
146
|
+
expect( result ).toEqual( {} );
|
|
147
|
+
} );
|
|
148
|
+
it( 'returns an empty object if the file is invalid', async () => {
|
|
149
|
+
readFile.mockImplementation( () => Promise.resolve( '{' ) );
|
|
150
|
+
const result = await getCacheFile( cacheOptions );
|
|
151
|
+
expect( result ).toEqual( {} );
|
|
152
|
+
} );
|
|
153
|
+
} );
|
|
154
|
+
} );
|
package/lib/test/cli.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
const cli = require( '../cli' );
|
|
6
|
+
const env = require( '../env' );
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Mocked dependencies
|
|
10
|
+
*/
|
|
11
|
+
jest.spyOn( process, 'exit' ).mockImplementation( () => {} );
|
|
12
|
+
jest.mock( 'ora', () => () => ( {
|
|
13
|
+
start() {
|
|
14
|
+
return { text: '', succeed: jest.fn(), fail: jest.fn() };
|
|
15
|
+
},
|
|
16
|
+
} ) );
|
|
17
|
+
jest.mock( '../env', () => {
|
|
18
|
+
const actual = jest.requireActual( '../env' );
|
|
19
|
+
return {
|
|
20
|
+
start: jest.fn( Promise.resolve.bind( Promise ) ),
|
|
21
|
+
stop: jest.fn( Promise.resolve.bind( Promise ) ),
|
|
22
|
+
clean: jest.fn( Promise.resolve.bind( Promise ) ),
|
|
23
|
+
run: jest.fn( Promise.resolve.bind( Promise ) ),
|
|
24
|
+
ValidationError: actual.ValidationError,
|
|
25
|
+
AfterSetupError: actual.AfterSetupError,
|
|
26
|
+
};
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
describe( 'env cli', () => {
|
|
30
|
+
beforeEach( jest.clearAllMocks );
|
|
31
|
+
|
|
32
|
+
it( 'parses start commands.', () => {
|
|
33
|
+
cli().parse( [ 'start' ] );
|
|
34
|
+
const { spinner } = env.start.mock.calls[ 0 ][ 0 ];
|
|
35
|
+
expect( spinner.text ).toBe( '' );
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
it( 'parses stop commands.', () => {
|
|
39
|
+
cli().parse( [ 'stop' ] );
|
|
40
|
+
const { spinner } = env.stop.mock.calls[ 0 ][ 0 ];
|
|
41
|
+
expect( spinner.text ).toBe( '' );
|
|
42
|
+
} );
|
|
43
|
+
|
|
44
|
+
it( 'parses clean commands for the default environment.', () => {
|
|
45
|
+
cli().parse( [ 'clean' ] );
|
|
46
|
+
const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
|
|
47
|
+
expect( environment ).toBe( 'tests' );
|
|
48
|
+
expect( spinner.text ).toBe( '' );
|
|
49
|
+
} );
|
|
50
|
+
it( 'parses clean commands for all environments.', () => {
|
|
51
|
+
cli().parse( [ 'clean', 'all' ] );
|
|
52
|
+
const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
|
|
53
|
+
expect( environment ).toBe( 'all' );
|
|
54
|
+
expect( spinner.text ).toBe( '' );
|
|
55
|
+
} );
|
|
56
|
+
it( 'parses clean commands for the development environment.', () => {
|
|
57
|
+
cli().parse( [ 'clean', 'development' ] );
|
|
58
|
+
const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
|
|
59
|
+
expect( environment ).toBe( 'development' );
|
|
60
|
+
expect( spinner.text ).toBe( '' );
|
|
61
|
+
} );
|
|
62
|
+
it( 'parses clean commands for the tests environment.', () => {
|
|
63
|
+
cli().parse( [ 'clean', 'tests' ] );
|
|
64
|
+
const { environment, spinner } = env.clean.mock.calls[ 0 ][ 0 ];
|
|
65
|
+
expect( environment ).toBe( 'tests' );
|
|
66
|
+
expect( spinner.text ).toBe( '' );
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
it( 'parses run commands without arguments.', () => {
|
|
70
|
+
cli().parse( [ 'run', 'tests-wordpress', 'test' ] );
|
|
71
|
+
const { container, command, spinner } = env.run.mock.calls[ 0 ][ 0 ];
|
|
72
|
+
expect( container ).toBe( 'tests-wordpress' );
|
|
73
|
+
expect( command ).toStrictEqual( [ 'test' ] );
|
|
74
|
+
expect( spinner.text ).toBe( '' );
|
|
75
|
+
} );
|
|
76
|
+
it( 'parses run commands with variadic arguments.', () => {
|
|
77
|
+
cli().parse( [ 'run', 'tests-wordpress', 'test', 'test1', '--test2' ] );
|
|
78
|
+
const { container, command, spinner } = env.run.mock.calls[ 0 ][ 0 ];
|
|
79
|
+
expect( container ).toBe( 'tests-wordpress' );
|
|
80
|
+
expect( command ).toStrictEqual( [ 'test', 'test1', '--test2' ] );
|
|
81
|
+
expect( spinner.text ).toBe( '' );
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
it( 'handles successful commands with messages.', async () => {
|
|
85
|
+
env.start.mockResolvedValueOnce( 'success message' );
|
|
86
|
+
cli().parse( [ 'start' ] );
|
|
87
|
+
const { spinner } = env.start.mock.calls[ 0 ][ 0 ];
|
|
88
|
+
await env.start.mock.results[ 0 ].value;
|
|
89
|
+
expect( spinner.succeed ).toHaveBeenCalledWith(
|
|
90
|
+
expect.stringMatching( /^success message \(in \d+s \d+ms\)$/ )
|
|
91
|
+
);
|
|
92
|
+
} );
|
|
93
|
+
it( 'handles successful commands with spinner text.', async () => {
|
|
94
|
+
env.start.mockResolvedValueOnce();
|
|
95
|
+
cli().parse( [ 'start' ] );
|
|
96
|
+
const { spinner } = env.start.mock.calls[ 0 ][ 0 ];
|
|
97
|
+
spinner.text = 'success spinner text';
|
|
98
|
+
await env.start.mock.results[ 0 ].value;
|
|
99
|
+
expect( spinner.succeed ).toHaveBeenCalledWith(
|
|
100
|
+
expect.stringMatching( /^success spinner text \(in \d+s \d+ms\)$/ )
|
|
101
|
+
);
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
it( 'handles failed commands with messages.', async () => {
|
|
105
|
+
env.start.mockRejectedValueOnce( {
|
|
106
|
+
message: 'failure message',
|
|
107
|
+
} );
|
|
108
|
+
const consoleError = console.error;
|
|
109
|
+
console.error = jest.fn();
|
|
110
|
+
const processExit = process.exit;
|
|
111
|
+
process.exit = jest.fn();
|
|
112
|
+
|
|
113
|
+
cli().parse( [ 'start' ] );
|
|
114
|
+
const { spinner } = env.start.mock.calls[ 0 ][ 0 ];
|
|
115
|
+
await env.start.mock.results[ 0 ].value.catch( () => {} );
|
|
116
|
+
|
|
117
|
+
expect( spinner.fail ).toHaveBeenCalledWith( 'failure message' );
|
|
118
|
+
expect( console.error ).toHaveBeenCalled();
|
|
119
|
+
expect( process.exit ).toHaveBeenCalledWith( 1 );
|
|
120
|
+
console.error = consoleError;
|
|
121
|
+
process.exit = processExit;
|
|
122
|
+
} );
|
|
123
|
+
it( 'handles failed docker commands with errors.', async () => {
|
|
124
|
+
env.start.mockRejectedValueOnce( {
|
|
125
|
+
err: 'failure error',
|
|
126
|
+
out: 'message',
|
|
127
|
+
exitCode: 1,
|
|
128
|
+
} );
|
|
129
|
+
const consoleError = console.error;
|
|
130
|
+
console.error = jest.fn();
|
|
131
|
+
const processExit = process.exit;
|
|
132
|
+
process.exit = jest.fn();
|
|
133
|
+
const stderr = process.stderr.write;
|
|
134
|
+
process.stderr.write = jest.fn();
|
|
135
|
+
|
|
136
|
+
cli().parse( [ 'start' ] );
|
|
137
|
+
const { spinner } = env.start.mock.calls[ 0 ][ 0 ];
|
|
138
|
+
await env.start.mock.results[ 0 ].value.catch( () => {} );
|
|
139
|
+
|
|
140
|
+
expect( spinner.fail ).toHaveBeenCalledWith(
|
|
141
|
+
'Error while running docker-compose command.'
|
|
142
|
+
);
|
|
143
|
+
expect( process.stderr.write ).toHaveBeenCalledWith( 'failure error' );
|
|
144
|
+
expect( process.exit ).toHaveBeenCalledWith( 1 );
|
|
145
|
+
console.error = consoleError;
|
|
146
|
+
process.exit = processExit;
|
|
147
|
+
process.stderr.write = stderr;
|
|
148
|
+
} );
|
|
149
|
+
} );
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
const { execSync } = require( 'child_process' );
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
const {
|
|
11
|
+
AfterSetupError,
|
|
12
|
+
executeAfterSetup,
|
|
13
|
+
} = require( '../execute-after-setup' );
|
|
14
|
+
|
|
15
|
+
jest.mock( 'child_process', () => ( {
|
|
16
|
+
execSync: jest.fn(),
|
|
17
|
+
} ) );
|
|
18
|
+
|
|
19
|
+
describe( 'executeAfterSetup', () => {
|
|
20
|
+
const spinner = {
|
|
21
|
+
info: jest.fn(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
afterEach( () => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
it( 'should do nothing without afterSetup option', () => {
|
|
29
|
+
executeAfterSetup( { afterSetup: null }, spinner );
|
|
30
|
+
|
|
31
|
+
expect( spinner.info ).not.toHaveBeenCalled();
|
|
32
|
+
} );
|
|
33
|
+
|
|
34
|
+
it( 'should run afterSetup option and print output without extra whitespace', () => {
|
|
35
|
+
execSync.mockReturnValue( 'Test \n' );
|
|
36
|
+
|
|
37
|
+
executeAfterSetup( { afterSetup: 'Test Setup' }, spinner );
|
|
38
|
+
|
|
39
|
+
expect( execSync ).toHaveBeenCalled();
|
|
40
|
+
expect( execSync.mock.calls[ 0 ][ 0 ] ).toEqual( 'Test Setup' );
|
|
41
|
+
expect( spinner.info ).toHaveBeenCalledWith( 'After Setup:\nTest' );
|
|
42
|
+
} );
|
|
43
|
+
|
|
44
|
+
it( 'should print nothing if afterSetup returns no output', () => {
|
|
45
|
+
execSync.mockReturnValue( '' );
|
|
46
|
+
|
|
47
|
+
executeAfterSetup( { afterSetup: 'Test Setup' }, spinner );
|
|
48
|
+
|
|
49
|
+
expect( execSync ).toHaveBeenCalled();
|
|
50
|
+
expect( execSync.mock.calls[ 0 ][ 0 ] ).toEqual( 'Test Setup' );
|
|
51
|
+
expect( spinner.info ).not.toHaveBeenCalled();
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
it( 'should throw AfterSetupError when process errors', () => {
|
|
55
|
+
execSync.mockImplementation( ( command ) => {
|
|
56
|
+
expect( command ).toEqual( 'Test Setup' );
|
|
57
|
+
throw { stderr: 'Something bad happened.' };
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
expect( () =>
|
|
61
|
+
executeAfterSetup( { afterSetup: 'Test Setup' }, spinner )
|
|
62
|
+
).toThrow(
|
|
63
|
+
new AfterSetupError( 'After Setup:\nSomething bad happened.' )
|
|
64
|
+
);
|
|
65
|
+
} );
|
|
66
|
+
} );
|
package/lib/test/md5.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
const md5 = require( '../md5' );
|
|
6
|
+
|
|
7
|
+
describe( 'md5', () => {
|
|
8
|
+
it( 'creates a hash of a string', () => {
|
|
9
|
+
const result = md5( 'hello' );
|
|
10
|
+
expect( result ).toMatchSnapshot();
|
|
11
|
+
} );
|
|
12
|
+
it( 'creates a hash of a object', () => {
|
|
13
|
+
const result = md5( { foo: 'xyz', test: { anotherFoo: 123 } } );
|
|
14
|
+
expect( result ).toMatchSnapshot();
|
|
15
|
+
} );
|
|
16
|
+
it( 'creates a hash of a number', () => {
|
|
17
|
+
const result = md5( 123789 );
|
|
18
|
+
expect( result ).toMatchSnapshot();
|
|
19
|
+
} );
|
|
20
|
+
it( 'creates a hash of null', () => {
|
|
21
|
+
const result = md5( null );
|
|
22
|
+
expect( result ).toMatchSnapshot();
|
|
23
|
+
} );
|
|
24
|
+
it( 'uses the same hash for the same values', () => {
|
|
25
|
+
const testObj = {
|
|
26
|
+
test: 1,
|
|
27
|
+
otherTest: {
|
|
28
|
+
foo: 'hello',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const result1 = md5( testObj );
|
|
32
|
+
const result2 = md5( { ...testObj } );
|
|
33
|
+
expect( result1 ).toBe( result2 );
|
|
34
|
+
} );
|
|
35
|
+
} );
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Internal dependencies
|
|
4
|
+
*/
|
|
5
|
+
const parseXdebugMode = require( '../parse-xdebug-mode' );
|
|
6
|
+
|
|
7
|
+
describe( 'parseXdebugMode', () => {
|
|
8
|
+
it( 'throws an error if the passed value is neither a string nor undefined', () => {
|
|
9
|
+
const errorMessage = 'is not a mode recognized by Xdebug';
|
|
10
|
+
expect( () => parseXdebugMode( true ) ).toThrow( errorMessage );
|
|
11
|
+
expect( () => parseXdebugMode( false ) ).toThrow( errorMessage );
|
|
12
|
+
expect( () => parseXdebugMode( 1 ) ).toThrow( errorMessage );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
it( 'sets the Xdebug mode to "off" if no --xdebug flag is passed', () => {
|
|
16
|
+
const result = parseXdebugMode( undefined );
|
|
17
|
+
expect( result ).toEqual( 'off' );
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
it( 'sets the Xdebug mode to "debug" if no mode is specified', () => {
|
|
21
|
+
const result = parseXdebugMode( '' );
|
|
22
|
+
expect( result ).toEqual( 'debug' );
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
it( 'throws an error if a given mode is not recognized, including the invalid mode in the output', () => {
|
|
26
|
+
const fakeMode = 'fake-mode-123';
|
|
27
|
+
expect.assertions( 2 );
|
|
28
|
+
// Single mode:
|
|
29
|
+
expect( () => parseXdebugMode( fakeMode ) ).toThrow( fakeMode );
|
|
30
|
+
|
|
31
|
+
// Many modes:
|
|
32
|
+
expect( () =>
|
|
33
|
+
parseXdebugMode( `debug,profile,${ fakeMode }` )
|
|
34
|
+
).toThrow( fakeMode );
|
|
35
|
+
} );
|
|
36
|
+
|
|
37
|
+
it( 'returns all modes passed', () => {
|
|
38
|
+
const result = parseXdebugMode( 'debug,profile,trace' );
|
|
39
|
+
expect( result ).toEqual( 'debug,profile,trace' );
|
|
40
|
+
} );
|
|
41
|
+
} );
|
package/lib/wordpress.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use strict';
|
|
1
2
|
/**
|
|
2
3
|
* External dependencies
|
|
3
4
|
*/
|
|
@@ -14,7 +15,7 @@ const copyDir = util.promisify( require( 'copy-dir' ) );
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @typedef {import('./config').WPConfig} WPConfig
|
|
17
|
-
* @typedef {import('./config').
|
|
18
|
+
* @typedef {import('./config').WPEnvironmentConfig} WPEnvironmentConfig
|
|
18
19
|
* @typedef {import('./config').WPSource} WPSource
|
|
19
20
|
* @typedef {'development'|'tests'} WPEnvironment
|
|
20
21
|
* @typedef {'development'|'tests'|'all'} WPEnvironmentSelection
|
|
@@ -44,20 +45,7 @@ async function checkDatabaseConnection( { dockerComposeConfigPath, debug } ) {
|
|
|
44
45
|
* @param {Object} spinner A CLI spinner which indicates progress.
|
|
45
46
|
*/
|
|
46
47
|
async function configureWordPress( environment, config, spinner ) {
|
|
47
|
-
const url =
|
|
48
|
-
const port = config.env[ environment ].port;
|
|
49
|
-
const domain =
|
|
50
|
-
environment === 'tests'
|
|
51
|
-
? config.env.tests.config.WP_TESTS_DOMAIN
|
|
52
|
-
: config.env.development.config.WP_SITEURL;
|
|
53
|
-
if ( port === 80 ) {
|
|
54
|
-
return domain;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return `${ domain }:${ port }`;
|
|
58
|
-
} )();
|
|
59
|
-
|
|
60
|
-
const installCommand = `wp core install --url="${ url }" --title="${ config.name }" --admin_user=admin --admin_password=password --admin_email=wordpress@example.com --skip-email`;
|
|
48
|
+
const installCommand = `wp core install --url="${ config.env[ environment ].config.WP_SITEURL }" --title="${ config.name }" --admin_user=admin --admin_password=password --admin_email=wordpress@example.com --skip-email`;
|
|
61
49
|
|
|
62
50
|
// -eo pipefail exits the command as soon as anything fails in bash.
|
|
63
51
|
const setupCommands = [ 'set -eo pipefail', installCommand ];
|
|
@@ -99,6 +87,7 @@ async function configureWordPress( environment, config, spinner ) {
|
|
|
99
87
|
[ 'bash', '-c', setupCommands.join( ' && ' ) ],
|
|
100
88
|
{
|
|
101
89
|
config: config.dockerComposeConfigPath,
|
|
90
|
+
commandOptions: [ '--rm' ],
|
|
102
91
|
log: config.debug,
|
|
103
92
|
}
|
|
104
93
|
);
|
|
@@ -160,30 +149,13 @@ async function setupWordPressDirectories( config ) {
|
|
|
160
149
|
config.env.development.coreSource.path,
|
|
161
150
|
config.env.development.coreSource.testsPath
|
|
162
151
|
);
|
|
163
|
-
await createUploadsDir( config.env.development.coreSource.testsPath );
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const checkedPaths = {};
|
|
167
|
-
for ( const { coreSource } of Object.values( config.env ) ) {
|
|
168
|
-
if ( coreSource && ! checkedPaths[ coreSource.path ] ) {
|
|
169
|
-
await createUploadsDir( coreSource.path );
|
|
170
|
-
checkedPaths[ coreSource.path ] = true;
|
|
171
|
-
}
|
|
172
152
|
}
|
|
173
153
|
}
|
|
174
154
|
|
|
175
|
-
async function createUploadsDir( corePath ) {
|
|
176
|
-
// Ensure the tests uploads folder is writeable for travis,
|
|
177
|
-
// creating the folder if necessary.
|
|
178
|
-
const uploadPath = path.join( corePath, 'wp-content/uploads' );
|
|
179
|
-
await fs.mkdir( uploadPath, { recursive: true } );
|
|
180
|
-
await fs.chmod( uploadPath, 0o0767 );
|
|
181
|
-
}
|
|
182
|
-
|
|
183
155
|
/**
|
|
184
156
|
* Returns true if all given environment configs have the same core source.
|
|
185
157
|
*
|
|
186
|
-
* @param {
|
|
158
|
+
* @param {WPEnvironmentConfig[]} envs An array of environments to check.
|
|
187
159
|
*
|
|
188
160
|
* @return {boolean} True if all the environments have the same core source.
|
|
189
161
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/env",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.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",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"test": "echo \"Error: run tests from root\" && exit 1"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "e936127e1e13881f1a940b7bd1593a9e500147f3"
|
|
55
55
|
}
|