@automattic/vip 3.12.2 → 3.14.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.
@@ -23,6 +23,7 @@ exports.promptForComponent = promptForComponent;
23
23
  exports.promptForMultisite = promptForMultisite;
24
24
  exports.promptForPhpVersion = promptForPhpVersion;
25
25
  exports.promptForText = promptForText;
26
+ exports.promptForURL = promptForURL;
26
27
  exports.promptForWordPress = promptForWordPress;
27
28
  exports.resolvePath = resolvePath;
28
29
  exports.resolvePhpVersion = resolvePhpVersion;
@@ -161,7 +162,7 @@ function processComponentOptionInput(passedParam, allowLocal) {
161
162
  }
162
163
  return {
163
164
  mode: 'image',
164
- tag: param === 'demo' ? undefined : param
165
+ tag: param === 'demo' || param === 'image' ? undefined : param
165
166
  };
166
167
  }
167
168
  function getOptionsFromAppInfo(appInfo) {
@@ -190,6 +191,13 @@ function getOptionsFromAppInfo(appInfo) {
190
191
  // eslint-disable-next-line complexity
191
192
  async function promptForArguments(preselectedOptions, defaultOptions, suppressPrompts, create) {
192
193
  debug('Provided preselected', preselectedOptions, 'and default', defaultOptions);
194
+ let isVIPUser;
195
+ try {
196
+ const currentUser = await (0, _user.getCurrentUserInfo)(true);
197
+ isVIPUser = currentUser?.isVIP ?? false;
198
+ } catch (err) {
199
+ isVIPUser = false;
200
+ }
193
201
  if (suppressPrompts) {
194
202
  preselectedOptions = {
195
203
  ...defaultOptions,
@@ -208,7 +216,7 @@ async function promptForArguments(preselectedOptions, defaultOptions, suppressPr
208
216
  elasticsearch: false,
209
217
  php: preselectedOptions.php ? resolvePhpVersion(preselectedOptions.php) : await promptForPhpVersion(resolvePhpVersion(defaultOptions.php ?? _devEnvironment.DEV_ENVIRONMENT_DEFAULTS.phpVersion)),
210
218
  mariadb: preselectedOptions.mariadb ?? defaultOptions.mariadb,
211
- mediaRedirectDomain: preselectedOptions.mediaRedirectDomain ?? '',
219
+ mediaRedirectDomain: preselectedOptions.mediaRedirectDomain ?? defaultOptions.mediaRedirectDomain ?? '',
212
220
  wordpress: {
213
221
  mode: 'image',
214
222
  tag: ''
@@ -241,16 +249,19 @@ async function promptForArguments(preselectedOptions, defaultOptions, suppressPr
241
249
  if (setMediaRedirectDomain) {
242
250
  instanceData.mediaRedirectDomain = defaultOptions.mediaRedirectDomain;
243
251
  }
244
- }
245
- instanceData.wordpress = await processWordPress((preselectedOptions.wordpress ?? '').toString(), (defaultOptions.wordpress ?? '').toString());
246
- for (const component of _devEnvironment.DEV_ENVIRONMENT_COMPONENTS) {
247
- const option = (preselectedOptions[component] ?? '').toString();
248
- const defaultValue = (defaultOptions[component] ?? '').toString();
249
-
250
- // eslint-disable-next-line no-await-in-loop
251
- const result = await processComponent(component, option, defaultValue, suppressPrompts);
252
- instanceData[component] = result;
253
- }
252
+ } else if (!create && defaultOptions.mediaRedirectDomain) {
253
+ const mediaRedirectPromptText = 'URL to redirect for missing media files ("n" to disable)?';
254
+ const mediaRedirectDomain = await promptForURL(mediaRedirectPromptText, defaultOptions.mediaRedirectDomain);
255
+ instanceData.mediaRedirectDomain = mediaRedirectDomain;
256
+ }
257
+ instanceData.wordpress = await processWordPress(preselectedOptions.wordpress ?? '', defaultOptions.wordpress ?? '');
258
+ instanceData.appCode = await processComponent('appCode', preselectedOptions.appCode ?? '', defaultOptions.appCode ?? '', suppressPrompts, validateAppCodeLocalPath);
259
+ let preselectedMU = preselectedOptions.muPlugins;
260
+ const defaultMU = defaultOptions.muPlugins;
261
+ if (!preselectedMU && !isVIPUser && (!defaultMU || ['demo', 'image'].includes(defaultMU))) {
262
+ preselectedMU = 'image';
263
+ }
264
+ instanceData.muPlugins = await processComponent('muPlugins', preselectedMU ?? '', defaultMU ?? '', suppressPrompts, validateMuPluginsLocalPath);
254
265
  debug(`Processing elasticsearch with preselected "%s"`, preselectedOptions.elasticsearch);
255
266
  if ('elasticsearch' in preselectedOptions) {
256
267
  instanceData.elasticsearch = Boolean(preselectedOptions.elasticsearch);
@@ -295,48 +306,41 @@ async function processWordPress(preselectedValue, defaultValue) {
295
306
  debug(result);
296
307
  return result;
297
308
  }
298
- async function processComponent(component, preselectedValue, defaultValue, suppressPrompts = false) {
299
- debug(`processing a component '${component}', with preselected/default - ${preselectedValue}/${defaultValue}`);
300
- let result;
301
- let allowLocal = true;
302
- if (component === 'muPlugins') {
303
- try {
304
- const currentUser = await (0, _user.getCurrentUserInfo)(true);
305
- allowLocal = currentUser?.isVIP ?? false;
306
- } catch (err) {
307
- allowLocal = false;
308
- }
309
+ async function processComponent(componentType, preselectedValue, defaultValue, suppressPrompts, localPathValidator) {
310
+ debug(`Processing the '${componentType}' component, with preselected = %s, default = %s`, preselectedValue, defaultValue);
311
+ const defaultObject = defaultValue ? processComponentOptionInput(defaultValue, true) : null;
312
+ if (preselectedValue && !suppressPrompts) {
313
+ console.log('%s Path to your local %s: %s', _chalk.default.green('✓'), componentDisplayNames[componentType], preselectedValue);
309
314
  }
310
- const defaultObject = defaultValue ? processComponentOptionInput(defaultValue, allowLocal) : null;
311
- if (preselectedValue) {
312
- result = processComponentOptionInput(preselectedValue, allowLocal);
313
- if (!suppressPrompts) {
314
- console.log(`${_chalk.default.green('✓')} Path to your local ${componentDisplayNames[component]}: ${preselectedValue}`);
315
- }
316
- } else {
317
- result = await promptForComponent(component, allowLocal, defaultObject);
315
+ let result = preselectedValue ? processComponentOptionInput(preselectedValue, true) : await promptForComponent(componentType, true, defaultObject);
316
+ debug(result);
317
+ if (result.mode === 'local') {
318
+ result = await validateLocalPath(result, localPathValidator, componentType, defaultObject);
318
319
  }
319
320
  debug(result);
320
- while ('local' === result.mode) {
321
- const resolvedPath = resolvePath(result.dir ?? '');
322
- result.dir = resolvedPath;
321
+ return result;
322
+ }
323
+ async function validateLocalPath(config, validator, componentName, defaultObject) {
324
+ while (config.mode === 'local') {
325
+ const resolvedPath = resolvePath(config.dir ?? '');
326
+ config.dir = resolvedPath;
323
327
  const {
324
328
  result: isPathValid,
325
329
  message
326
- } = validateLocalPath(component, resolvedPath);
330
+ } = validator(resolvedPath);
327
331
  if (isPathValid) {
328
332
  break;
329
333
  } else if (isStdinTTY) {
330
334
  console.log(_chalk.default.yellow('Warning:'), message);
331
335
  // eslint-disable-next-line no-await-in-loop
332
- result = await promptForComponent(component, allowLocal, defaultObject);
336
+ config = await promptForComponent(componentName, true, defaultObject);
333
337
  } else {
334
338
  throw new Error(message);
335
339
  }
336
340
  }
337
- return result;
341
+ return config;
338
342
  }
339
- function validateLocalPath(component, providedPath) {
343
+ function validateMuPluginsLocalPath(providedPath) {
340
344
  if (!isNonEmptyDirectory(providedPath)) {
341
345
  const message = `Provided path "${providedPath}" does not point to a valid or existing directory.`;
342
346
  return {
@@ -344,24 +348,35 @@ function validateLocalPath(component, providedPath) {
344
348
  message
345
349
  };
346
350
  }
347
- if (component === 'appCode') {
348
- const files = ['languages', 'plugins', 'themes', 'private', 'images', 'client-mu-plugins', 'vip-config'];
349
- const missingFiles = [];
350
- for (const file of files) {
351
- const filePath = _path.default.resolve(providedPath, file);
352
- if (!(0, _nodeFs.existsSync)(filePath)) {
353
- missingFiles.push(file);
354
- }
355
- }
356
- if (missingFiles.length > 0) {
357
- // eslint-disable-next-line max-len
358
- const message = `Provided path "${providedPath}" is missing following files/folders: ${missingFiles.join(', ')}. Learn more: https://docs.wpvip.com/wordpress-skeleton/`;
359
- return {
360
- result: false,
361
- message
362
- };
351
+ return {
352
+ result: true,
353
+ message: ''
354
+ };
355
+ }
356
+ function validateAppCodeLocalPath(providedPath) {
357
+ if (!isNonEmptyDirectory(providedPath)) {
358
+ const message = `Provided path "${providedPath}" does not point to a valid or existing directory.`;
359
+ return {
360
+ result: false,
361
+ message
362
+ };
363
+ }
364
+ const files = ['languages', 'plugins', 'themes', 'private', 'images', 'client-mu-plugins', 'vip-config'];
365
+ const missingFiles = [];
366
+ for (const file of files) {
367
+ const filePath = _path.default.resolve(providedPath, file);
368
+ if (!(0, _nodeFs.existsSync)(filePath)) {
369
+ missingFiles.push(file);
363
370
  }
364
371
  }
372
+ if (missingFiles.length > 0) {
373
+ // eslint-disable-next-line max-len
374
+ const message = `Provided path "${providedPath}" is missing following files/folders: ${missingFiles.join(', ')}. Learn more: https://docs.wpvip.com/wordpress-skeleton/`;
375
+ return {
376
+ result: false,
377
+ message
378
+ };
379
+ }
365
380
  return {
366
381
  result: true,
367
382
  message: ''
@@ -399,6 +414,36 @@ async function promptForText(message, initial) {
399
414
  }
400
415
  return (result.input || '').trim();
401
416
  }
417
+ async function promptForURL(message, initial) {
418
+ let result = {
419
+ input: initial
420
+ };
421
+ if (isStdinTTY) {
422
+ const URLValidator = value => {
423
+ if (!value.trim() || value === 'n') {
424
+ return true;
425
+ }
426
+ try {
427
+ const url = new URL(value);
428
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
429
+ return 'value must be a http:// or https:// URL';
430
+ }
431
+ return true;
432
+ } catch {
433
+ return 'value needs to be a valid URL or an empty string';
434
+ }
435
+ };
436
+ result = await (0, _enquirer.prompt)({
437
+ type: 'input',
438
+ name: 'input',
439
+ message,
440
+ initial,
441
+ validate: URLValidator
442
+ });
443
+ }
444
+ const retval = result.input.trim();
445
+ return retval === 'n' ? '' : retval;
446
+ }
402
447
  const multisiteOptions = ['subdomain', 'subdirectory'];
403
448
  async function promptForMultisite(message, initial) {
404
449
  // `undefined` is used here only because our tests need overhauling
@@ -676,30 +721,92 @@ function getEnvTrackingInfo(slug) {
676
721
  };
677
722
  }
678
723
  }
724
+ // Map of supported editors and their configurations
725
+ const SUPPORTED_EDITORS = {
726
+ vscode: {
727
+ displayName: 'VS Code',
728
+ candidates: ['code', 'code-insiders', 'codium'],
729
+ workspace: {
730
+ extension: 'code-workspace',
731
+ generator: _devEnvironmentCore.generateVSCodeWorkspace
732
+ }
733
+ },
734
+ cursor: {
735
+ displayName: 'Cursor',
736
+ candidates: ['cursor'],
737
+ workspace: {
738
+ extension: 'code-workspace',
739
+ generator: _devEnvironmentCore.generateVSCodeWorkspace
740
+ }
741
+ },
742
+ phpstorm: {
743
+ displayName: 'PHPStorm',
744
+ candidates: ['phpstorm', 'phpstorm64.exe'],
745
+ errors: {
746
+ notFound: `PHPStorm launcher was not detected in the expected path.\nPlease follow the setup instructions: https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html#standalone`
747
+ },
748
+ workspace: {
749
+ extension: 'iml',
750
+ generator: _devEnvironmentCore.generatePHPStormWorkspace
751
+ }
752
+ },
753
+ windsurf: {
754
+ displayName: 'Windsurf',
755
+ candidates: ['windsurf'],
756
+ workspace: {
757
+ extension: 'code-workspace',
758
+ generator: _devEnvironmentCore.generateVSCodeWorkspace
759
+ }
760
+ }
761
+ };
762
+
763
+ // Helper to get workspace path for any editor
764
+ function getWorkspacePath(slug, editor) {
765
+ const {
766
+ workspace
767
+ } = SUPPORTED_EDITORS[editor];
768
+ if (editor === 'phpstorm') {
769
+ return _path.default.join((0, _devEnvironmentCore.getEnvironmentPath)(slug), '.idea', `${slug}.${workspace.extension}`);
770
+ }
771
+ return _path.default.join((0, _devEnvironmentCore.getEnvironmentPath)(slug), `${slug}.${workspace.extension}`);
772
+ }
679
773
  function postStart(slug, options) {
680
- if (options.openVSCode) {
681
- launchVSCode(slug);
774
+ let editorType;
775
+ if (options.editor) {
776
+ const editor = options.editor.toLowerCase();
777
+ if (!Object.keys(SUPPORTED_EDITORS).includes(editor)) {
778
+ throw new Error(`Invalid editor specified. Supported editors are: ${Object.keys(SUPPORTED_EDITORS).join(', ')}`);
779
+ }
780
+ editorType = editor;
781
+ } else if (options.vscode) {
782
+ editorType = 'vscode';
783
+ }
784
+ if (editorType) {
785
+ launchEditor(slug, editorType);
682
786
  }
683
787
  }
684
- const launchVSCode = slug => {
685
- const workspacePath = (0, _devEnvironmentCore.getVSCodeWorkspacePath)(slug);
788
+ const launchEditor = (slug, type) => {
789
+ const editorConfig = SUPPORTED_EDITORS[type];
790
+ const workspacePath = getWorkspacePath(slug, type);
686
791
  if ((0, _nodeFs.existsSync)(workspacePath)) {
687
- console.log('VS Code workspace already exists, skipping creation.');
792
+ console.log('Project already exists, skipping creation.');
688
793
  } else {
689
- (0, _devEnvironmentCore.generateVSCodeWorkspace)(slug);
690
- console.log('VS Code workspace generated');
691
- }
692
- const vsCodeExecutable = getVSCodeExecutable();
693
- if (vsCodeExecutable) {
694
- (0, _child_process.spawn)(vsCodeExecutable, [workspacePath], {
794
+ editorConfig.workspace.generator(slug);
795
+ console.log(`${editorConfig.displayName} project generated`);
796
+ }
797
+ console.log(`Project file location:\n${workspacePath}\n`);
798
+ const executable = findExecutable(editorConfig.candidates);
799
+ if (executable) {
800
+ // For PHPStorm, pass the environment home folder instead of the workspace path
801
+ const launchPath = type === 'phpstorm' ? (0, _devEnvironmentCore.getEnvironmentPath)(slug) : workspacePath;
802
+ (0, _child_process.spawn)(executable, [launchPath], {
695
803
  shell: process.platform === 'win32'
696
804
  });
697
805
  } else {
698
- console.log(`VS Code was not detected in the expected path. VS Code Workspace file location:\n${workspacePath}`);
806
+ console.log(editorConfig.errors?.notFound || `${editorConfig.displayName} was not detected in the expected path.`);
699
807
  }
700
808
  };
701
- const getVSCodeExecutable = () => {
702
- const candidates = ['code', 'code-insiders', 'codium'];
809
+ const findExecutable = candidates => {
703
810
  for (const candidate of candidates) {
704
811
  const result = (0, _shelljs.which)(candidate);
705
812
  if (result) {
@@ -6,6 +6,7 @@ exports.destroyEnvironment = destroyEnvironment;
6
6
  exports.doesEnvironmentExist = doesEnvironmentExist;
7
7
  exports.exec = exec;
8
8
  exports.fetchVersionList = fetchVersionList;
9
+ exports.generatePHPStormWorkspace = generatePHPStormWorkspace;
9
10
  exports.generateVSCodeWorkspace = generateVSCodeWorkspace;
10
11
  exports.getAllEnvironmentNames = getAllEnvironmentNames;
11
12
  exports.getApplicationInformation = getApplicationInformation;
@@ -23,7 +24,6 @@ exports.stopEnvironment = stopEnvironment;
23
24
  exports.updateEnvironment = updateEnvironment;
24
25
  exports.writeEnvironmentData = writeEnvironmentData;
25
26
  var _chalk = _interopRequireDefault(require("chalk"));
26
- var _copyDir = _interopRequireDefault(require("copy-dir"));
27
27
  var _debug = _interopRequireDefault(require("debug"));
28
28
  var _ejs = _interopRequireDefault(require("ejs"));
29
29
  var _enquirer = require("enquirer");
@@ -31,6 +31,7 @@ var _graphql = require("graphql");
31
31
  var _utils = require("lando/lib/utils");
32
32
  var _nodeFetch = _interopRequireDefault(require("node-fetch"));
33
33
  var _nodeFs = _interopRequireDefault(require("node:fs"));
34
+ var _promises = require("node:fs/promises");
34
35
  var _nodePath = _interopRequireDefault(require("node:path"));
35
36
  var _semver = _interopRequireDefault(require("semver"));
36
37
  var _uuid = require("uuid");
@@ -69,11 +70,13 @@ function xdgDataDirectory() {
69
70
  async function startEnvironment(lando, slug, options) {
70
71
  debug('Will start an environment', slug);
71
72
  const instancePath = getEnvironmentPath(slug);
72
- debug('Instance path for', slug, 'is:', instancePath);
73
+ debug('Instance path for %s is %s', slug, instancePath);
73
74
  const environmentExists = _nodeFs.default.existsSync(instancePath);
74
75
  if (!environmentExists) {
75
76
  throw new Error(_devEnvironment.DEV_ENVIRONMENT_NOT_FOUND);
76
77
  }
78
+ const envFilePath = _nodePath.default.join(instancePath, '.env');
79
+ _nodeFs.default.appendFileSync(envFilePath, '');
77
80
  let updated = false;
78
81
  if (!options.skipWpVersionsCheck && process.stdin.isTTY) {
79
82
  updated = await maybeUpdateWordPressImage(lando, slug);
@@ -98,7 +101,7 @@ async function stopEnvironment(lando, slug) {
98
101
  }
99
102
  await (0, _devEnvironmentLando.landoStop)(lando, instancePath);
100
103
  }
101
- async function createEnvironment(lando, instanceData, integrationsConfig) {
104
+ async function createEnvironment(lando, instanceData, integrationsConfig, envVars) {
102
105
  const slug = instanceData.siteSlug;
103
106
  integrationsConfig ??= {};
104
107
  debug('Will process an environment', slug, 'with instanceData for creation: ', instanceData);
@@ -110,7 +113,7 @@ async function createEnvironment(lando, instanceData, integrationsConfig) {
110
113
  }
111
114
  const preProcessedInstanceData = preProcessInstanceData(instanceData);
112
115
  debug('Will create an environment', slug, 'with instanceData: ', preProcessedInstanceData);
113
- await prepareLandoEnv(lando, preProcessedInstanceData, instancePath, integrationsConfig);
116
+ await prepareLandoEnv(lando, preProcessedInstanceData, instancePath, integrationsConfig, envVars);
114
117
  }
115
118
  async function updateEnvironment(lando, instanceData) {
116
119
  const slug = instanceData.siteSlug;
@@ -123,7 +126,7 @@ async function updateEnvironment(lando, instanceData) {
123
126
  }
124
127
  const preProcessedInstanceData = preProcessInstanceData(instanceData);
125
128
  debug('Will create an environment', slug, 'with instanceData: ', preProcessedInstanceData);
126
- await prepareLandoEnv(lando, preProcessedInstanceData, instancePath, undefined);
129
+ await prepareLandoEnv(lando, preProcessedInstanceData, instancePath, undefined, undefined);
127
130
  }
128
131
  function preProcessInstanceData(instanceData) {
129
132
  const newInstanceData = {
@@ -366,7 +369,7 @@ async function writeIntegrationsConfig(instancePath, integrationsConfig) {
366
369
  debug(`Integrations configuration file created in ${integrationsConfigTargetPath}`);
367
370
  }
368
371
  }
369
- async function prepareLandoEnv(lando, instanceData, instancePath, integrationsConfig) {
372
+ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsConfig, envVars) {
370
373
  const templateData = {
371
374
  ...instanceData,
372
375
  domain: lando.config.domain
@@ -380,6 +383,7 @@ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsCo
380
383
  const nginxFolderPath = _nodePath.default.join(instancePath, nginxPathString);
381
384
  const nginxFileTargetPath = _nodePath.default.join(nginxFolderPath, nginxFileName);
382
385
  const instanceDataTargetPath = _nodePath.default.join(instancePath, instanceDataFileName);
386
+ const envFilePath = _nodePath.default.join(instancePath, '.env');
383
387
  await _nodeFs.default.promises.mkdir(instancePath, {
384
388
  recursive: true
385
389
  });
@@ -396,6 +400,15 @@ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsCo
396
400
  }
397
401
  }
398
402
  await Promise.all([_nodeFs.default.promises.writeFile(landoFileTargetPath, landoFile), _nodeFs.default.promises.writeFile(nginxFileTargetPath, nginxFile), _nodeFs.default.promises.writeFile(instanceDataTargetPath, instanceDataFile)]);
403
+ if (envVars !== undefined) {
404
+ const env = [];
405
+ Object.entries(envVars ?? {}).forEach(([key]) => {
406
+ env.push(`VIP_ENV_VAR_${key}=`);
407
+ });
408
+ await _nodeFs.default.promises.writeFile(envFilePath, env.join('\n'));
409
+ } else {
410
+ await _nodeFs.default.promises.appendFile(envFilePath, '');
411
+ }
399
412
  debug(`Lando file created in ${landoFileTargetPath}`);
400
413
  debug(`Nginx file created in ${nginxFileTargetPath}`);
401
414
  debug(`Instance data file created in ${instanceDataTargetPath}`);
@@ -444,6 +457,11 @@ async function getApplicationInformation(appId, envType) {
444
457
  type,
445
458
  branch,
446
459
  isMultisite,
460
+ environmentVariables {
461
+ nodes {
462
+ name
463
+ }
464
+ }
447
465
  getIntegrationsDevEnvConfig {
448
466
  data
449
467
  }
@@ -486,6 +504,12 @@ async function getApplicationInformation(appId, envType) {
486
504
  envData = environments.find(candidateEnv => candidateEnv.type === env);
487
505
  }
488
506
  if (envData) {
507
+ const envVars = {};
508
+ envData.environmentVariables?.nodes?.forEach(envvar => {
509
+ if (envvar?.name) {
510
+ envVars[envvar.name] = '';
511
+ }
512
+ });
489
513
  appData.environment = {
490
514
  name: envData.name,
491
515
  branch: envData.branch,
@@ -494,7 +518,8 @@ async function getApplicationInformation(appId, envType) {
494
518
  primaryDomain: envData.primaryDomain?.name ?? '',
495
519
  php: envData.softwareSettings?.php?.current.version ?? '',
496
520
  wordpress: envData.softwareSettings?.wordpress?.current.version ?? '',
497
- integrations: envData.getIntegrationsDevEnvConfig?.data ?? {}
521
+ integrations: envData.getIntegrationsDevEnvConfig?.data ?? {},
522
+ envVars
498
523
  };
499
524
  }
500
525
  }
@@ -536,7 +561,7 @@ async function importMediaPath(slug, filePath) {
536
561
  if (!(await doesEnvironmentExist(environmentPath))) {
537
562
  throw new Error(_devEnvironment.DEV_ENVIRONMENT_NOT_FOUND);
538
563
  }
539
- const files = _nodeFs.default.readdirSync(resolvedPath);
564
+ const files = await (0, _promises.readdir)(resolvedPath);
540
565
  if (files.includes(uploadPathString)) {
541
566
  const confirm = await (0, _enquirer.prompt)({
542
567
  type: 'confirm',
@@ -549,8 +574,15 @@ async function importMediaPath(slug, filePath) {
549
574
  }
550
575
  const uploadsPath = _nodePath.default.join(environmentPath, uploadPathString);
551
576
  console.log(`${_chalk.default.yellow('-')} Started copying files`);
552
- _copyDir.default.sync(resolvedPath, uploadsPath);
553
- console.log(`${_chalk.default.green('✓')} Files successfully copied to ${uploadsPath}.`);
577
+ try {
578
+ await (0, _promises.cp)(resolvedPath, uploadsPath, {
579
+ recursive: true
580
+ });
581
+ console.log(`${_chalk.default.green('✓')} Files successfully copied to ${uploadsPath}.`);
582
+ } catch (error) {
583
+ console.error(`${_chalk.default.red('✗')} Error copying files to ${uploadsPath}.`);
584
+ throw error;
585
+ }
554
586
  }
555
587
 
556
588
  /**
@@ -768,17 +800,27 @@ function generateVSCodeWorkspace(slug) {
768
800
  path: instanceData.appCode.dir
769
801
  });
770
802
  }
803
+
804
+ // Create debug configuration
805
+ const debugConfig = {
806
+ name: `Debug ${slug}`,
807
+ type: 'php',
808
+ request: 'launch',
809
+ port: 9003,
810
+ pathMappings
811
+ };
812
+
813
+ // Check if running under WSL and add hostname if needed
814
+ // This is to allow xdebug to work when running under WSL
815
+ if (process.env?.WSL_DISTRO_NAME) {
816
+ debug('WSL detected, adding hostname to debug configuration');
817
+ debugConfig.hostname = '0.0.0.0';
818
+ }
771
819
  const workspace = {
772
820
  folders,
773
821
  launch: {
774
822
  version: '0.2.0',
775
- configurations: [{
776
- name: `Debug ${slug}`,
777
- type: 'php',
778
- request: 'launch',
779
- port: 9003,
780
- pathMappings
781
- }]
823
+ configurations: [debugConfig]
782
824
  }
783
825
  };
784
826
  _nodeFs.default.writeFileSync(workspacePath, JSON.stringify(workspace, null, 2));
@@ -796,7 +838,7 @@ const generatePathMappings = (location, instanceData) => {
796
838
  pathMappings['/wp/wp-content/plugins'] = _nodePath.default.resolve(instanceData.appCode.dir, 'plugins');
797
839
  pathMappings['/wp/wp-content/private'] = _nodePath.default.resolve(instanceData.appCode.dir, 'private');
798
840
  pathMappings['/wp/wp-content/themes'] = _nodePath.default.resolve(instanceData.appCode.dir, 'themes');
799
- pathMappings['/wp/wp-content/vip-config'] = _nodePath.default.resolve(instanceData.appCode.dir, 'vip-config');
841
+ pathMappings['/wp/vip-config'] = _nodePath.default.resolve(instanceData.appCode.dir, 'vip-config');
800
842
  }
801
843
  pathMappings['/wp'] = _nodePath.default.resolve(location, 'wordpress');
802
844
  return pathMappings;
@@ -805,4 +847,88 @@ function getVSCodeWorkspacePath(slug) {
805
847
  const location = getEnvironmentPath(slug);
806
848
  const workspacePath = _nodePath.default.join(location, `${slug}.code-workspace`);
807
849
  return workspacePath;
850
+ }
851
+
852
+ /**
853
+ * Generates PHPStorm project configuration including debug settings
854
+ *
855
+ * @param {string} slug - The slug of the environment to generate PHPStorm config for
856
+ * @return {string} Project directory path
857
+ */
858
+ function generatePHPStormWorkspace(slug) {
859
+ debug('Generating PHPStorm Workspace');
860
+ const location = getEnvironmentPath(slug);
861
+ // const location = location;
862
+ const instanceData = readEnvironmentData(slug);
863
+ const pathMappings = generatePathMappings(location, instanceData);
864
+
865
+ // Create .idea directory
866
+ _nodeFs.default.mkdirSync(_nodePath.default.join(location, '.idea', 'runConfigurations'), {
867
+ recursive: true
868
+ });
869
+
870
+ // Generate workspace.xml
871
+ const workspaceXml = `<?xml version="1.0" encoding="UTF-8"?>
872
+ <project version="4">
873
+ <component name="PhpWorkspaceProjectConfiguration">
874
+ <include_path>
875
+ <path value="$PROJECT_DIR$/wordpress" />
876
+ ${instanceData?.muPlugins?.dir ? `<path value="${instanceData.muPlugins.dir}" />` : ''}
877
+ ${instanceData?.appCode?.dir ? `<path value="${instanceData.appCode.dir}" />` : ''}
878
+ </include_path>
879
+ </component>
880
+ <component name="PhpDebugGeneral" listening_started="true" />
881
+ <component name="PhpDebugXdebugSettings">
882
+ <debug_server_list>
883
+ <server host="localhost" port="9003" />
884
+ </debug_server_list>
885
+ <path_mappings>
886
+ ${Object.entries(pathMappings).map(([serverPath, localPath]) => ` <mapping local-root="${serverPath}" remote-root="$PROJECT_DIR$/${_nodePath.default.relative(location, localPath)}" />`).join('\n')}
887
+ </path_mappings>
888
+ </component>
889
+ <component name="PhpServers">
890
+ <servers>
891
+ <server host="localhost" id="${(0, _uuid.v4)()}" name="localhost" use_path_mappings="true">
892
+ <path_mappings>
893
+ <mapping local-root="$PROJECT_DIR$/wordpress" remote-root="/wp" />
894
+ ${Object.entries(pathMappings).map(([serverPath, localPath]) => `<mapping local-root="${localPath}" remote-root="${serverPath}" />`).join('\n')}
895
+ </path_mappings>
896
+ </server>
897
+ </servers>
898
+ </component>
899
+ <component name="WordPressConfiguration" enabled="true">
900
+ <wordpressPath>$PROJECT_DIR$/wordpress</wordpressPath>
901
+ </component>
902
+ </project>`;
903
+ _nodeFs.default.writeFileSync(_nodePath.default.join(location, '.idea', 'workspace.xml'), workspaceXml);
904
+ const xdebugXml = `<component name="ProjectRunConfigurationManager">
905
+ <configuration default="false" name="VIP Debug" type="PhpRemoteDebugRunConfigurationType" factoryName="PHP Remote Debug" nameIsGenerated="true" filter_connections="NOT_FILTER" server_name="localhost" session_id="XDEBUG">
906
+ <method v="2" />
907
+ </configuration>
908
+ </component>
909
+ `;
910
+ _nodeFs.default.writeFileSync(_nodePath.default.join(location, '.idea', 'runConfigurations', 'vip-xdebug.xml'), xdebugXml);
911
+ const projectXml = `<?xml version="1.0" encoding="UTF-8"?>
912
+ <project version="4">
913
+ <component name="ProjectModuleManager">
914
+ <modules>
915
+ <module fileurl="file://$PROJECT_DIR$/.idea/${slug}.iml" filepath="$PROJECT_DIR$/.idea/${slug}.iml" />
916
+ </modules>
917
+ </component>
918
+ </project>
919
+ `;
920
+ _nodeFs.default.writeFileSync(_nodePath.default.join(location, '.idea', 'modules.xml'), projectXml);
921
+ const modulesXml = `<?xml version="1.0" encoding="UTF-8"?>
922
+ <module type="WEB_MODULE" version="4">
923
+ <component name="NewModuleRootManager">
924
+ <content url="file://$MODULE_DIR$/${_nodePath.default.relative(location, instanceData?.appCode?.dir ?? '')}" />
925
+ <content url="file://$MODULE_DIR$/${_nodePath.default.relative(location, instanceData?.muPlugins?.dir ?? '')}" />
926
+ <content url="file://$MODULE_DIR$" />
927
+ <orderEntry type="sourceFolder" forTests="false" />
928
+ <orderEntry type="inheritedJdk" />
929
+ </component>
930
+ </module>
931
+ `;
932
+ _nodeFs.default.writeFileSync(_nodePath.default.join(location, '.idea', slug + '.iml'), modulesXml);
933
+ return location;
808
934
  }
@@ -44,9 +44,7 @@ const debug = (0, _debug.default)(DEBUG_KEY);
44
44
  async function getLandoConfig() {
45
45
  // The path will be smth like `yarn/global/node_modules/lando/lib/lando.js`; we need the path up to `lando` (inclusive)
46
46
  const landoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(require.resolve('lando')));
47
- // The path will be smth like `yarn/global/node_modules/@lando/compose/index.js`; we need the path up to `@lando` (inclusive)
48
- const atLandoPath = (0, _nodePath.dirname)((0, _nodePath.dirname)(require.resolve('@lando/compose')));
49
- debug(`Getting Lando config, using paths '${landoPath}' and '${atLandoPath}' for plugins`);
47
+ debug(`Getting Lando config, using paths '${landoPath}' for plugins`);
50
48
  const isLandoDebugSelected = _debug.default.enabled(DEBUG_KEY);
51
49
  const isAllDebugSelected = _debug.default.enabled('"*"');
52
50
  let logLevelConsole;
@@ -75,11 +73,7 @@ async function getLandoConfig() {
75
73
  landoFile: '.lando.yml',
76
74
  preLandoFiles: ['.lando.base.yml', '.lando.dist.yml', '.lando.upstream.yml'],
77
75
  postLandoFiles: ['.lando.local.yml'],
78
- pluginDirs: [landoPath, {
79
- path: atLandoPath,
80
- subdir: '.',
81
- namespace: '@lando'
82
- }],
76
+ pluginDirs: [landoPath],
83
77
  disablePlugins: [],
84
78
  proxyName: 'vip-dev-env-proxy',
85
79
  userConfRoot: landoDir,
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.MediaImportProgressTracker = void 0;
5
- var _singleLineLog = require("single-line-log");
5
+ var _singleLineLog = require("@wwa/single-line-log");
6
6
  var _format = require("../../lib/cli/format");
7
7
  var _status = require("../../lib/media-import/status");
8
8
  const PRINT_INTERVAL = process.env.DEBUG ? 5000 : 200; // How often the report is printed. Mainly affects the "spinner" animation.