@wordpress/env 7.0.0 → 8.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.
@@ -1,10 +1,5 @@
1
1
  'use strict';
2
2
  /* eslint-disable jest/no-conditional-expect */
3
- /**
4
- * External dependencies
5
- */
6
- const path = require( 'path' );
7
-
8
3
  /**
9
4
  * Internal dependencies
10
5
  */
@@ -40,6 +35,7 @@ const DEFAULT_CONFIG = {
40
35
  pluginSources: [],
41
36
  themeSources: [],
42
37
  config: {
38
+ FS_METHOD: 'direct',
43
39
  WP_DEBUG: true,
44
40
  SCRIPT_DEBUG: true,
45
41
  WP_ENVIRONMENT_TYPE: 'local',
@@ -51,7 +47,11 @@ const DEFAULT_CONFIG = {
51
47
  WP_HOME: 'http://localhost',
52
48
  },
53
49
  mappings: {},
54
- afterSetup: null,
50
+ lifecycleScripts: {
51
+ afterStart: null,
52
+ afterClean: null,
53
+ afterDestroy: null,
54
+ },
55
55
  env: {
56
56
  development: {},
57
57
  tests: {
@@ -76,11 +76,11 @@ describe( 'parseConfig', () => {
76
76
  delete process.env.WP_ENV_TESTS_PORT;
77
77
  delete process.env.WP_ENV_CORE;
78
78
  delete process.env.WP_ENV_PHP_VERSION;
79
- delete process.env.WP_ENV_AFTER_SETUP;
79
+ delete process.env.WP_ENV_LIFECYCLE_SCRIPT_AFTER_START;
80
80
  } );
81
81
 
82
82
  it( 'should return default config', async () => {
83
- const parsed = await parseConfig( './', '/cache' );
83
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
84
84
 
85
85
  expect( parsed ).toEqual( DEFAULT_CONFIG );
86
86
  } );
@@ -88,60 +88,44 @@ describe( 'parseConfig', () => {
88
88
  it( 'should infer a core mounting default when ran from a WordPress directory', async () => {
89
89
  detectDirectoryType.mockResolvedValue( 'core' );
90
90
 
91
- const parsed = await parseConfig( './', '/cache' );
91
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
92
92
 
93
- expect( parsed ).toEqual( {
94
- ...DEFAULT_CONFIG,
95
- coreSource: {
96
- basename: 'gutenberg',
97
- path: path.resolve( '.' ),
98
- testsPath: '/cache/tests-gutenberg',
99
- type: 'local',
100
- },
101
- } );
93
+ expect( parsed.pluginSources ).toHaveLength( 0 );
94
+ expect( parsed.themeSources ).toHaveLength( 0 );
95
+ expect( parsed.coreSource ).toMatchObject( { type: 'local' } );
102
96
  } );
103
97
 
104
98
  it( 'should infer a plugin mounting default when ran from a plugin directory', async () => {
105
99
  detectDirectoryType.mockResolvedValue( 'plugin' );
106
100
 
107
- const parsed = await parseConfig( './', '/cache' );
101
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
108
102
 
109
- expect( parsed ).toEqual( {
110
- ...DEFAULT_CONFIG,
111
- pluginSources: [
112
- {
113
- basename: 'gutenberg',
114
- path: path.resolve( '.' ),
115
- type: 'local',
116
- },
117
- ],
118
- } );
103
+ expect( parsed.coreSource ).toMatchObject( { type: 'git' } );
104
+ expect( parsed.themeSources ).toHaveLength( 0 );
105
+ expect( parsed.pluginSources ).toHaveLength( 1 );
106
+ expect( parsed.pluginSources[ 0 ] ).toMatchObject( { type: 'local' } );
119
107
  } );
120
108
 
121
109
  it( 'should infer a theme mounting default when ran from a theme directory', async () => {
122
110
  detectDirectoryType.mockResolvedValue( 'theme' );
123
111
 
124
- const parsed = await parseConfig( './', '/cache' );
112
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
125
113
 
126
- expect( parsed ).toEqual( {
127
- ...DEFAULT_CONFIG,
128
- themeSources: [
129
- {
130
- basename: 'gutenberg',
131
- path: path.resolve( '.' ),
132
- type: 'local',
133
- },
134
- ],
135
- } );
114
+ expect( parsed.coreSource ).toMatchObject( { type: 'git' } );
115
+ expect( parsed.pluginSources ).toHaveLength( 0 );
116
+ expect( parsed.themeSources ).toHaveLength( 1 );
117
+ expect( parsed.themeSources[ 0 ] ).toMatchObject( { type: 'local' } );
136
118
  } );
137
119
 
138
120
  it( 'should merge configs with precedence', async () => {
139
121
  readRawConfigFile.mockImplementation( async ( configFile ) => {
140
- if ( configFile === path.resolve( './.wp-env.json' ) ) {
122
+ if ( configFile === '/test/gutenberg/.wp-env.json' ) {
141
123
  return {
142
124
  core: 'WordPress/WordPress#Test',
143
125
  phpVersion: '1.0',
144
- afterSetup: 'test',
126
+ lifecycleScripts: {
127
+ afterStart: 'test',
128
+ },
145
129
  env: {
146
130
  development: {
147
131
  port: 1234,
@@ -153,9 +137,12 @@ describe( 'parseConfig', () => {
153
137
  };
154
138
  }
155
139
 
156
- if ( configFile === path.resolve( './.wp-env.override.json' ) ) {
140
+ if ( configFile === '/test/gutenberg/.wp-env.override.json' ) {
157
141
  return {
158
142
  phpVersion: '2.0',
143
+ lifecycleScripts: {
144
+ afterDestroy: 'test',
145
+ },
159
146
  env: {
160
147
  tests: {
161
148
  port: 1011,
@@ -167,7 +154,7 @@ describe( 'parseConfig', () => {
167
154
  throw new Error( 'Invalid File: ' + configFile );
168
155
  } );
169
156
 
170
- const parsed = await parseConfig( './', '/cache' );
157
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
171
158
 
172
159
  const expected = {
173
160
  ...DEFAULT_CONFIG,
@@ -181,7 +168,11 @@ describe( 'parseConfig', () => {
181
168
  type: 'git',
182
169
  },
183
170
  phpVersion: '2.0',
184
- afterSetup: 'test',
171
+ lifecycleScripts: {
172
+ ...DEFAULT_CONFIG.lifecycleScripts,
173
+ afterStart: 'test',
174
+ afterDestroy: 'test',
175
+ },
185
176
  env: {
186
177
  development: {
187
178
  ...DEFAULT_CONFIG.env.development,
@@ -198,7 +189,7 @@ describe( 'parseConfig', () => {
198
189
 
199
190
  it( 'should parse core, plugin, theme, and mapping sources', async () => {
200
191
  readRawConfigFile.mockImplementation( async ( configFile ) => {
201
- if ( configFile === path.resolve( '.', './.wp-env.json' ) ) {
192
+ if ( configFile === '/test/gutenberg/.wp-env.json' ) {
202
193
  return {
203
194
  core: 'WordPress/WordPress#Test',
204
195
  plugins: [ 'WordPress/TestPlugin#Test' ],
@@ -210,16 +201,14 @@ describe( 'parseConfig', () => {
210
201
  };
211
202
  }
212
203
 
213
- if (
214
- configFile === path.resolve( '.', './.wp-env.override.json' )
215
- ) {
204
+ if ( configFile === '/test/gutenberg/.wp-env.override.json' ) {
216
205
  return {};
217
206
  }
218
207
 
219
208
  throw new Error( 'Invalid File: ' + configFile );
220
209
  } );
221
210
 
222
- const parsed = await parseConfig( './', '/cache' );
211
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
223
212
 
224
213
  expect( parsed ).toEqual( {
225
214
  ...DEFAULT_CONFIG,
@@ -270,9 +259,9 @@ describe( 'parseConfig', () => {
270
259
  process.env.WP_ENV_TESTS_PORT = 456;
271
260
  process.env.WP_ENV_CORE = 'WordPress/WordPress#test';
272
261
  process.env.WP_ENV_PHP_VERSION = '3.0';
273
- process.env.WP_ENV_AFTER_SETUP = 'test after';
262
+ process.env.WP_ENV_LIFECYCLE_SCRIPT_AFTER_START = 'test after';
274
263
 
275
- const parsed = await parseConfig( './', '/cache' );
264
+ const parsed = await parseConfig( '/test/gutenberg', '/cache' );
276
265
 
277
266
  expect( parsed ).toEqual( {
278
267
  ...DEFAULT_CONFIG,
@@ -288,7 +277,10 @@ describe( 'parseConfig', () => {
288
277
  type: 'git',
289
278
  },
290
279
  phpVersion: '3.0',
291
- afterSetup: 'test after',
280
+ lifecycleScripts: {
281
+ ...DEFAULT_CONFIG.lifecycleScripts,
282
+ afterStart: 'test after',
283
+ },
292
284
  env: {
293
285
  development: {
294
286
  port: 123,
@@ -329,7 +321,7 @@ describe( 'parseConfig', () => {
329
321
 
330
322
  expect.assertions( 1 );
331
323
  try {
332
- await parseConfig( './', '/cache' );
324
+ await parseConfig( '/test/gutenberg', '/cache' );
333
325
  } catch ( error ) {
334
326
  expect( error ).toEqual(
335
327
  new ValidationError(
@@ -338,5 +330,64 @@ describe( 'parseConfig', () => {
338
330
  );
339
331
  }
340
332
  } );
333
+
334
+ it( 'throws for unknown config options', async () => {
335
+ readRawConfigFile.mockImplementation( async ( configFile ) => {
336
+ if ( configFile === '/test/gutenberg/.wp-env.json' ) {
337
+ return {
338
+ test: 'test',
339
+ };
340
+ }
341
+
342
+ if ( configFile === '/test/gutenberg/.wp-env.override.json' ) {
343
+ return {};
344
+ }
345
+
346
+ throw new Error( 'Invalid File: ' + configFile );
347
+ } );
348
+
349
+ expect.assertions( 1 );
350
+ try {
351
+ await parseConfig( '/test/gutenberg', '/cache' );
352
+ } catch ( error ) {
353
+ expect( error ).toEqual(
354
+ new ValidationError(
355
+ `Invalid /test/gutenberg/.wp-env.json: "test" is not a configuration option.`
356
+ )
357
+ );
358
+ }
359
+ } );
360
+
361
+ it( 'throws for root-only config options', async () => {
362
+ readRawConfigFile.mockImplementation( async ( configFile ) => {
363
+ if ( configFile === '/test/gutenberg/.wp-env.json' ) {
364
+ return {
365
+ env: {
366
+ development: {
367
+ // Only the root can have environment-specific configurations.
368
+ env: {},
369
+ },
370
+ },
371
+ };
372
+ }
373
+
374
+ if ( configFile === '/test/gutenberg/.wp-env.override.json' ) {
375
+ return {};
376
+ }
377
+
378
+ throw new Error( 'Invalid File: ' + configFile );
379
+ } );
380
+
381
+ expect.assertions( 1 );
382
+ try {
383
+ await parseConfig( '/test/gutenberg', '/cache' );
384
+ } catch ( error ) {
385
+ expect( error ).toEqual(
386
+ new ValidationError(
387
+ `Invalid /test/gutenberg/.wp-env.json: "development.env" is not a configuration option.`
388
+ )
389
+ );
390
+ }
391
+ } );
341
392
  } );
342
393
  /* eslint-enable jest/no-conditional-expect */
@@ -45,7 +45,7 @@ describe( 'parseSourceString', () => {
45
45
  describe( 'local sources', () => {
46
46
  it( 'should parse relative directories', () => {
47
47
  expect( parseSourceString( '.', options ) ).toEqual( {
48
- basename: 'gutenberg',
48
+ basename: path.basename( path.resolve( '.' ) ),
49
49
  path: path.resolve( '.' ),
50
50
  type: 'local',
51
51
  } );
@@ -156,7 +156,9 @@ describe( 'postProcessConfig', () => {
156
156
  const processed = postProcessConfig( {
157
157
  port: 8888,
158
158
  testsPort: 8889,
159
- afterSetup: 'test',
159
+ lifecycleScripts: {
160
+ afterStart: 'test',
161
+ },
160
162
  env: {
161
163
  development: {},
162
164
  tests: {},
@@ -166,7 +168,9 @@ describe( 'postProcessConfig', () => {
166
168
  expect( processed ).toEqual( {
167
169
  port: 8888,
168
170
  testsPort: 8889,
169
- afterSetup: 'test',
171
+ lifecycleScripts: {
172
+ afterStart: 'test',
173
+ },
170
174
  env: {
171
175
  development: {
172
176
  port: 8888,
@@ -117,7 +117,7 @@ describe( 'validate-config', () => {
117
117
  describe( 'checkObjectWithValues', () => {
118
118
  it( 'throws when not an object', () => {
119
119
  expect( () =>
120
- checkObjectWithValues( 'test.json', 'test', 'test', [] )
120
+ checkObjectWithValues( 'test.json', 'test', 'test', [], false )
121
121
  ).toThrow(
122
122
  new ValidationError(
123
123
  'Invalid test.json: "test" must be an object.'
@@ -125,7 +125,13 @@ describe( 'validate-config', () => {
125
125
  );
126
126
 
127
127
  expect( () =>
128
- checkObjectWithValues( 'test.json', 'test', [ 'test' ], [] )
128
+ checkObjectWithValues(
129
+ 'test.json',
130
+ 'test',
131
+ [ 'test' ],
132
+ [],
133
+ false
134
+ )
129
135
  ).toThrow(
130
136
  new ValidationError(
131
137
  'Invalid test.json: "test" must be an object.'
@@ -139,33 +145,42 @@ describe( 'validate-config', () => {
139
145
  'test.json',
140
146
  'test',
141
147
  { test: 'test' },
142
- []
148
+ [],
149
+ false
143
150
  )
144
151
  ).toThrow(
145
152
  new ValidationError(
146
- 'Invalid test.json: "test.test" must be a .'
153
+ 'Invalid test.json: "test.test" must be of type: .'
147
154
  )
148
155
  );
149
156
  } );
150
157
 
151
158
  it( 'throws when type is not allowed', () => {
152
159
  expect( () =>
153
- checkObjectWithValues( 'test.json', 'test', { test: 'test' }, [
154
- 'number',
155
- ] )
160
+ checkObjectWithValues(
161
+ 'test.json',
162
+ 'test',
163
+ { test: 'test' },
164
+ [ 'number' ],
165
+ false
166
+ )
156
167
  ).toThrow(
157
168
  new ValidationError(
158
- 'Invalid test.json: "test.test" must be a number.'
169
+ 'Invalid test.json: "test.test" must be of type: number.'
159
170
  )
160
171
  );
161
172
 
162
173
  expect( () =>
163
- checkObjectWithValues( 'test.json', 'test', { test: 1 }, [
164
- 'string',
165
- ] )
174
+ checkObjectWithValues(
175
+ 'test.json',
176
+ 'test',
177
+ { test: 1 },
178
+ [ 'string' ],
179
+ false
180
+ )
166
181
  ).toThrow(
167
182
  new ValidationError(
168
- 'Invalid test.json: "test.test" must be a string.'
183
+ 'Invalid test.json: "test.test" must be of type: string.'
169
184
  )
170
185
  );
171
186
 
@@ -174,32 +189,65 @@ describe( 'validate-config', () => {
174
189
  'test.json',
175
190
  'test',
176
191
  { test: [ 'test' ] },
177
- [ 'object', 'string' ]
192
+ [ 'object', 'string' ],
193
+ false
194
+ )
195
+ ).toThrow(
196
+ new ValidationError(
197
+ 'Invalid test.json: "test.test" must be of type: object or string.'
198
+ )
199
+ );
200
+
201
+ expect( () =>
202
+ checkObjectWithValues(
203
+ 'test.json',
204
+ 'test',
205
+ { test: null },
206
+ [ 'object' ],
207
+ true
178
208
  )
179
209
  ).toThrow(
180
210
  new ValidationError(
181
- 'Invalid test.json: "test.test" must be a object or string.'
211
+ 'Invalid test.json: "test.test" must be of type: object.'
182
212
  )
183
213
  );
184
214
  } );
185
215
 
186
216
  it( 'passes when type is allowed', () => {
187
217
  expect( () =>
188
- checkObjectWithValues( 'test.json', 'test', { test: 'test' }, [
189
- 'string',
190
- ] )
218
+ checkObjectWithValues(
219
+ 'test.json',
220
+ 'test',
221
+ { test: 'test' },
222
+ [ 'string' ],
223
+ false
224
+ )
225
+ ).not.toThrow();
226
+ expect( () =>
227
+ checkObjectWithValues(
228
+ 'test.json',
229
+ 'test',
230
+ { test: '' },
231
+ [ 'string' ],
232
+ true
233
+ )
191
234
  ).not.toThrow();
192
235
  expect( () =>
193
- checkObjectWithValues( 'test.json', 'test', { test: 1 }, [
194
- 'number',
195
- ] )
236
+ checkObjectWithValues(
237
+ 'test.json',
238
+ 'test',
239
+ { test: 1 },
240
+ [ 'number' ],
241
+ false
242
+ )
196
243
  ).not.toThrow();
197
244
  expect( () =>
198
245
  checkObjectWithValues(
199
246
  'test.json',
200
247
  'test',
201
248
  { test: { nested: 'test' } },
202
- [ 'object' ]
249
+ [ 'object' ],
250
+ false
203
251
  )
204
252
  ).not.toThrow();
205
253
  expect( () =>
@@ -207,7 +255,17 @@ describe( 'validate-config', () => {
207
255
  'test.json',
208
256
  'test',
209
257
  { test: [ 'test' ] },
210
- [ 'array' ]
258
+ [ 'array' ],
259
+ false
260
+ )
261
+ ).not.toThrow();
262
+ expect( () =>
263
+ checkObjectWithValues(
264
+ 'test.json',
265
+ 'test',
266
+ { test: null },
267
+ [ 'null' ],
268
+ false
211
269
  )
212
270
  ).not.toThrow();
213
271
  } );
@@ -74,8 +74,15 @@ function checkStringArray( configFile, configKey, array ) {
74
74
  * @param {string} configKey The configuration key we're validating.
75
75
  * @param {string[]} obj The object that we're checking.
76
76
  * @param {string[]} allowTypes The types that are allowed.
77
+ * @param {boolean} allowEmpty Indicates whether or not empty values are allowed.
77
78
  */
78
- function checkObjectWithValues( configFile, configKey, obj, allowTypes ) {
79
+ function checkObjectWithValues(
80
+ configFile,
81
+ configKey,
82
+ obj,
83
+ allowTypes,
84
+ allowEmpty
85
+ ) {
79
86
  if ( allowTypes === undefined ) {
80
87
  allowTypes = [];
81
88
  }
@@ -87,18 +94,37 @@ function checkObjectWithValues( configFile, configKey, obj, allowTypes ) {
87
94
  }
88
95
 
89
96
  for ( const key in obj ) {
90
- if ( ! obj[ key ] && ! allowTypes.includes( 'empty' ) ) {
91
- throw new ValidationError(
92
- `Invalid ${ configFile }: "${ configKey }.${ key }" must not be empty.`
93
- );
97
+ // Some values need to be uniquely validated.
98
+ switch ( obj[ key ] ) {
99
+ case null:
100
+ case undefined: {
101
+ break;
102
+ }
103
+
104
+ default: {
105
+ if ( ! obj[ key ] && ! allowEmpty ) {
106
+ throw new ValidationError(
107
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must not be empty.`
108
+ );
109
+ }
110
+ }
94
111
  }
95
112
 
96
- // Identify arrays uniquely.
97
- const type = Array.isArray( obj[ key ] ) ? 'array' : typeof obj[ key ];
113
+ // Some types need to be uniquely identified.
114
+ let type;
115
+ if ( obj[ key ] === undefined ) {
116
+ type = 'undefined';
117
+ } else if ( obj[ key ] === null ) {
118
+ type = 'null';
119
+ } else if ( Array.isArray( obj[ key ] ) ) {
120
+ type = 'array';
121
+ } else {
122
+ type = typeof obj[ key ];
123
+ }
98
124
 
99
125
  if ( ! allowTypes.includes( type ) ) {
100
126
  throw new ValidationError(
101
- `Invalid ${ configFile }: "${ configKey }.${ key }" must be a ${ allowTypes.join(
127
+ `Invalid ${ configFile }: "${ configKey }.${ key }" must be of type: ${ allowTypes.join(
102
128
  ' or '
103
129
  ) }.`
104
130
  );
package/lib/env.js CHANGED
@@ -3,11 +3,11 @@
3
3
  * Internal dependencies
4
4
  */
5
5
  const { ValidationError } = require( './config' );
6
- const { AfterSetupError } = require( './execute-after-setup' );
6
+ const { LifecycleScriptError } = require( './execute-lifecycle-script' );
7
7
  const commands = require( './commands' );
8
8
 
9
9
  module.exports = {
10
10
  ...commands,
11
11
  ValidationError,
12
- AfterSetupError,
12
+ LifecycleScriptError,
13
13
  };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const { exec } = require( 'child_process' );
6
+
7
+ /**
8
+ * @typedef {import('./config').WPConfig} WPConfig
9
+ */
10
+
11
+ /**
12
+ * Error subtype which indicates that the lifecycle script failed.
13
+ */
14
+ class LifecycleScriptError extends Error {
15
+ constructor( event, stderr ) {
16
+ super( `${ event } Error:\n${ stderr }` );
17
+
18
+ this.event = event;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Executes any defined life cycle script.
24
+ *
25
+ * @param {string} event The lifecycle event to run the script for.
26
+ * @param {WPConfig} config The config object to use.
27
+ * @param {Object} spinner A CLI spinner which indciates progress.
28
+ *
29
+ * @return {Promise} Resolves when the script has completed and rejects when there is an error.
30
+ */
31
+ function executeLifecycleScript( event, config, spinner ) {
32
+ if ( ! config.lifecycleScripts[ event ] ) {
33
+ return Promise.resolve();
34
+ }
35
+
36
+ return new Promise( ( resolve, reject ) => {
37
+ // We're going to append the script output to the spinner while it's executing.
38
+ const spinnerMessage = `Executing ${ event } Script`;
39
+ spinner.text = spinnerMessage;
40
+
41
+ // Execute the script asynchronously so that it won't block the spinner.
42
+ const childProc = exec( config.lifecycleScripts[ event ], {
43
+ encoding: 'utf-8',
44
+ stdio: 'pipe',
45
+ env: process.env,
46
+ } );
47
+
48
+ // Collect all of the output so that we can make use of it.
49
+ let output = '';
50
+ childProc.stdout.on( 'data', ( data ) => {
51
+ output += data;
52
+
53
+ // Keep the spinner updated with the command output.
54
+ spinner.text = `${ spinnerMessage }\n${ output }`;
55
+ } );
56
+
57
+ // Listen for any error output so we can display it if the command fails.
58
+ let error = '';
59
+ childProc.stderr.on( 'data', ( data ) => {
60
+ error += data;
61
+ } );
62
+
63
+ // Pass any process creation errors directly up.
64
+ childProc.on( 'error', reject );
65
+
66
+ // Handle the completion of the command based on whether it was successful or not.
67
+ childProc.on( 'exit', ( code ) => {
68
+ if ( code === 0 ) {
69
+ // Keep the output of the command in debug mode.
70
+ if ( config.debug ) {
71
+ spinner.info( `${ event } Script:\n${ output.trimEnd() }` );
72
+ }
73
+
74
+ resolve();
75
+ return;
76
+ }
77
+
78
+ reject( new LifecycleScriptError( event, error.trimEnd() ) );
79
+ } );
80
+ } );
81
+ }
82
+
83
+ module.exports = {
84
+ LifecycleScriptError,
85
+ executeLifecycleScript,
86
+ };
@@ -13,14 +13,10 @@ module.exports = function getHostUser() {
13
13
  const hostUser = os.userInfo();
14
14
 
15
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();
16
+ // we're just going to assign them to 1000. Docker Desktop already takes care of
17
+ // permission-related issues using magic, so this should be fine.
18
+ const uid = ( hostUser.uid === -1 ? 1000 : hostUser.uid ).toString();
19
+ const gid = ( hostUser.gid === -1 ? 1000 : hostUser.gid ).toString();
24
20
 
25
21
  return {
26
22
  name: hostUser.username,