@wordpress/env 6.0.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.
Files changed (54) hide show
  1. package/README.md +109 -43
  2. package/lib/build-docker-compose-config.js +67 -96
  3. package/lib/cache.js +1 -0
  4. package/lib/cli.js +16 -3
  5. package/lib/commands/clean.js +14 -1
  6. package/lib/commands/destroy.js +12 -37
  7. package/lib/commands/index.js +1 -0
  8. package/lib/commands/install-path.js +1 -0
  9. package/lib/commands/logs.js +1 -0
  10. package/lib/commands/run.js +59 -3
  11. package/lib/commands/start.js +30 -8
  12. package/lib/commands/stop.js +1 -0
  13. package/lib/config/add-or-replace-port.js +12 -3
  14. package/lib/config/db-env.js +1 -0
  15. package/lib/config/detect-directory-type.js +1 -1
  16. package/lib/config/get-cache-directory.js +39 -0
  17. package/lib/config/get-config-from-environment-vars.js +86 -0
  18. package/lib/config/index.js +7 -5
  19. package/lib/config/load-config.js +92 -0
  20. package/lib/config/merge-configs.js +104 -0
  21. package/lib/config/parse-config.js +418 -157
  22. package/lib/config/parse-source-string.js +164 -0
  23. package/lib/config/post-process-config.js +202 -0
  24. package/lib/config/read-raw-config-file.js +7 -33
  25. package/lib/config/test/__snapshots__/config-integration.js.snap +271 -0
  26. package/lib/config/test/add-or-replace-port.js +29 -1
  27. package/lib/config/test/config-integration.js +143 -0
  28. package/lib/config/test/get-cache-directory.js +57 -0
  29. package/lib/config/test/merge-configs.js +111 -0
  30. package/lib/config/test/parse-config.js +342 -0
  31. package/lib/config/test/parse-source-string.js +154 -0
  32. package/lib/config/test/post-process-config.js +295 -0
  33. package/lib/config/test/read-raw-config-file.js +19 -63
  34. package/lib/config/test/validate-config.js +305 -0
  35. package/lib/config/validate-config.js +93 -54
  36. package/lib/env.js +2 -0
  37. package/lib/execute-after-setup.js +51 -0
  38. package/lib/get-host-user.js +31 -0
  39. package/lib/init-config.js +181 -63
  40. package/lib/md5.js +1 -0
  41. package/lib/parse-xdebug-mode.js +1 -0
  42. package/lib/retry.js +1 -0
  43. package/lib/test/__snapshots__/md5.js.snap +9 -0
  44. package/lib/test/build-docker-compose-config.js +134 -0
  45. package/lib/test/cache.js +154 -0
  46. package/lib/test/cli.js +149 -0
  47. package/lib/test/execute-after-setup.js +66 -0
  48. package/lib/test/md5.js +35 -0
  49. package/lib/test/parse-xdebug-mode.js +41 -0
  50. package/lib/wordpress.js +4 -19
  51. package/package.json +2 -2
  52. package/lib/config/config.js +0 -353
  53. package/lib/config/test/__snapshots__/config.js.snap +0 -83
  54. package/lib/config/test/config.js +0 -1241
@@ -0,0 +1,305 @@
1
+ 'use strict';
2
+ /**
3
+ * Internal dependencies
4
+ */
5
+ const {
6
+ ValidationError,
7
+ checkString,
8
+ checkPort,
9
+ checkStringArray,
10
+ checkObjectWithValues,
11
+ checkVersion,
12
+ checkValidURL,
13
+ } = require( '../validate-config' );
14
+
15
+ describe( 'validate-config', () => {
16
+ describe( 'checkString', () => {
17
+ it( 'throws when not a string', () => {
18
+ expect( () => checkString( 'test.json', 'test', 1234 ) ).toThrow(
19
+ new ValidationError(
20
+ 'Invalid test.json: "test" must be a string.'
21
+ )
22
+ );
23
+ } );
24
+
25
+ it( 'passes for string', () => {
26
+ expect( () =>
27
+ checkString( 'test.json', 'test', 'test' )
28
+ ).not.toThrow();
29
+ } );
30
+ } );
31
+
32
+ describe( 'checkPort', () => {
33
+ it( 'throws when not a number', () => {
34
+ expect( () => checkPort( 'test.json', 'test', 'test' ) ).toThrow(
35
+ new ValidationError(
36
+ 'Invalid test.json: "test" must be an integer.'
37
+ )
38
+ );
39
+
40
+ expect( () =>
41
+ checkPort( 'test.json', 'test', { test: 'test' } )
42
+ ).toThrow(
43
+ new ValidationError(
44
+ 'Invalid test.json: "test" must be an integer.'
45
+ )
46
+ );
47
+ } );
48
+
49
+ it( 'throws when port out of range', () => {
50
+ expect( () => checkPort( 'test.json', 'test', -1 ) ).toThrow(
51
+ new ValidationError(
52
+ 'Invalid test.json: "test" must be a valid port.'
53
+ )
54
+ );
55
+
56
+ expect( () => checkPort( 'test.json', 'test', 99999999 ) ).toThrow(
57
+ new ValidationError(
58
+ 'Invalid test.json: "test" must be a valid port.'
59
+ )
60
+ );
61
+ } );
62
+
63
+ it( 'passes for valid port', () => {
64
+ expect( () =>
65
+ checkPort( 'test.json', 'test', 8888 )
66
+ ).not.toThrow();
67
+ } );
68
+ } );
69
+
70
+ describe( 'checkStringArray', () => {
71
+ it( 'throws when not an array', () => {
72
+ expect( () =>
73
+ checkStringArray( 'test.json', 'test', 'test' )
74
+ ).toThrow(
75
+ new ValidationError(
76
+ 'Invalid test.json: "test" must be an array.'
77
+ )
78
+ );
79
+
80
+ expect( () =>
81
+ checkStringArray( 'test.json', 'test', { test: 'test' } )
82
+ ).toThrow(
83
+ new ValidationError(
84
+ 'Invalid test.json: "test" must be an array.'
85
+ )
86
+ );
87
+ } );
88
+
89
+ it( 'throws when array contains non-strings', () => {
90
+ expect( () =>
91
+ checkStringArray( 'test.json', 'test', [ 12 ] )
92
+ ).toThrow(
93
+ new ValidationError(
94
+ 'Invalid test.json: "test" must be an array of strings.'
95
+ )
96
+ );
97
+
98
+ expect( () =>
99
+ checkStringArray( 'test.json', 'test', [ 'test', 12 ] )
100
+ ).toThrow(
101
+ new ValidationError(
102
+ 'Invalid test.json: "test" must be an array of strings.'
103
+ )
104
+ );
105
+ } );
106
+
107
+ it( 'passes for string arrays', () => {
108
+ expect( () =>
109
+ checkStringArray( 'test.json', 'test', [] )
110
+ ).not.toThrow();
111
+ expect( () =>
112
+ checkStringArray( 'test.json', 'test', [ 'test' ] )
113
+ ).not.toThrow();
114
+ } );
115
+ } );
116
+
117
+ describe( 'checkObjectWithValues', () => {
118
+ it( 'throws when not an object', () => {
119
+ expect( () =>
120
+ checkObjectWithValues( 'test.json', 'test', 'test', [] )
121
+ ).toThrow(
122
+ new ValidationError(
123
+ 'Invalid test.json: "test" must be an object.'
124
+ )
125
+ );
126
+
127
+ expect( () =>
128
+ checkObjectWithValues( 'test.json', 'test', [ 'test' ], [] )
129
+ ).toThrow(
130
+ new ValidationError(
131
+ 'Invalid test.json: "test" must be an object.'
132
+ )
133
+ );
134
+ } );
135
+
136
+ it( 'throws when no allowed types are given', () => {
137
+ expect( () =>
138
+ checkObjectWithValues(
139
+ 'test.json',
140
+ 'test',
141
+ { test: 'test' },
142
+ []
143
+ )
144
+ ).toThrow(
145
+ new ValidationError(
146
+ 'Invalid test.json: "test.test" must be a .'
147
+ )
148
+ );
149
+ } );
150
+
151
+ it( 'throws when type is not allowed', () => {
152
+ expect( () =>
153
+ checkObjectWithValues( 'test.json', 'test', { test: 'test' }, [
154
+ 'number',
155
+ ] )
156
+ ).toThrow(
157
+ new ValidationError(
158
+ 'Invalid test.json: "test.test" must be a number.'
159
+ )
160
+ );
161
+
162
+ expect( () =>
163
+ checkObjectWithValues( 'test.json', 'test', { test: 1 }, [
164
+ 'string',
165
+ ] )
166
+ ).toThrow(
167
+ new ValidationError(
168
+ 'Invalid test.json: "test.test" must be a string.'
169
+ )
170
+ );
171
+
172
+ expect( () =>
173
+ checkObjectWithValues(
174
+ 'test.json',
175
+ 'test',
176
+ { test: [ 'test' ] },
177
+ [ 'object', 'string' ]
178
+ )
179
+ ).toThrow(
180
+ new ValidationError(
181
+ 'Invalid test.json: "test.test" must be a object or string.'
182
+ )
183
+ );
184
+ } );
185
+
186
+ it( 'passes when type is allowed', () => {
187
+ expect( () =>
188
+ checkObjectWithValues( 'test.json', 'test', { test: 'test' }, [
189
+ 'string',
190
+ ] )
191
+ ).not.toThrow();
192
+ expect( () =>
193
+ checkObjectWithValues( 'test.json', 'test', { test: 1 }, [
194
+ 'number',
195
+ ] )
196
+ ).not.toThrow();
197
+ expect( () =>
198
+ checkObjectWithValues(
199
+ 'test.json',
200
+ 'test',
201
+ { test: { nested: 'test' } },
202
+ [ 'object' ]
203
+ )
204
+ ).not.toThrow();
205
+ expect( () =>
206
+ checkObjectWithValues(
207
+ 'test.json',
208
+ 'test',
209
+ { test: [ 'test' ] },
210
+ [ 'array' ]
211
+ )
212
+ ).not.toThrow();
213
+ } );
214
+ } );
215
+
216
+ describe( 'checkVersion', () => {
217
+ it( 'throws for invalid input', () => {
218
+ expect( () => checkVersion( 'test.json', 'test', 'test' ) ).toThrow(
219
+ new ValidationError(
220
+ 'Invalid test.json: "test" must be a string of the format "X", "X.X", or "X.X.X".'
221
+ )
222
+ );
223
+
224
+ expect( () => checkVersion( 'test.json', 'test', 123 ) ).toThrow(
225
+ new ValidationError(
226
+ 'Invalid test.json: "test" must be a string.'
227
+ )
228
+ );
229
+ } );
230
+
231
+ it( 'passes for different version formats', () => {
232
+ expect( () =>
233
+ checkVersion( 'test.json', 'test', '1' )
234
+ ).not.toThrow();
235
+ expect( () =>
236
+ checkVersion( 'test.json', 'test', '1.1' )
237
+ ).not.toThrow();
238
+ expect( () =>
239
+ checkVersion( 'test.json', 'test', '1.1.1' )
240
+ ).not.toThrow();
241
+ expect( () =>
242
+ checkVersion( 'test.json', 'test', '15.7.2' )
243
+ ).not.toThrow();
244
+ expect( () =>
245
+ checkVersion( 'test.json', 'test', '26634543' )
246
+ ).not.toThrow();
247
+ } );
248
+ } );
249
+
250
+ describe( 'checkValidURL', () => {
251
+ it( 'throws for invaid URLs', () => {
252
+ expect( () =>
253
+ checkValidURL( 'test.json', 'test', 'localhost' )
254
+ ).toThrow(
255
+ new ValidationError(
256
+ 'Invalid test.json: "test" must be a valid URL.'
257
+ )
258
+ );
259
+
260
+ expect( () => checkValidURL( 'test.json', 'test', '' ) ).toThrow(
261
+ new ValidationError(
262
+ 'Invalid test.json: "test" must be a valid URL.'
263
+ )
264
+ );
265
+
266
+ expect( () => checkValidURL( 'test.json', 'test', 123 ) ).toThrow(
267
+ new ValidationError(
268
+ 'Invalid test.json: "test" must be a valid URL.'
269
+ )
270
+ );
271
+ } );
272
+
273
+ it( 'passes for valid URLs', () => {
274
+ expect( () =>
275
+ checkValidURL( 'test.json', 'test', 'http://test.com' )
276
+ ).not.toThrow();
277
+ expect( () =>
278
+ checkValidURL( 'test.json', 'test', 'https://test.com' )
279
+ ).not.toThrow();
280
+ expect( () =>
281
+ checkValidURL( 'test.json', 'test', 'http://test' )
282
+ ).not.toThrow();
283
+ expect( () =>
284
+ checkValidURL(
285
+ 'test.json',
286
+ 'test',
287
+ 'http://test/test?test=test'
288
+ )
289
+ ).not.toThrow();
290
+ expect( () =>
291
+ checkValidURL( 'test.json', 'test', 'http://test.co.uk' )
292
+ ).not.toThrow();
293
+ expect( () =>
294
+ checkValidURL( 'test.json', 'test', 'https://test.co.uk:8888' )
295
+ ).not.toThrow();
296
+ expect( () =>
297
+ checkValidURL(
298
+ 'test.json',
299
+ 'test',
300
+ 'http://test.co.uk:8888/test?test=test#test'
301
+ )
302
+ ).not.toThrow();
303
+ } );
304
+ } );
305
+ } );
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * @typedef {import('./config').WPServiceConfig} WPServiceConfig
5
- * @typedef {import('./config').WPSource} WPSource
4
+ * @typedef {import('./parse-source-string').WPSource} WPSource
6
5
  */
7
6
 
8
7
  /**
@@ -12,105 +11,145 @@
12
11
  class ValidationError extends Error {}
13
12
 
14
13
  /**
15
- * Validates a config object by throwing a ValidationError if any of its properties
16
- * do not match the required format.
14
+ * Validates that the value is a string.
17
15
  *
18
- * @param {Object} config A config object to validate.
19
- * @param {?string} envLocation Identifies if the error occurred in a specific environment property.
20
- * @return {Object} The passed config object with no modifications.
16
+ * @param {string} configFile The configuration file we're validating.
17
+ * @param {string} configKey The configuration key we're validating.
18
+ * @param {number} value The value to check.
21
19
  */
22
- function validateConfig( config, envLocation ) {
23
- const envPrefix = envLocation ? `env.${ envLocation }.` : '';
24
- if ( config.core !== null && typeof config.core !== 'string' ) {
20
+ function checkString( configFile, configKey, value ) {
21
+ if ( typeof value !== 'string' ) {
25
22
  throw new ValidationError(
26
- `Invalid .wp-env.json: "${ envPrefix }core" must be null or a string.`
23
+ `Invalid ${ configFile }: "${ configKey }" must be a string.`
27
24
  );
28
25
  }
26
+ }
29
27
 
30
- if (
31
- ! Array.isArray( config.plugins ) ||
32
- config.plugins.some( ( plugin ) => typeof plugin !== 'string' )
33
- ) {
28
+ /**
29
+ * Validates the port and throws if it isn't valid.
30
+ *
31
+ * @param {string} configFile The configuration file we're validating.
32
+ * @param {string} configKey The configuration key we're validating.
33
+ * @param {number} port The port to check.
34
+ */
35
+ function checkPort( configFile, configKey, port ) {
36
+ if ( ! Number.isInteger( port ) ) {
34
37
  throw new ValidationError(
35
- `Invalid .wp-env.json: "${ envPrefix }plugins" must be an array of strings.`
38
+ `Invalid ${ configFile }: "${ configKey }" must be an integer.`
36
39
  );
37
40
  }
38
41
 
39
- if (
40
- ! Array.isArray( config.themes ) ||
41
- config.themes.some( ( theme ) => typeof theme !== 'string' )
42
- ) {
42
+ if ( port < 0 || port > 65535 ) {
43
43
  throw new ValidationError(
44
- `Invalid .wp-env.json: "${ envPrefix }themes" must be an array of strings.`
44
+ `Invalid ${ configFile }: "${ configKey }" must be a valid port.`
45
45
  );
46
46
  }
47
+ }
47
48
 
48
- if ( ! Number.isInteger( config.port ) ) {
49
+ /**
50
+ * Validates the array and throws if it isn't valid.
51
+ *
52
+ * @param {string} configFile The config file we're validating.
53
+ * @param {string} configKey The configuration key we're validating.
54
+ * @param {string[]} array The array that we're checking.
55
+ */
56
+ function checkStringArray( configFile, configKey, array ) {
57
+ if ( ! Array.isArray( array ) ) {
49
58
  throw new ValidationError(
50
- `Invalid .wp-env.json: "${ envPrefix }port" must be an integer.`
59
+ `Invalid ${ configFile }: "${ configKey }" must be an array.`
51
60
  );
52
61
  }
53
62
 
54
- if ( typeof config.config !== 'object' ) {
63
+ if ( array.some( ( value ) => typeof value !== 'string' ) ) {
55
64
  throw new ValidationError(
56
- `Invalid .wp-env.json: "${ envPrefix }config" must be an object.`
65
+ `Invalid ${ configFile }: "${ configKey }" must be an array of strings.`
57
66
  );
58
67
  }
68
+ }
59
69
 
60
- if ( typeof config.mappings !== 'object' ) {
70
+ /**
71
+ * Validates the object and throws if it isn't valid.
72
+ *
73
+ * @param {string} configFile The config file we're validating.
74
+ * @param {string} configKey The configuration key we're validating.
75
+ * @param {string[]} obj The object that we're checking.
76
+ * @param {string[]} allowTypes The types that are allowed.
77
+ */
78
+ function checkObjectWithValues( configFile, configKey, obj, allowTypes ) {
79
+ if ( allowTypes === undefined ) {
80
+ allowTypes = [];
81
+ }
82
+
83
+ if ( typeof obj !== 'object' || Array.isArray( obj ) ) {
61
84
  throw new ValidationError(
62
- `Invalid .wp-env.json: "${ envPrefix }mappings" must be an object.`
85
+ `Invalid ${ configFile }: "${ configKey }" must be an object.`
63
86
  );
64
87
  }
65
88
 
66
- for ( const [ wpDir, localDir ] of Object.entries( config.mappings ) ) {
67
- if ( ! localDir || typeof localDir !== 'string' ) {
89
+ for ( const key in obj ) {
90
+ if ( ! obj[ key ] && ! allowTypes.includes( 'empty' ) ) {
68
91
  throw new ValidationError(
69
- `Invalid .wp-env.json: "${ envPrefix }mappings.${ wpDir }" should be a string.`
92
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must not be empty.`
93
+ );
94
+ }
95
+
96
+ // Identify arrays uniquely.
97
+ const type = Array.isArray( obj[ key ] ) ? 'array' : typeof obj[ key ];
98
+
99
+ if ( ! allowTypes.includes( type ) ) {
100
+ throw new ValidationError(
101
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must be a ${ allowTypes.join(
102
+ ' or '
103
+ ) }.`
70
104
  );
71
105
  }
72
106
  }
107
+ }
73
108
 
74
- if (
75
- config.phpVersion &&
76
- ! (
77
- typeof config.phpVersion === 'string' &&
78
- config.phpVersion.length === 3
79
- )
80
- ) {
109
+ /**
110
+ * Validates the version and throws if it isn't valid.
111
+ *
112
+ * @param {string} configFile The config file we're validating.
113
+ * @param {string} configKey The configuration key we're validating.
114
+ * @param {string} version The version that we're checking.
115
+ */
116
+ function checkVersion( configFile, configKey, version ) {
117
+ if ( typeof version !== 'string' ) {
81
118
  throw new ValidationError(
82
- `Invalid .wp-env.json: "${ envPrefix }phpVersion" must be a string of the format "0.0".`
119
+ `Invalid ${ configFile }: "${ configKey }" must be a string.`
83
120
  );
84
121
  }
85
122
 
86
- checkValidURL( envPrefix, config.config, 'WP_SITEURL' );
87
- checkValidURL( envPrefix, config.config, 'WP_HOME' );
88
-
89
- return config;
123
+ if ( ! version.match( /[0-9]+(?:\.[0-9]+)*/ ) ) {
124
+ throw new ValidationError(
125
+ `Invalid ${ configFile }: "${ configKey }" must be a string of the format "X", "X.X", or "X.X.X".`
126
+ );
127
+ }
90
128
  }
91
129
 
92
130
  /**
93
- * Validates the input and throws if it isn't a valid URL.
131
+ * Validates the url and throws if it isn't valid.
94
132
  *
95
- * @param {string} envPrefix The environment we're validating.
96
- * @param {Object} config The configuration object we're looking at.
97
- * @param {string} configKey The configuration key we're validating.
133
+ * @param {string} configFile The config file we're validating.
134
+ * @param {string} configKey The configuration key we're validating.
135
+ * @param {string} url The URL that we're checking.
98
136
  */
99
- function checkValidURL( envPrefix, config, configKey ) {
100
- if ( config[ configKey ] === undefined ) {
101
- return;
102
- }
103
-
137
+ function checkValidURL( configFile, configKey, url ) {
104
138
  try {
105
- new URL( config[ configKey ] );
139
+ new URL( url );
106
140
  } catch {
107
141
  throw new ValidationError(
108
- `Invalid .wp-env.json: "${ envPrefix }config.${ configKey }" must be a valid URL.`
142
+ `Invalid ${ configFile }: "${ configKey }" must be a valid URL.`
109
143
  );
110
144
  }
111
145
  }
112
146
 
113
147
  module.exports = {
114
- validateConfig,
115
148
  ValidationError,
149
+ checkString,
150
+ checkPort,
151
+ checkStringArray,
152
+ checkObjectWithValues,
153
+ checkVersion,
154
+ checkValidURL,
116
155
  };
package/lib/env.js CHANGED
@@ -3,9 +3,11 @@
3
3
  * Internal dependencies
4
4
  */
5
5
  const { ValidationError } = require( './config' );
6
+ const { AfterSetupError } = require( './execute-after-setup' );
6
7
  const commands = require( './commands' );
7
8
 
8
9
  module.exports = {
9
10
  ...commands,
10
11
  ValidationError,
12
+ AfterSetupError,
11
13
  };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const { execSync } = require( 'child_process' );
6
+
7
+ /**
8
+ * @typedef {import('./config').WPConfig} WPConfig
9
+ */
10
+
11
+ /**
12
+ * Error subtype which indicates that the afterSetup command failed.
13
+ */
14
+ class AfterSetupError extends Error {}
15
+
16
+ /**
17
+ * Executes any defined afterSetup command.
18
+ *
19
+ * @param {WPConfig} config The config object to use.
20
+ * @param {Object} spinner A CLI spinner which indciates progress.
21
+ */
22
+ function executeAfterSetup( config, spinner ) {
23
+ if ( ! config.afterSetup ) {
24
+ return;
25
+ }
26
+
27
+ spinner.text = 'Executing Script: afterSetup';
28
+
29
+ try {
30
+ let output = execSync( config.afterSetup, {
31
+ encoding: 'utf-8',
32
+ stdio: 'pipe',
33
+ env: process.env,
34
+ } );
35
+
36
+ // Remove any trailing whitespace for nicer output.
37
+ output = output.trimRight();
38
+
39
+ // We don't need to bother with any output if there isn't any.
40
+ if ( output ) {
41
+ spinner.info( `After Setup:\n${ output }` );
42
+ }
43
+ } catch ( error ) {
44
+ throw new AfterSetupError( `After Setup:\n${ error.stderr }` );
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ AfterSetupError,
50
+ executeAfterSetup,
51
+ };
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const os = require( 'os' );
6
+
7
+ /**
8
+ * Gets information about the host user.
9
+ *
10
+ * @return {Object} The host user's name, uid, and gid.
11
+ */
12
+ module.exports = function getHostUser() {
13
+ const hostUser = os.userInfo();
14
+
15
+ // On Windows the uid and gid will be -1. Since there isn't a great way to handle this,
16
+ // we're just going to say that the host user is root. On Windows you'll likely be
17
+ // using WSL to run commands inside the container, which has a uid and gid. If
18
+ // you aren't, you'll be mounting directories from Windows, to a Linux
19
+ // VM (Docker Desktop uses one), to the guest OS. I assume that
20
+ // when dealing with this configuration that file ownership
21
+ // has the same kind of magic handling that macOS uses.
22
+ const uid = ( hostUser.uid === -1 ? 0 : hostUser.uid ).toString();
23
+ const gid = ( hostUser.gid === -1 ? 0 : hostUser.gid ).toString();
24
+
25
+ return {
26
+ name: hostUser.username,
27
+ uid,
28
+ gid,
29
+ fullUser: uid + ':' + gid,
30
+ };
31
+ };