@automattic/vip 3.25.0 → 3.25.2-dev.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.
package/AGENTS.md CHANGED
@@ -72,6 +72,12 @@ Guide for future agents working on this codebase. Focus on traps, cross-cutting
72
72
  - `prepare` runs `npm run clean && npm run build`; npm package bins point to `dist/**`. Always rebuild before publishing so dist matches src.
73
73
  - `helpers/prepublishOnly.js` enforces branch `trunk` for `npm publish --tag latest` and optionally reruns `npm test`. Release flows expect a clean node version that satisfies `engines.node`.
74
74
 
75
+ ## Standalone SEA Packaging
76
+
77
+ - Canonical runbook for standalone executable build/signing is in `docs/SEA-BUILD-SIGNING.md`. Use it for macOS, Linux, Windows native, and WSL-mediated Windows builds.
78
+ - SEA builds are Node 22 only (enforced in `helpers/build-sea.js`); always verify `node -v` before `npm run build:sea`.
79
+ - The executable is self-contained for Node runtime + JS deps, but `dev-env` commands still require host Docker/Compose availability.
80
+
75
81
  ## Common Pitfalls Checklist
76
82
 
77
83
  - Running CLI without a token opens a browser (`open`) and waits for interactive input—pass `--help` or set `WPVIP_DEPLOY_TOKEN` in automation.
package/CLAUDE.md CHANGED
@@ -1,3 +1,4 @@
1
1
  # CLAUDE
2
2
 
3
3
  For guidance on working in this repo, traps, and migration notes, see `AGENTS.md`.
4
+ For standalone SEA build/signing by platform, see `docs/SEA-BUILD-SIGNING.md`.
@@ -218,6 +218,8 @@ services:
218
218
  environment:
219
219
  LANDO_NO_SCRIPTS: 1
220
220
  LANDO_NEEDS_EXEC: 1
221
+ LANDO_HOST_UID: ${LANDO_HOST_USER_ID}
222
+ LANDO_HOST_GID: ${LANDO_HOST_GROUP_ID}
221
223
  volumes:
222
224
  mu-plugins: {}
223
225
  initOnly: true
@@ -237,6 +239,9 @@ services:
237
239
  - clientcode_private:/clientcode/private
238
240
  - clientcode_themes:/clientcode/themes
239
241
  - clientcode_vipconfig:/clientcode/vip-config
242
+ environment:
243
+ LANDO_HOST_UID: ${LANDO_HOST_USER_ID}
244
+ LANDO_HOST_GID: ${LANDO_HOST_GROUP_ID}
240
245
  volumes:
241
246
  clientcode_clientmuPlugins: {}
242
247
  clientcode_images: {}
@@ -10,6 +10,23 @@ var _userError = _interopRequireDefault(require("../lib/user-error"));
10
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
11
  const exampleUsage = 'vip dev-env exec';
12
12
  const usage = 'vip dev-env exec';
13
+ const ENV_UP_CHECK_ATTEMPTS = 5;
14
+ const ENV_UP_CHECK_DELAY_MS = 1500;
15
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
16
+ async function waitForEnvironmentReadiness(lando, slug) {
17
+ const instancePath = (0, _devEnvironmentCore.getEnvironmentPath)(slug);
18
+ return pollEnvironmentReadiness(lando, instancePath, 1);
19
+ }
20
+ async function pollEnvironmentReadiness(lando, instancePath, attempt) {
21
+ if (await (0, _devEnvironmentLando.isEnvUp)(lando, instancePath)) {
22
+ return true;
23
+ }
24
+ if (attempt >= ENV_UP_CHECK_ATTEMPTS) {
25
+ return false;
26
+ }
27
+ await sleep(ENV_UP_CHECK_DELAY_MS);
28
+ return pollEnvironmentReadiness(lando, instancePath, attempt + 1);
29
+ }
13
30
  const examples = [{
14
31
  usage: `${exampleUsage} --slug=example-site -- wp post list`,
15
32
  description: 'Run a WP-CLI command against a local environment named "example-site".\n' + ' * A double dash ("--") must separate the arguments of "vip" from those of the "wp" command.'
@@ -46,7 +63,7 @@ const examples = [{
46
63
  arg = process.argv.slice(argSplitterIx + 1);
47
64
  }
48
65
  if (!opt.force) {
49
- const isUp = await (0, _devEnvironmentLando.isEnvUp)(lando, (0, _devEnvironmentCore.getEnvironmentPath)(slug));
66
+ const isUp = await waitForEnvironmentReadiness(lando, slug);
50
67
  if (!isUp) {
51
68
  throw new _userError.default('A WP-CLI command can only be executed on a running local environment.');
52
69
  }
@@ -4,6 +4,6 @@
4
4
  var _command = _interopRequireDefault(require("../lib/cli/command"));
5
5
  var _tracker = require("../lib/tracker");
6
6
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- (0, _command.default)().command('sql', 'Import a SQL database file to an environment.').command('validate-sql', 'Validate a local SQL database file prior to import.').command('validate-files', 'Validate the directory structure and contents of a local media file directory prior to archiving and uploading it to a publicly accessible URL.').command('media', 'Import media files to an environment from an archived file located at a publicly accessible URL.').example('vip @example-app.develop import sql example-file.sql', 'Import the local SQL database file "example-file.sql" to the develop environment.').example('vip @example-app.production import media https://www.example.com/uploads.tar.gz', 'Import an archived file from a publicly accessible URL to the production environment.').argv(process.argv, async () => {
7
+ (0, _command.default)().command('sql', 'Import a SQL database file to an environment.').command('validate-sql', 'Validate a local SQL database file prior to import.').command('validate-files', 'Validate that the directory structure and contents of a local media file directory can be successfully imported.').command('media', 'Import media files to a production environment from an archived file at a local path or a publicly accessible remote URL.').example('vip @example-app.develop import sql example-file.sql', 'Import the local SQL database file "example-file.sql" to the develop environment.').example('vip @example-app.production import media https://www.example.com/uploads.tar.gz', 'Import an archived file from a publicly accessible URL to the production environment.').argv(process.argv, async () => {
8
8
  await (0, _tracker.trackEvent)('vip_import_command_execute');
9
9
  });
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ var _seaDispatch = require("../lib/cli/sea-dispatch");
5
+ var _seaRuntime = require("../lib/cli/sea-runtime");
6
+ const run = async () => {
7
+ if ((0, _seaDispatch.isSeaRuntime)()) {
8
+ process.env.VIP_CLI_SEA_MODE = '1';
9
+ await (0, _seaRuntime.prepareSeaRuntimeFilesystem)();
10
+ const resolution = (0, _seaDispatch.resolveInternalBinFromArgv)(process.argv);
11
+ if (resolution.bin !== 'vip') {
12
+ process.env.VIP_CLI_TARGET_BIN = resolution.bin;
13
+ process.env.VIP_CLI_TARGET_START = String(resolution.start);
14
+ process.env.VIP_CLI_TARGET_LENGTH = String(resolution.length);
15
+ }
16
+ }
17
+ await import('./vip.js');
18
+ };
19
+ void run();
package/dist/bin/vip.js CHANGED
@@ -7,6 +7,8 @@ var _debug = _interopRequireDefault(require("debug"));
7
7
  var _enquirer = require("enquirer");
8
8
  var _command = _interopRequireWildcard(require("../lib/cli/command"));
9
9
  var _config = _interopRequireDefault(require("../lib/cli/config"));
10
+ var _internalBinLoader = require("../lib/cli/internal-bin-loader");
11
+ var _seaDispatch = require("../lib/cli/sea-dispatch");
10
12
  var _token = _interopRequireDefault(require("../lib/token"));
11
13
  var _tracker = require("../lib/tracker");
12
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -20,10 +22,28 @@ if (_config.default && _config.default.environment !== 'production') {
20
22
  // Config
21
23
  const tokenURL = 'https://dashboard.wpvip.com/me/cli/token';
22
24
  const customDeployToken = process.env.WPVIP_DEPLOY_TOKEN;
25
+ async function maybeExecuteSeaTargetCommand() {
26
+ const targetBin = process.env.VIP_CLI_TARGET_BIN;
27
+ if (!(0, _seaDispatch.isSeaRuntime)() || !targetBin || targetBin === 'vip') {
28
+ return false;
29
+ }
30
+ const start = Number(process.env.VIP_CLI_TARGET_START ?? '0');
31
+ const length = Number(process.env.VIP_CLI_TARGET_LENGTH ?? '0');
32
+ const resolution = {
33
+ start: Number.isInteger(start) ? start : 0,
34
+ length: Number.isInteger(length) ? length : 0
35
+ };
36
+ process.argv = (0, _seaDispatch.rewriteArgvForInternalBin)(process.argv, resolution);
37
+ const loaded = await (0, _internalBinLoader.loadInternalBin)(targetBin);
38
+ if (!loaded) {
39
+ throw new Error(`Unable to load SEA command target "${targetBin}"`);
40
+ }
41
+ return true;
42
+ }
23
43
  const runCmd = async function () {
24
44
  const cmd = (0, _command.default)();
25
45
  cmd.command('logout', 'Log out the current authenticated VIP-CLI user.').command('app', 'Interact with applications that the current authenticated VIP-CLI user has permission to access.').command('backup', 'Generate a backup of an environment.').command('cache', 'Manage page cache for an environment.').command('config', 'Manage environment configurations.').command('dev-env', 'Create and manage VIP Local Development Environments.').command('export', 'Export a copy of data associated with an environment.').command('import', 'Import media or SQL database files to an environment.').command('logs', 'Retrieve Runtime Logs from an environment.').command('search-replace', 'Search for a string in a local SQL file and replace it with a new string.').command('slowlogs', 'Retrieve MySQL slow query logs from an environment.').command('db', "Access an environment's database.").command('sync', 'Sync the database from production to a non-production environment.').command('whoami', 'Retrieve details about the current authenticated VIP-CLI user.').command('wp', 'Execute a WP-CLI command against an environment.');
26
- cmd.argv(process.argv);
46
+ await cmd.argv(process.argv);
27
47
  };
28
48
 
29
49
  /**
@@ -34,7 +54,89 @@ const runCmd = async function () {
34
54
  function doesArgvHaveAtLeastOneParam(argv, params) {
35
55
  return argv.some(arg => params.includes(arg));
36
56
  }
57
+ async function runLoginFlow() {
58
+ console.log();
59
+ console.log(' _ __ ________ ________ ____');
60
+ console.log(' | | / // _/ __ / ____/ / / _/');
61
+ console.log(' | | / / / // /_/ /______/ / / / / / ');
62
+ console.log(' | |/ /_/ // ____//_____/ /___/ /____/ / ');
63
+ console.log(' |___//___/_/ ____/_____/___/ ');
64
+ console.log();
65
+ console.log(' VIP-CLI is your tool for interacting with and managing your VIP applications.');
66
+ console.log();
67
+ console.log(' Authenticate your installation of VIP-CLI with your Personal Access Token. This URL will be opened in your web browser automatically so that you can retrieve your token: ' + tokenURL);
68
+ console.log();
69
+ await (0, _tracker.trackEvent)('login_command_execute');
70
+ const answer = await (0, _enquirer.prompt)({
71
+ type: 'confirm',
72
+ name: 'continue',
73
+ message: 'Ready to authenticate?'
74
+ });
75
+ if (!answer.continue) {
76
+ await (0, _tracker.trackEvent)('login_command_browser_cancelled');
77
+ return null;
78
+ }
79
+ const {
80
+ default: open
81
+ } = await import('open');
82
+ open(tokenURL, {
83
+ wait: false
84
+ });
85
+ await (0, _tracker.trackEvent)('login_command_browser_opened');
86
+ const {
87
+ token: tokenInput
88
+ } = await (0, _enquirer.prompt)({
89
+ type: 'password',
90
+ name: 'token',
91
+ message: 'Access Token:'
92
+ });
93
+ let token;
94
+ try {
95
+ token = new _token.default(tokenInput);
96
+ } catch (err) {
97
+ console.log('The token provided is malformed. Please check the token and try again.');
98
+ await (0, _tracker.trackEvent)('login_command_token_submit_error', {
99
+ error: err.message
100
+ });
101
+ return null;
102
+ }
103
+ if (token.expired()) {
104
+ console.log('The token provided is expired. Please log in again to refresh the token.');
105
+ await (0, _tracker.trackEvent)('login_command_token_submit_error', {
106
+ error: 'expired'
107
+ });
108
+ return null;
109
+ }
110
+ if (!token.valid()) {
111
+ console.log('The provided token is not valid. Please log in again to refresh the token.');
112
+ await (0, _tracker.trackEvent)('login_command_token_submit_error', {
113
+ error: 'invalid'
114
+ });
115
+ return null;
116
+ }
117
+ try {
118
+ await _token.default.set(token.raw);
119
+ } catch (err) {
120
+ await (0, _tracker.trackEvent)('login_command_token_submit_error', {
121
+ error: err.message
122
+ });
123
+ throw err;
124
+ }
125
+
126
+ // De-anonymize user for tracking
127
+ await (0, _tracker.aliasUser)(token.id);
128
+ await (0, _tracker.trackEvent)('login_command_token_submit_success');
129
+ return token;
130
+ }
37
131
  const rootCmd = async function () {
132
+ if ((0, _seaDispatch.isSeaRuntime)() && !process.env.VIP_CLI_TARGET_BIN) {
133
+ const resolution = (0, _seaDispatch.resolveInternalBinFromArgv)(process.argv);
134
+ if (resolution.bin !== 'vip') {
135
+ process.env.VIP_CLI_TARGET_BIN = resolution.bin;
136
+ process.env.VIP_CLI_TARGET_START = String(resolution.start);
137
+ process.env.VIP_CLI_TARGET_LENGTH = String(resolution.length);
138
+ }
139
+ }
38
140
  let token = await _token.default.get();
39
141
  const isHelpCommand = doesArgvHaveAtLeastOneParam(process.argv, ['help', '-h', '--help']);
40
142
  const isVersionCommand = doesArgvHaveAtLeastOneParam(process.argv, ['-v', '--version']);
@@ -44,82 +146,22 @@ const rootCmd = async function () {
44
146
  const isCustomDeployCmdWithKey = doesArgvHaveAtLeastOneParam(process.argv, ['deploy']) && Boolean(customDeployToken);
45
147
  debug('Argv:', process.argv);
46
148
  if (!isLoginCommand && (isLogoutCommand || isHelpCommand || isVersionCommand || isDevEnvCommandWithoutEnv || token?.valid() || isCustomDeployCmdWithKey)) {
47
- await runCmd();
48
- } else {
49
- console.log();
50
- console.log(' _ __ ________ ________ ____');
51
- console.log(' | | / // _/ __ / ____/ / / _/');
52
- console.log(' | | / / / // /_/ /______/ / / / / / ');
53
- console.log(' | |/ /_/ // ____//_____/ /___/ /____/ / ');
54
- console.log(' |___//___/_/ ____/_____/___/ ');
55
- console.log();
56
- console.log(' VIP-CLI is your tool for interacting with and managing your VIP applications.');
57
- console.log();
58
- console.log(' Authenticate your installation of VIP-CLI with your Personal Access Token. This URL will be opened in your web browser automatically so that you can retrieve your token: ' + tokenURL);
59
- console.log();
60
- await (0, _tracker.trackEvent)('login_command_execute');
61
- const answer = await (0, _enquirer.prompt)({
62
- type: 'confirm',
63
- name: 'continue',
64
- message: 'Ready to authenticate?'
65
- });
66
- if (!answer.continue) {
67
- await (0, _tracker.trackEvent)('login_command_browser_cancelled');
68
- return;
69
- }
70
- const {
71
- default: open
72
- } = await import('open');
73
- open(tokenURL, {
74
- wait: false
75
- });
76
- await (0, _tracker.trackEvent)('login_command_browser_opened');
77
- const {
78
- token: tokenInput
79
- } = await (0, _enquirer.prompt)({
80
- type: 'password',
81
- name: 'token',
82
- message: 'Access Token:'
83
- });
84
- try {
85
- token = new _token.default(tokenInput);
86
- } catch (err) {
87
- console.log('The token provided is malformed. Please check the token and try again.');
88
- await (0, _tracker.trackEvent)('login_command_token_submit_error', {
89
- error: err.message
90
- });
149
+ if (await maybeExecuteSeaTargetCommand()) {
91
150
  return;
92
151
  }
93
- if (token.expired()) {
94
- console.log('The token provided is expired. Please log in again to refresh the token.');
95
- await (0, _tracker.trackEvent)('login_command_token_submit_error', {
96
- error: 'expired'
97
- });
98
- return;
99
- }
100
- if (!token.valid()) {
101
- console.log('The provided token is not valid. Please log in again to refresh the token.');
102
- await (0, _tracker.trackEvent)('login_command_token_submit_error', {
103
- error: 'invalid'
104
- });
152
+ await runCmd();
153
+ } else {
154
+ token = await runLoginFlow();
155
+ if (!token) {
105
156
  return;
106
157
  }
107
- try {
108
- await _token.default.set(token.raw);
109
- } catch (err) {
110
- await (0, _tracker.trackEvent)('login_command_token_submit_error', {
111
- error: err.message
112
- });
113
- throw err;
114
- }
115
-
116
- // De-anonymize user for tracking
117
- await (0, _tracker.aliasUser)(token.id);
118
- await (0, _tracker.trackEvent)('login_command_token_submit_success');
119
158
  if (isLoginCommand) {
120
159
  console.log('You are now logged in - see `vip -h` for a list of available commands.');
121
160
  process.exit();
122
161
  }
162
+ if (await maybeExecuteSeaTargetCommand()) {
163
+ return;
164
+ }
123
165
  await runCmd();
124
166
  }
125
167
  };
@@ -115,7 +115,7 @@ class DevEnvImportSQLCommand {
115
115
  if (!dumpDetails.sourceDb) {
116
116
  console.log(`${_chalk.default.yellow('!')} Unable to identify source DB. Skipping myloader source-db option`);
117
117
  }
118
- importArg = ['db-myloader', '--overwrite-tables', ...(dumpDetails.sourceDb ? [`--source-db=${dumpDetails.sourceDb}`] : []), `--threads=${threadCount}`, '--max-threads-for-schema-creation=10', '--max-threads-for-index-creation=10', '--skip-triggers', '--skip-post', '--innodb-optimize-keys', '--checksum=SKIP', '--metadata-refresh-interval=2000000', '--stream'].concat(this.options.quiet ? ['--verbose=0'] : ['--verbose=3']);
118
+ importArg = ['db-myloader', '--overwrite-tables', ...(dumpDetails.sourceDb ? [`--source-db=${dumpDetails.sourceDb}`] : []), `--threads=${threadCount}`, '--max-threads-for-schema-creation=10', '--max-threads-for-index-creation=10', '--skip-triggers', '--skip-post', '--optimize-keys', '--checksum=SKIP', '--metadata-refresh-interval=2000000', '--stream'].concat(this.options.quiet ? ['--verbose=0'] : ['--verbose=3']);
119
119
  }
120
120
  return importArg;
121
121
  }