@automattic/vip 2.27.0-dev3 → 2.27.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.
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CONFIGURATION_FILE_NAME = void 0;
7
+ exports.getConfigurationFileOptions = getConfigurationFileOptions;
8
+ exports.mergeConfigurationFileOptions = mergeConfigurationFileOptions;
9
+ exports.printConfigurationFile = printConfigurationFile;
10
+ var _debug = _interopRequireDefault(require("debug"));
11
+ var _fs = _interopRequireDefault(require("fs"));
12
+ var _path = _interopRequireDefault(require("path"));
13
+ var _chalk = _interopRequireDefault(require("chalk"));
14
+ var _jsYaml = _interopRequireWildcard(require("js-yaml"));
15
+ var exit = _interopRequireWildcard(require("../cli/exit"));
16
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+ /**
20
+ *
21
+ * @format
22
+ */
23
+
24
+ /**
25
+ * External dependencies
26
+ */
27
+
28
+ /**
29
+ * Internal dependencies
30
+ */
31
+
32
+ const debug = (0, _debug.default)('@automattic/vip:bin:dev-environment');
33
+ const CONFIGURATION_FILE_NAME = '.vip-dev-env.yml';
34
+ exports.CONFIGURATION_FILE_NAME = CONFIGURATION_FILE_NAME;
35
+ async function getConfigurationFileOptions() {
36
+ const configurationFilePath = _path.default.join(process.cwd(), CONFIGURATION_FILE_NAME);
37
+ let configurationFileContents = '';
38
+ const fileExists = await _fs.default.promises.access(configurationFilePath, _fs.default.R_OK).then(() => true).catch(() => false);
39
+ if (fileExists) {
40
+ debug('Reading configuration file from:', configurationFilePath);
41
+ configurationFileContents = await _fs.default.promises.readFile(configurationFilePath, 'utf8');
42
+ } else {
43
+ return {};
44
+ }
45
+ let configurationFromFile = {};
46
+ try {
47
+ configurationFromFile = _jsYaml.default.load(configurationFileContents, {
48
+ // Only allow strings, arrays, and objects to be parsed from configuration file
49
+ // This causes number-looking values like `php: 8.1` to be parsed directly into strings
50
+ schema: _jsYaml.FAILSAFE_SCHEMA
51
+ });
52
+ } catch (err) {
53
+ const messageToShow = `Configuration file ${_chalk.default.grey(CONFIGURATION_FILE_NAME)} could not be loaded:\n` + err.toString();
54
+ exit.withError(messageToShow);
55
+ }
56
+ const configuration = await sanitizeConfiguration(configurationFromFile).catch(async ({
57
+ message
58
+ }) => {
59
+ exit.withError(message);
60
+ return {};
61
+ });
62
+ debug('Sanitized configuration from file:', configuration);
63
+ return configuration;
64
+ }
65
+ async function sanitizeConfiguration(configuration) {
66
+ const genericConfigurationError = `Configuration file ${_chalk.default.grey(CONFIGURATION_FILE_NAME)} is available but ` + `couldn't be loaded. Ensure there is a ${_chalk.default.cyan('configuration-version')} and ${_chalk.default.cyan('slug')} ` + `configured. For example:\n\n${_chalk.default.grey(getConfigurationFileExample())}`;
67
+ if (Array.isArray(configuration) || typeof configuration !== 'object') {
68
+ throw new Error(genericConfigurationError);
69
+ } else if (configuration['configuration-version'] === undefined || configuration.slug === undefined) {
70
+ throw new Error(genericConfigurationError);
71
+ }
72
+ const validVersions = getAllConfigurationFileVersions().map(version => _chalk.default.cyan(version)).join(', ');
73
+ if (!isValidConfigurationFileVersion(configuration['configuration-version'])) {
74
+ throw new Error(`Configuration file ${_chalk.default.grey(CONFIGURATION_FILE_NAME)} has an invalid ` + `${_chalk.default.cyan('configuration-version')} key. Update to a supported version. For example:\n\n` + _chalk.default.grey(getConfigurationFileExample()) + `\nSupported configuration versions: ${validVersions}.\n`);
75
+ }
76
+ const stringToBooleanIfDefined = value => {
77
+ if (value === undefined || !['true', 'false'].includes(value)) {
78
+ return undefined;
79
+ }
80
+ return value === 'true';
81
+ };
82
+ const sanitizedConfiguration = {
83
+ 'configuration-version': configuration['configuration-version'],
84
+ slug: configuration.slug,
85
+ title: configuration.title,
86
+ multisite: stringToBooleanIfDefined(configuration.multisite),
87
+ php: configuration.php,
88
+ wordpress: configuration.wordpress,
89
+ 'mu-plugins': configuration['mu-plugins'],
90
+ 'app-code': configuration['app-code'],
91
+ elasticsearch: stringToBooleanIfDefined(configuration.elasticsearch),
92
+ phpmyadmin: stringToBooleanIfDefined(configuration.phpmyadmin),
93
+ xdebug: stringToBooleanIfDefined(configuration.xdebug),
94
+ mailhog: stringToBooleanIfDefined(configuration.mailhog)
95
+ };
96
+
97
+ // Remove undefined values
98
+ Object.keys(sanitizedConfiguration).forEach(key => sanitizedConfiguration[key] === undefined && delete sanitizedConfiguration[key]);
99
+ return sanitizedConfiguration;
100
+ }
101
+ function mergeConfigurationFileOptions(preselectedOptions, configurationFileOptions) {
102
+ // configurationFileOptions holds different parameters than present in
103
+ // preselectedOptions like "slug", and friendly-named parameters (e.g.
104
+ // 'app-code' vs 'appCode'). Selectively merge configurationFileOptions
105
+ // parameters into preselectedOptions.
106
+ const configurationFileInstanceOptions = {
107
+ title: configurationFileOptions.title,
108
+ multisite: configurationFileOptions.multisite,
109
+ php: configurationFileOptions.php,
110
+ wordpress: configurationFileOptions.wordpress,
111
+ muPlugins: configurationFileOptions['mu-plugins'],
112
+ appCode: configurationFileOptions['app-code'],
113
+ elasticsearch: configurationFileOptions.elasticsearch,
114
+ phpmyadmin: configurationFileOptions.phpmyadmin,
115
+ xdebug: configurationFileOptions.xdebug,
116
+ mailhog: configurationFileOptions.mailhog
117
+ };
118
+ const mergedOptions = {};
119
+ Object.keys(configurationFileInstanceOptions).forEach(key => {
120
+ // preselectedOptions (supplied from command-line) override configurationFileOptions
121
+ if (preselectedOptions[key] !== undefined) {
122
+ mergedOptions[key] = preselectedOptions[key];
123
+ } else if (configurationFileInstanceOptions[key] !== undefined) {
124
+ mergedOptions[key] = configurationFileInstanceOptions[key];
125
+ }
126
+ });
127
+ return mergedOptions;
128
+ }
129
+ function printConfigurationFile(configurationOptions) {
130
+ const isConfigurationFileEmpty = Object.keys(configurationOptions).length === 0;
131
+ if (isConfigurationFileEmpty) {
132
+ return;
133
+ }
134
+
135
+ // Customized formatter because Lando's printTable() automatically uppercases keys
136
+ // which may be confusing for YAML configuration
137
+ const settingLines = [];
138
+ for (const [key, value] of Object.entries(configurationOptions)) {
139
+ settingLines.push(`${_chalk.default.cyan(key)}: ${String(value)}`);
140
+ }
141
+ console.log(settingLines.join('\n') + '\n');
142
+ }
143
+ const CONFIGURATION_FILE_VERSIONS = ['0.preview-unstable'];
144
+ function getAllConfigurationFileVersions() {
145
+ return CONFIGURATION_FILE_VERSIONS;
146
+ }
147
+ function getLatestConfigurationFileVersion() {
148
+ return CONFIGURATION_FILE_VERSIONS[CONFIGURATION_FILE_VERSIONS.length - 1];
149
+ }
150
+ function isValidConfigurationFileVersion(version) {
151
+ return CONFIGURATION_FILE_VERSIONS.includes(version);
152
+ }
153
+ function getConfigurationFileExample() {
154
+ return `configuration-version: ${getLatestConfigurationFileVersion()}
155
+ slug: dev-site
156
+ php: 8.0
157
+ wordpress: 6.0
158
+ app-code: ./site-code
159
+ mu-plugins: image
160
+ multisite: false
161
+ phpmyadmin: true
162
+ elasticsearch: true
163
+ xdebug: true
164
+ `;
165
+ }
@@ -17,6 +17,7 @@ exports.printAllEnvironmentsInfo = printAllEnvironmentsInfo;
17
17
  exports.printEnvironmentInfo = printEnvironmentInfo;
18
18
  exports.readEnvironmentData = readEnvironmentData;
19
19
  exports.resolveImportPath = resolveImportPath;
20
+ exports.showLogs = showLogs;
20
21
  exports.startEnvironment = startEnvironment;
21
22
  exports.stopEnvironment = stopEnvironment;
22
23
  exports.updateEnvironment = updateEnvironment;
@@ -200,6 +201,18 @@ function parseComponentForInfo(component) {
200
201
  }
201
202
  return component.tag || '[demo-image]';
202
203
  }
204
+ async function showLogs(lando, slug, options = {}) {
205
+ debug('Will display logs command on env', slug, 'with options', options);
206
+ const instancePath = getEnvironmentPath(slug);
207
+ debug('Instance path for', slug, 'is:', instancePath);
208
+ if (options.service) {
209
+ const appInfo = await (0, _devEnvironmentLando.landoInfo)(lando, instancePath);
210
+ if (!appInfo.services.includes(options.service)) {
211
+ throw new _userError.default(`Service '${options.service}' not found. Please choose from one: ${appInfo.services}`);
212
+ }
213
+ }
214
+ return (0, _devEnvironmentLando.landoLogs)(lando, instancePath, options);
215
+ }
203
216
  async function printEnvironmentInfo(lando, slug, options) {
204
217
  debug('Will get info for an environment', slug);
205
218
  const instancePath = getEnvironmentPath(slug);
@@ -9,7 +9,9 @@ exports.isEnvUp = isEnvUp;
9
9
  exports.landoDestroy = landoDestroy;
10
10
  exports.landoExec = landoExec;
11
11
  exports.landoInfo = landoInfo;
12
+ exports.landoLogs = landoLogs;
12
13
  exports.landoRebuild = landoRebuild;
14
+ exports.landoShell = landoShell;
13
15
  exports.landoStart = landoStart;
14
16
  exports.landoStop = landoStop;
15
17
  exports.validateDockerAccess = validateDockerAccess;
@@ -42,7 +44,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
42
44
  */
43
45
  const DEBUG_KEY = '@automattic/vip:bin:dev-environment';
44
46
  const debug = (0, _debug.default)(DEBUG_KEY);
45
-
46
47
  /**
47
48
  * @return {Promise<object>} Lando configuration
48
49
  */
@@ -160,6 +161,17 @@ async function landoStart(lando, instancePath) {
160
161
  const app = await getLandoApplication(lando, instancePath);
161
162
  await app.start();
162
163
  }
164
+ async function landoLogs(lando, instancePath, options) {
165
+ debug('Will show lando logs on path:', instancePath, ' with options: ', options);
166
+ const app = await getLandoApplication(lando, instancePath);
167
+ const logTask = lando.tasks.find(task => task.command === 'logs');
168
+ await logTask.run({
169
+ follow: options.follow,
170
+ service: options.service,
171
+ timestamps: options.timestamps,
172
+ _app: app
173
+ });
174
+ }
163
175
  async function landoRebuild(lando, instancePath) {
164
176
  debug('Will rebuild lando app on path:', instancePath);
165
177
  const app = await getLandoApplication(lando, instancePath);
@@ -178,12 +190,14 @@ async function addHooks(app, lando) {
178
190
  debug('Registry ghcr.io is not resolvable, image pull might be broken.');
179
191
  registryResolvable = false;
180
192
  }
181
- data.opts.pull = registryResolvable && instanceData.pullAfter < Date.now();
182
- if (Array.isArray(data.opts.pullable) && Array.isArray(data.opts.local) && data.opts.local.length === 0 && !data.opts.pull) {
193
+ const pull = registryResolvable && (instanceData.pullAfter || 0) < Date.now();
194
+ if (Array.isArray(data.opts.pullable) && Array.isArray(data.opts.local) && data.opts.local.length === 0 && !pull) {
195
+ // Settigs `data.opts.pullable` to an empty array prevents Lando from pulling images with `docker pull`.
196
+ // Note that if some of the images are not available, they will still be pulled by `docker-compose`.
183
197
  data.opts.local = data.opts.pullable;
184
198
  data.opts.pullable = [];
185
199
  }
186
- if (data.opts.pull || !instanceData.pullAfter) {
200
+ if (pull || !instanceData.pullAfter) {
187
201
  instanceData.pullAfter = Date.now() + 7 * 24 * 60 * 60 * 1000;
188
202
  (0, _devEnvironmentCore.writeEnvironmentData)(app._name, instanceData);
189
203
  }
@@ -404,6 +418,21 @@ async function landoExec(lando, instancePath, toolName, args, options) {
404
418
  process.argv = savedArgv;
405
419
  }
406
420
  }
421
+ async function landoShell(lando, instancePath, service, user, command) {
422
+ const app = await getLandoApplication(lando, instancePath);
423
+ const shellTask = lando.tasks.find(task => task.command === 'ssh');
424
+ if (!command.length) {
425
+ const interactive = process.stdin.isTTY ? '-i' : '';
426
+ command = ['/bin/sh', '-c', `if [ -x /bin/bash ]; then /bin/bash ${interactive}; else /bin/sh ${interactive}; fi; exit 0`];
427
+ }
428
+ debug('Running command "%o" in service "%s" as user "%s"', command, service, user);
429
+ await shellTask.run({
430
+ command,
431
+ service,
432
+ user,
433
+ _app: app
434
+ });
435
+ }
407
436
 
408
437
  /**
409
438
  * Sometimes the proxy network seems to disapper leaving only orphant stopped proxy container.
@@ -445,10 +474,10 @@ async function validateDockerInstalled(lando) {
445
474
  }
446
475
  async function validateDockerAccess(lando) {
447
476
  const docker = lando.engine.docker;
448
- lando.log.verbose('Fetching docker info to verify user is in docker group');
477
+ lando.log.verbose('Fetching docker info to verify Docker connection');
449
478
  try {
450
479
  await docker.info();
451
480
  } catch (error) {
452
- throw Error('Failed to connect to docker. Please verify that the current user is part of docker group and has access to docker commands.');
481
+ throw Error('Failed to connect to Docker. Please verify that Docker engine (service) is running and follow the troubleshooting instructions for your platform.');
453
482
  }
454
483
  }