@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.
@@ -35,6 +35,8 @@ services:
35
35
  image: <%= php %>
36
36
  command: run.sh
37
37
  working_dir: /wp
38
+ env_file:
39
+ - .env
38
40
  environment:
39
41
  XDEBUG: <%= xdebug ? 'enable' : 'disable' %>
40
42
  <% if ( xdebugConfig ) { %>
@@ -81,4 +81,4 @@ async function deleteEnvVarCommand(arg, opt) {
81
81
  envContext: true,
82
82
  requiredArgs: 1,
83
83
  usage: `${baseUsage} <VARIABLE_NAME>`
84
- }).examples(examples).option('skip-confirmation', 'Skip manual confirmation of input (USE WITH CAUTION)', false).argv(process.argv, deleteEnvVarCommand);
84
+ }).examples(examples).option('skip-confirmation', 'Skip the confirmation prompt (USE WITH CAUTION).', false).argv(process.argv, deleteEnvVarCommand);
@@ -100,4 +100,4 @@ async function setEnvVarCommand(arg, opt) {
100
100
  envContext: true,
101
101
  requiredArgs: 1,
102
102
  usage: `${baseUsage} <VARIABLE_NAME>`
103
- }).option('from-file', 'Read environment variable value from file (useful for multiline input)').option('skip-confirmation', 'Skip manual confirmation of input (USE WITH CAUTION)', false).examples(examples).argv(process.argv, setEnvVarCommand);
103
+ }).option('from-file', 'Read environment variable value from a UTF-8-encoded text file (useful for multiline input). Accepts a relative or absolute path.').option('skip-confirmation', 'Skip the confirmation prompt (USE WITH CAUTION).', false).examples(examples).argv(process.argv, setEnvVarCommand);
@@ -8,8 +8,8 @@ const exampleUsage = 'vip @example-app.develop config envvar';
8
8
 
9
9
  // Command examples
10
10
  const examples = [{
11
- usage: `${exampleUsage} delete MY_VARIABLE`,
12
- description: 'Delete the environment variable "MY_VARIABLE" from the environment.'
11
+ usage: `${exampleUsage} set MY_VARIABLE`,
12
+ description: 'Add or update the environment variable "MY_VARIABLE" and assign its value at the prompt.'
13
13
  }, {
14
14
  usage: `${exampleUsage} get MY_VARIABLE`,
15
15
  description: 'Retrieve the value of the environment variable "MY_VARIABLE".'
@@ -20,8 +20,8 @@ const examples = [{
20
20
  usage: `${exampleUsage} list`,
21
21
  description: 'List the names of all environment variables.'
22
22
  }, {
23
- usage: `${exampleUsage} set MY_VARIABLE`,
24
- description: 'Add or update the environment variable "MY_VARIABLE" and assign its value at the prompt.'
23
+ usage: `${exampleUsage} delete MY_VARIABLE`,
24
+ description: 'Delete the environment variable "MY_VARIABLE" from the environment.'
25
25
  }];
26
26
  (0, _command.default)({
27
27
  requiredArgs: 0,
@@ -69,10 +69,13 @@ cmd.argv(process.argv, async (arg, opt) => {
69
69
  let defaultOptions = {};
70
70
  /** @type {Record<string,import('../lib/dev-environment/types').IntegrationConfig>} */
71
71
  let integrationsConfig = {};
72
+ /** @type {Record<string,string>} */
73
+ let envVars = {};
72
74
  try {
73
75
  if (opt.app) {
74
76
  const appInfo = await (0, _devEnvironmentCore.getApplicationInformation)(opt.app, opt.env);
75
77
  integrationsConfig = appInfo.environment?.integrations ?? {};
78
+ envVars = appInfo.environment?.envVars ?? {};
76
79
  defaultOptions = (0, _devEnvironmentCli.getOptionsFromAppInfo)(appInfo);
77
80
  }
78
81
  } catch (error) {
@@ -90,7 +93,7 @@ cmd.argv(process.argv, async (arg, opt) => {
90
93
  const instanceData = await (0, _devEnvironmentCli.promptForArguments)(preselectedOptions, defaultOptions, suppressPrompts, true);
91
94
  instanceData.siteSlug = slug;
92
95
  try {
93
- await (0, _devEnvironmentCore.createEnvironment)(lando, instanceData, integrationsConfig);
96
+ await (0, _devEnvironmentCore.createEnvironment)(lando, instanceData, integrationsConfig, envVars);
94
97
  await (0, _devEnvironmentCore.printEnvironmentInfo)(lando, slug, {
95
98
  extended: false,
96
99
  suppressWarnings: true
@@ -21,17 +21,24 @@ const examples = [{
21
21
  usage: `${exampleUsage} --skip-rebuild --slug=example-site`,
22
22
  description: 'Start only the services of a local environment that are not currently in a running state.'
23
23
  }, {
24
- usage: `${exampleUsage} --vscode --slug=example-site`,
24
+ usage: `${exampleUsage} --editor=vscode --slug=example-site`,
25
25
  description: 'Start a local environment and generate a Workspace file for developing in Visual Studio Code.'
26
+ }, {
27
+ usage: `${exampleUsage} --editor=cursor --slug=example-site`,
28
+ description: 'Start a local environment and generate a Workspace file for developing in Cursor Editor.'
29
+ }, {
30
+ usage: `${exampleUsage} --editor=phpstorm --slug=example-site`,
31
+ description: 'Start a local environment and generate a Workspace file for developing in PhpStorm.'
26
32
  }];
27
33
  (0, _command.default)({
28
34
  usage
29
- }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('skip-rebuild', 'Only start services that are not in a running state.').option(['w', 'skip-wp-versions-check'], 'Skip the prompt to update WordPress; occurs if the last major release version is not configured.').option('vscode', 'Generate a Visual Studio Code Workspace file.').examples(examples).argv(process.argv, async (arg, opt) => {
35
+ }).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('skip-rebuild', 'Only start services that are not in a running state.').option(['w', 'skip-wp-versions-check'], 'Skip the prompt to update WordPress; occurs if the last major release version is not configured.').option('vscode', 'Generate a Visual Studio Code Workspace file (deprecated, use --editor=vscode instead).').option('editor', 'Generate a workspace file for the specified editor (supports: vscode, cursor, windsurf, phpstorm).').examples(examples).argv(process.argv, async (arg, opt) => {
30
36
  const slug = await (0, _devEnvironmentCli.getEnvironmentName)(opt);
31
37
  const lando = await (0, _devEnvironmentLando.bootstrapLando)();
32
38
  (0, _devEnvironmentCli.validateDependencies)(lando);
33
39
  const startProcessing = new Date();
34
40
  const trackingInfo = (0, _devEnvironmentCli.getEnvTrackingInfo)(slug);
41
+ trackingInfo.editor = opt.editor || (opt.vscode ? 'vscode' : undefined);
35
42
  trackingInfo.vscode = Boolean(opt.vscode);
36
43
  trackingInfo.docker = lando.config.versions.engine;
37
44
  trackingInfo.docker_compose = lando.config.versions.compose;
@@ -55,6 +62,7 @@ const examples = [{
55
62
  process.exitCode = 1;
56
63
  }
57
64
  (0, _devEnvironmentCli.postStart)(slug, {
58
- openVSCode: Boolean(opt.vscode)
65
+ editor: opt.editor,
66
+ vscode: Boolean(opt.vscode)
59
67
  });
60
68
  });
@@ -3,6 +3,10 @@
3
3
 
4
4
  var _command = _interopRequireDefault(require("../lib/cli/command"));
5
5
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
6
+ if (process.getuid?.() === 0) {
7
+ console.error('This script should not be run as root. Exiting.');
8
+ process.exit(1);
9
+ }
6
10
  (0, _command.default)({
7
11
  requiredArgs: 0
8
12
  }).command('create', 'Create a new local environment.').command('update', 'Update the settings of a local environment.').command('start', 'Start a local environment.').command('stop', 'Stop a local environment.').command('destroy', 'Remove a local environment.').command('info', 'Retrieve information about a local environment.').command('list', 'Retrieve information about all local environments.').command('exec', 'Run a WP-CLI command against a local environment.').command('import', 'Import media or database files to a local environment.').command('shell', 'Create a shell and run commands against a local environment.').command('logs', 'Retrieve logs for a local environment.').command('sync', 'Sync the database of a VIP Platform environment to a local environment.').command('purge', 'Remove all local environments.').argv(process.argv);
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ var _singleLineLog = require("@wwa/single-line-log");
4
5
  var _chalk = _interopRequireDefault(require("chalk"));
5
6
  var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
6
- var _singleLineLog = require("single-line-log");
7
7
  var _api = _interopRequireDefault(require("../lib/api"));
8
8
  var _app2 = _interopRequireDefault(require("../lib/api/app"));
9
9
  var _command = _interopRequireDefault(require("../lib/cli/command"));
@@ -8,6 +8,7 @@ var _readline = _interopRequireDefault(require("readline"));
8
8
  var _socket = _interopRequireDefault(require("socket.io-client"));
9
9
  var _socket2 = _interopRequireDefault(require("socket.io-stream"));
10
10
  var _stream = require("stream");
11
+ var _wpSsh = require("../commands/wp-ssh");
11
12
  var _api = _interopRequireWildcard(require("../lib/api"));
12
13
  var _command = _interopRequireWildcard(require("../lib/cli/command"));
13
14
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
@@ -30,6 +31,7 @@ const appQuery = `id, name,
30
31
  appId
31
32
  type
32
33
  name
34
+ wpcliStrategy
33
35
  primaryDomain {
34
36
  name
35
37
  }
@@ -330,6 +332,13 @@ const examples = [{
330
332
  (0, _api.disableGlobalGraphQLErrorHandling)();
331
333
  const promptIdentifier = `${appName}.${(0, _command.getEnvIdentifier)(opts.env)}`;
332
334
  let countSIGINT = 0;
335
+ if (opts.env.wpcliStrategy === 'ssh') {
336
+ const wpCommandRunner = new _wpSsh.WPCliCommandOverSSH(opts.app, opts.env);
337
+ await wpCommandRunner.run(cmd, {
338
+ command: commandForAnalytics
339
+ });
340
+ return;
341
+ }
333
342
  const mutableStdout = new _stream.Writable({
334
343
  write(chunk, encoding, callback) {
335
344
  if (!this.muted) {
@@ -67,8 +67,9 @@ async function extractSiteUrls(sqlFile) {
67
67
  return new Promise((resolve, reject) => {
68
68
  const urls = new Set();
69
69
  readInterface.on('line', line => {
70
- const url = findSiteHomeUrl(line);
70
+ let url = findSiteHomeUrl(line);
71
71
  if (url) {
72
+ url = url.replace(/\/$/, '');
72
73
  urls.add(url);
73
74
  }
74
75
  });
@@ -178,7 +179,7 @@ class DevEnvSyncSQLCommand {
178
179
  if (!site?.blogId || site.blogId === 1) continue;
179
180
  const url = site?.homeUrl;
180
181
  if (!url) continue;
181
- const strippedUrl = stripProtocol(url);
182
+ const strippedUrl = stripProtocol(url).replace(/\/$/, '');
182
183
  if (!this.searchReplaceMap[strippedUrl]) continue;
183
184
  const domain = new URL(url).hostname;
184
185
  const newDomain = primaryDomain === domain ? this.landoDomain : `${this.slugifyDomain(domain)}.${this.landoDomain}`;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.WPCliCommandOverSSH = exports.NonZeroExitCodeError = void 0;
5
+ var _chalk = _interopRequireDefault(require("chalk"));
6
+ var _debug = _interopRequireDefault(require("debug"));
7
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
8
+ var ssh2 = _interopRequireWildcard(require("ssh2"));
9
+ var _package = _interopRequireDefault(require("../../package.json"));
10
+ var _api = _interopRequireDefault(require("../lib/api"));
11
+ var _tracker = require("../lib/tracker");
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ /**
16
+ * External dependencies
17
+ */
18
+
19
+ /**
20
+ * Internal dependencies
21
+ */
22
+
23
+ const debug = (0, _debug.default)('@automattic/vip:wp/ssh');
24
+ const NON_TTY_COLUMNS = 100;
25
+ const NON_TTY_ROWS = 15;
26
+ const SSH_HANDSHAKE_TIMEOUT_MS = 5000;
27
+ const TRIGGER_WP_CLI_COMMAND_MUTATION = (0, _graphqlTag.default)`
28
+ mutation TriggerWPCLICommandMutation($input: AppEnvironmentTriggerWPCLICommandInput) {
29
+ triggerWPCLICommandOnAppEnvironment(input: $input) {
30
+ inputToken
31
+ sshAuthentication {
32
+ host
33
+ port
34
+ username
35
+ privateKey
36
+ passphrase
37
+ }
38
+ command {
39
+ guid
40
+ }
41
+ }
42
+ }
43
+ `;
44
+ class NonZeroExitCodeError extends Error {
45
+ exitCode;
46
+ constructor(message, exitCode) {
47
+ super(message);
48
+ this.exitCode = exitCode;
49
+ }
50
+ }
51
+ exports.NonZeroExitCodeError = NonZeroExitCodeError;
52
+ class WPCliCommandOverSSH {
53
+ app;
54
+ env;
55
+ track;
56
+ constructor(app, env) {
57
+ this.app = app;
58
+ this.env = env;
59
+ this.track = (0, _tracker.makeCommandTracker)('wp', {
60
+ app: this.app.id,
61
+ env: this.env.id,
62
+ execution_type: 'ssh'
63
+ });
64
+ }
65
+ async run(command, extraTrackingInfo = {}) {
66
+ if (!this.app.id || !this.env.id) {
67
+ await this.track('error', {
68
+ error: 'no_app_env_id',
69
+ message: 'No app or env ID provided',
70
+ ...extraTrackingInfo
71
+ });
72
+ throw new Error('No app ID or environment ID provided');
73
+ }
74
+ debug("Requesting SSH authentication for command '%s'", command);
75
+ const sshAuth = await this.getSSHAuthForCommand(command, extraTrackingInfo);
76
+ const data = sshAuth.data?.triggerWPCLICommandOnAppEnvironment;
77
+ if (!data) {
78
+ await this.track('error', {
79
+ error: 'no_ssh_auth_data',
80
+ message: 'No SSH authentication data received',
81
+ ...extraTrackingInfo
82
+ });
83
+ throw new Error('WP-CLI SSH Authentication failed');
84
+ }
85
+ debug('Connecting to SSH', {
86
+ host: data.sshAuthentication.host,
87
+ port: data.sshAuthentication.port,
88
+ username: data.sshAuthentication.username,
89
+ guid: data.command.guid
90
+ });
91
+ try {
92
+ await this.executeCommandOverSSH({
93
+ host: data.sshAuthentication.host,
94
+ port: data.sshAuthentication.port,
95
+ username: data.sshAuthentication.username,
96
+ privateKey: data.sshAuthentication.privateKey,
97
+ passphrase: data.sshAuthentication.passphrase,
98
+ guid: data.command.guid,
99
+ inputToken: data.inputToken
100
+ });
101
+ await this.track('success', {
102
+ guid: data?.command.guid
103
+ });
104
+ } catch (err) {
105
+ if (err instanceof NonZeroExitCodeError) {
106
+ await this.track('error', {
107
+ guid: data?.command.guid,
108
+ error: 'non_zero_exit_code',
109
+ message: `Command failed with exit code ${err.exitCode}`,
110
+ ...extraTrackingInfo
111
+ });
112
+ process.exit(err.exitCode);
113
+ }
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ console.error(`${_chalk.default.red('Error:')} ${message}`);
116
+ await this.track('error', {
117
+ guid: data?.command.guid,
118
+ error: 'ssh_command_failed',
119
+ message: 'Error executing command over SSH',
120
+ ...extraTrackingInfo
121
+ });
122
+ process.exit(1);
123
+ }
124
+ }
125
+ async executeCommandOverSSH({
126
+ host,
127
+ port,
128
+ username,
129
+ privateKey,
130
+ passphrase,
131
+ guid,
132
+ inputToken
133
+ }) {
134
+ return new Promise((resolve, reject) => {
135
+ const conn = new ssh2.Client();
136
+ const columns = process.stdout.columns || NON_TTY_COLUMNS;
137
+ const rows = process.stdout.rows || NON_TTY_ROWS;
138
+ const isTTY = process.stdout.isTTY ? 'true' : 'false';
139
+ let commandStarted = false;
140
+ conn.on('ready', () => {
141
+ conn.exec(`GUID=${guid} INPUT_TOKEN=${inputToken} VERSION=${_package.default.version} ROWS=${rows} COLUMNS=${columns} TTY=${isTTY}`, (err, stream) => {
142
+ if (err) {
143
+ reject(err);
144
+ return;
145
+ }
146
+ stream.on('exit', exitCode => {
147
+ if (exitCode !== 0) {
148
+ reject(new NonZeroExitCodeError(`Command failed`, exitCode));
149
+ }
150
+ });
151
+ commandStarted = true;
152
+ const handleSIGINT = () => {
153
+ process.removeListener('SIGINT', handleSIGINT);
154
+ console.log('SIGINT received. Canceling command...');
155
+ stream.end('\x03');
156
+ };
157
+ process.on('SIGINT', handleSIGINT);
158
+ const handleSIGTERM = () => {
159
+ process.removeListener('SIGTERM', handleSIGTERM);
160
+ console.log('SIGTERM received. Canceling command...');
161
+ stream.end('\x1F');
162
+ };
163
+ process.on('SIGTERM', handleSIGTERM);
164
+ stream.pipe(process.stdout);
165
+ process.stdin.pipe(stream);
166
+ stream.on('close', () => {
167
+ process.removeListener('SIGINT', handleSIGINT);
168
+ process.removeListener('SIGTERM', handleSIGTERM);
169
+ conn.end();
170
+ resolve();
171
+ });
172
+ });
173
+ }).on('error', err => {
174
+ reject(err);
175
+ }).on('close', () => {
176
+ if (!commandStarted) {
177
+ reject(new Error('Connection closed before command started'));
178
+ }
179
+ }).connect({
180
+ host,
181
+ port,
182
+ username,
183
+ privateKey,
184
+ passphrase,
185
+ readyTimeout: SSH_HANDSHAKE_TIMEOUT_MS
186
+ });
187
+ });
188
+ }
189
+ async getSSHAuthForCommand(command, extraTrackingInfo) {
190
+ const api = (0, _api.default)();
191
+ try {
192
+ return api.mutate({
193
+ mutation: TRIGGER_WP_CLI_COMMAND_MUTATION,
194
+ variables: {
195
+ input: {
196
+ id: this.app.id,
197
+ environmentId: this.env.id,
198
+ command
199
+ }
200
+ }
201
+ });
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ await this.track('error', {
205
+ error: 'trigger_failed',
206
+ message,
207
+ ...extraTrackingInfo
208
+ });
209
+ throw new Error(`Unable to trigger the WP-CLI command`);
210
+ }
211
+ }
212
+ }
213
+ exports.WPCliCommandOverSSH = WPCliCommandOverSSH;
@@ -2,8 +2,8 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.StepStatus = exports.ProgressTracker = void 0;
5
+ var _singleLineLog = require("@wwa/single-line-log");
5
6
  var _nodeOs = require("node:os");
6
- var _singleLineLog = require("single-line-log");
7
7
  var _format = require("../../lib/cli/format");
8
8
  const PRINT_INTERVAL = process.env.DEBUG ? 5000 : 200; // How often the report is printed. Mainly affects the "spinner" animation.
9
9
  let StepStatus = exports.StepStatus = /*#__PURE__*/function (StepStatus) {