@automattic/vip 3.25.1 → 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 +6 -0
- package/CLAUDE.md +1 -0
- package/dist/bin/vip-dev-env-exec.js +18 -1
- package/dist/bin/vip-import.js +1 -1
- package/dist/bin/vip-sea.js +19 -0
- package/dist/bin/vip.js +111 -69
- package/dist/lib/cli/command.js +224 -53
- package/dist/lib/cli/config.js +13 -5
- package/dist/lib/cli/exit.js +2 -1
- package/dist/lib/cli/internal-bin-loader.js +81 -0
- package/dist/lib/cli/runtime-mode.js +21 -0
- package/dist/lib/cli/sea-dispatch.js +88 -0
- package/dist/lib/cli/sea-runtime.js +75 -0
- package/dist/lib/dev-environment/dev-environment-cli.js +9 -2
- package/dist/lib/dev-environment/dev-environment-core.js +62 -4
- package/dist/lib/dev-environment/dev-environment-lando.js +47 -10
- package/dist/lib/dev-environment/lando-loader.js +48 -0
- package/docs/COMMANDER-MIGRATION.md +55 -0
- package/docs/SEA-BUILD-SIGNING.md +171 -0
- package/helpers/build-sea.js +167 -0
- package/npm-shrinkwrap.json +499 -121
- package/package.json +6 -3
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
|
@@ -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 (
|
|
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
|
}
|
package/dist/bin/vip-import.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
};
|
package/dist/lib/cli/command.js
CHANGED
|
@@ -4,14 +4,18 @@ exports.__esModule = true;
|
|
|
4
4
|
exports.containsAppEnvArgument = containsAppEnvArgument;
|
|
5
5
|
exports.default = _default;
|
|
6
6
|
exports.getEnvIdentifier = getEnvIdentifier;
|
|
7
|
-
var _args = _interopRequireDefault(require("args"));
|
|
8
7
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
8
|
+
var _commander = require("commander");
|
|
9
9
|
var _debug = _interopRequireDefault(require("debug"));
|
|
10
10
|
var _enquirer = require("enquirer");
|
|
11
11
|
var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
|
|
12
|
+
var _nodeChild_process = require("node:child_process");
|
|
13
|
+
var _nodeFs = _interopRequireDefault(require("node:fs"));
|
|
14
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
12
15
|
var _envAlias = require("./envAlias");
|
|
13
16
|
var exit = _interopRequireWildcard(require("./exit"));
|
|
14
17
|
var _format = require("./format");
|
|
18
|
+
var _internalBinLoader = require("./internal-bin-loader");
|
|
15
19
|
var _prompt = require("./prompt");
|
|
16
20
|
var _package = _interopRequireDefault(require("../../../package.json"));
|
|
17
21
|
var _api = _interopRequireDefault(require("../../lib/api"));
|
|
@@ -37,15 +41,209 @@ process.on('uncaughtException', uncaughtError);
|
|
|
37
41
|
process.on('unhandledRejection', uncaughtError);
|
|
38
42
|
let _opts = {};
|
|
39
43
|
let alreadyConfirmedDebugAttachment = false;
|
|
44
|
+
const RESERVED_AUTO_SHORT_ALIASES = new Set(['h', 'v', 'd']);
|
|
45
|
+
function normalizeUsage(program, usage) {
|
|
46
|
+
if (!usage) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const [rootCommand, ...rest] = usage.trim().split(/\s+/);
|
|
50
|
+
if (rootCommand) {
|
|
51
|
+
program.name(rootCommand);
|
|
52
|
+
}
|
|
53
|
+
if (rest.length) {
|
|
54
|
+
const usageString = rest.join(' ');
|
|
55
|
+
program.usage(usageString.includes('[options]') ? usageString : `${usageString} [options]`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function createOptionDefinition(name, description, defaultValue, parseFn, usedShortNames) {
|
|
59
|
+
const isArray = Array.isArray(name);
|
|
60
|
+
const shortName = isArray ? name[0] : null;
|
|
61
|
+
const longName = isArray ? name[1] : name;
|
|
62
|
+
const normalizedLongName = String(longName).trim().replace(/^--?/, '');
|
|
63
|
+
const explicitShortName = shortName ? String(shortName).trim().replace(/^-/, '') : null;
|
|
64
|
+
let normalizedShortName = explicitShortName;
|
|
65
|
+
if (!normalizedShortName) {
|
|
66
|
+
const autoShortName = normalizedLongName.charAt(0);
|
|
67
|
+
const canUseAutoShortName = autoShortName && !RESERVED_AUTO_SHORT_ALIASES.has(autoShortName) && !usedShortNames.has(autoShortName);
|
|
68
|
+
if (canUseAutoShortName) {
|
|
69
|
+
normalizedShortName = autoShortName;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (normalizedShortName) {
|
|
73
|
+
usedShortNames.add(normalizedShortName);
|
|
74
|
+
}
|
|
75
|
+
const isBooleanOption = typeof defaultValue === 'boolean';
|
|
76
|
+
const usesOptionalValue = !isBooleanOption;
|
|
77
|
+
const parseOptionValue = value => {
|
|
78
|
+
if (parseFn) {
|
|
79
|
+
return parseFn(value);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
};
|
|
83
|
+
let parser;
|
|
84
|
+
if (usesOptionalValue) {
|
|
85
|
+
parser = (value, previousValue) => {
|
|
86
|
+
const parsedValue = parseOptionValue(value);
|
|
87
|
+
if (previousValue === undefined) {
|
|
88
|
+
return parsedValue;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(previousValue)) {
|
|
91
|
+
return [...previousValue, parsedValue];
|
|
92
|
+
}
|
|
93
|
+
return [previousValue, parsedValue];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
let flags = `--${normalizedLongName}`;
|
|
97
|
+
if (usesOptionalValue) {
|
|
98
|
+
flags += ' [value]';
|
|
99
|
+
}
|
|
100
|
+
if (normalizedShortName) {
|
|
101
|
+
flags = `-${normalizedShortName}, ${flags}`;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
flags,
|
|
105
|
+
description,
|
|
106
|
+
defaultValue,
|
|
107
|
+
parser
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
class CommanderArgsCompat {
|
|
111
|
+
constructor(opts) {
|
|
112
|
+
this.details = {
|
|
113
|
+
commands: []
|
|
114
|
+
};
|
|
115
|
+
this.sub = [];
|
|
116
|
+
this.examplesList = [];
|
|
117
|
+
this.usedShortNames = new Set();
|
|
118
|
+
this._opts = opts;
|
|
119
|
+
this.program = new _commander.Command();
|
|
120
|
+
this.program.allowUnknownOption(true);
|
|
121
|
+
this.program.allowExcessArguments(true);
|
|
122
|
+
this.program.helpOption(false);
|
|
123
|
+
normalizeUsage(this.program, this._opts.usage);
|
|
124
|
+
}
|
|
125
|
+
option(name, description, defaultValue, parseFn) {
|
|
126
|
+
const definition = createOptionDefinition(name, description, defaultValue, parseFn, this.usedShortNames);
|
|
127
|
+
const {
|
|
128
|
+
flags,
|
|
129
|
+
parser
|
|
130
|
+
} = definition;
|
|
131
|
+
if (parser && defaultValue !== undefined) {
|
|
132
|
+
this.program.option(flags, description, parser, defaultValue);
|
|
133
|
+
} else if (parser) {
|
|
134
|
+
this.program.option(flags, description, parser);
|
|
135
|
+
} else if (defaultValue !== undefined) {
|
|
136
|
+
this.program.option(flags, description, defaultValue);
|
|
137
|
+
} else {
|
|
138
|
+
this.program.option(flags, description);
|
|
139
|
+
}
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
command(name, description = '') {
|
|
143
|
+
this.details.commands.push({
|
|
144
|
+
usage: name,
|
|
145
|
+
description
|
|
146
|
+
});
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
example(usage, description) {
|
|
150
|
+
this.examplesList.push({
|
|
151
|
+
usage,
|
|
152
|
+
description
|
|
153
|
+
});
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
examples(examples = []) {
|
|
157
|
+
for (const example of examples) {
|
|
158
|
+
this.example(example.usage, example.description);
|
|
159
|
+
}
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
showVersion() {
|
|
163
|
+
console.log(_package.default.version);
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
showHelp() {
|
|
167
|
+
const lines = [this.program.helpInformation().trimEnd()];
|
|
168
|
+
if (this.details.commands.length) {
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push('Commands:');
|
|
171
|
+
for (const entry of this.details.commands) {
|
|
172
|
+
const commandName = entry.usage.padEnd(26, ' ');
|
|
173
|
+
lines.push(` ${commandName}${entry.description}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (this.examplesList.length) {
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push('Examples:');
|
|
179
|
+
for (const example of this.examplesList) {
|
|
180
|
+
lines.push(` - ${example.description}`);
|
|
181
|
+
lines.push(` $ ${example.usage}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
console.log(lines.join('\n'));
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
isDefined(value, key) {
|
|
188
|
+
if (key !== 'commands') {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return this.details.commands.some(entry => entry.usage === value);
|
|
192
|
+
}
|
|
193
|
+
parse(argv) {
|
|
194
|
+
this.program.parse(argv, {
|
|
195
|
+
from: 'node'
|
|
196
|
+
});
|
|
197
|
+
this.sub = this.program.args.slice();
|
|
198
|
+
return this.program.opts();
|
|
199
|
+
}
|
|
200
|
+
async executeSubcommand(argv, parsedAlias, subcommand) {
|
|
201
|
+
const currentScript = argv[1];
|
|
202
|
+
const extension = _nodePath.default.extname(currentScript);
|
|
203
|
+
const baseScriptPath = extension ? currentScript.slice(0, -extension.length) : currentScript;
|
|
204
|
+
const childScriptPath = extension ? `${baseScriptPath}-${subcommand}${extension}` : `${baseScriptPath}-${subcommand}`;
|
|
205
|
+
const aliasFromRawArgv = argv.slice(2).find(arg => (0, _envAlias.isAlias)(arg));
|
|
206
|
+
const subcommandIndex = parsedAlias.argv.findIndex((arg, index) => {
|
|
207
|
+
return index > 1 && arg === subcommand;
|
|
208
|
+
});
|
|
209
|
+
let childArgs = subcommandIndex > -1 ? parsedAlias.argv.slice(subcommandIndex + 1) : [];
|
|
210
|
+
if (aliasFromRawArgv) {
|
|
211
|
+
childArgs = [aliasFromRawArgv, ...childArgs];
|
|
212
|
+
}
|
|
213
|
+
let runResult;
|
|
214
|
+
if (_nodeFs.default.existsSync(childScriptPath)) {
|
|
215
|
+
runResult = (0, _nodeChild_process.spawnSync)(process.execPath, [childScriptPath, ...childArgs], {
|
|
216
|
+
stdio: 'inherit',
|
|
217
|
+
env: process.env
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
const fallbackCommand = `${_nodePath.default.basename(baseScriptPath)}-${subcommand}`;
|
|
221
|
+
if (process.env.VIP_CLI_SEA_MODE === '1' && (0, _internalBinLoader.hasInternalBin)(fallbackCommand)) {
|
|
222
|
+
process.argv = [process.argv[0], process.argv[1], ...childArgs];
|
|
223
|
+
const loaded = await (0, _internalBinLoader.loadInternalBin)(fallbackCommand);
|
|
224
|
+
if (!loaded) {
|
|
225
|
+
throw new Error(`Unable to load SEA subcommand "${fallbackCommand}"`);
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
runResult = (0, _nodeChild_process.spawnSync)(fallbackCommand, childArgs, {
|
|
230
|
+
stdio: 'inherit',
|
|
231
|
+
env: process.env,
|
|
232
|
+
shell: process.platform === 'win32'
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (runResult.error) {
|
|
236
|
+
throw runResult.error;
|
|
237
|
+
}
|
|
238
|
+
process.exit(runResult.status ?? 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
40
241
|
|
|
41
242
|
/**
|
|
42
243
|
* @param {string[]} argv
|
|
43
244
|
*/
|
|
44
245
|
// eslint-disable-next-line complexity
|
|
45
|
-
|
|
46
|
-
if (process.platform !== 'win32' && argv[1]?.endsWith('.js')) {
|
|
47
|
-
argv[1] = argv[1].slice(0, -3);
|
|
48
|
-
}
|
|
246
|
+
CommanderArgsCompat.prototype.argv = async function (argv, cb) {
|
|
49
247
|
if (process.execArgv.includes('--inspect') && !alreadyConfirmedDebugAttachment) {
|
|
50
248
|
await (0, _enquirer.prompt)({
|
|
51
249
|
type: 'confirm',
|
|
@@ -55,25 +253,13 @@ _args.default.argv = async function (argv, cb) {
|
|
|
55
253
|
alreadyConfirmedDebugAttachment = true;
|
|
56
254
|
}
|
|
57
255
|
const parsedAlias = (0, _envAlias.parseEnvAliasFromArgv)(argv);
|
|
256
|
+
const options = this.parse(parsedAlias.argv);
|
|
58
257
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// We can pass "vip command subcommand" to the name param for more accurate
|
|
65
|
-
// usage text:
|
|
66
|
-
// Usage: vip command subcommand [options]
|
|
67
|
-
//
|
|
68
|
-
// It also allows us to represent required args in usage text:
|
|
69
|
-
// Usage: vip command subcommand <arg1> <arg2> [options]
|
|
70
|
-
const name = _opts.usage || null;
|
|
71
|
-
const options = this.parse(parsedAlias.argv, {
|
|
72
|
-
help: false,
|
|
73
|
-
name,
|
|
74
|
-
version: false,
|
|
75
|
-
debug: false
|
|
76
|
-
});
|
|
258
|
+
// If there's a sub-command, run that instead
|
|
259
|
+
if (this.isDefined(this.sub[0], 'commands')) {
|
|
260
|
+
await this.executeSubcommand(argv, parsedAlias, this.sub[0]);
|
|
261
|
+
return {};
|
|
262
|
+
}
|
|
77
263
|
if (_opts.format && !options.format) {
|
|
78
264
|
options.format = 'table';
|
|
79
265
|
}
|
|
@@ -105,12 +291,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
105
291
|
});
|
|
106
292
|
exit.withError(error);
|
|
107
293
|
}
|
|
108
|
-
|
|
109
|
-
// If there's a sub-command, run that instead
|
|
110
|
-
if (this.isDefined(this.sub[0], 'commands')) {
|
|
111
|
-
return {};
|
|
112
|
-
}
|
|
113
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
294
|
+
if (process.env.NODE_ENV !== 'test' && process.env.VIP_CLI_SEA_MODE !== '1') {
|
|
114
295
|
const {
|
|
115
296
|
default: updateNotifier
|
|
116
297
|
} = await import('update-notifier');
|
|
@@ -121,18 +302,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
121
302
|
isGlobal: true
|
|
122
303
|
});
|
|
123
304
|
}
|
|
124
|
-
|
|
125
|
-
// `help` and `version` are always defined as subcommands
|
|
126
|
-
const customCommands = this.details.commands.filter(command => {
|
|
127
|
-
switch (command.usage) {
|
|
128
|
-
case 'help':
|
|
129
|
-
case 'version':
|
|
130
|
-
case 'debug':
|
|
131
|
-
return false;
|
|
132
|
-
default:
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
});
|
|
305
|
+
const customCommands = this.details.commands;
|
|
136
306
|
|
|
137
307
|
// Show help if no args passed
|
|
138
308
|
if (Boolean(customCommands.length) && !this.sub.length) {
|
|
@@ -152,7 +322,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
152
322
|
|
|
153
323
|
// Show help if subcommand is invalid
|
|
154
324
|
const subCommands = this.details.commands.map(cmd => cmd.usage);
|
|
155
|
-
if (!_opts.wildcardCommand && this.sub[_opts.requiredArgs] && 0 > subCommands.indexOf(this.sub[_opts.requiredArgs])) {
|
|
325
|
+
if (!_opts.wildcardCommand && this.sub[_opts.requiredArgs] && subCommands.length && 0 > subCommands.indexOf(this.sub[_opts.requiredArgs])) {
|
|
156
326
|
const subcommand = this.sub.join(' ');
|
|
157
327
|
await (0, _tracker.trackEvent)('command_validation_error', {
|
|
158
328
|
error: `Invalid subcommand: ${subcommand}`
|
|
@@ -368,7 +538,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
368
538
|
value: `${_chalk.default.cyan(launched)}`
|
|
369
539
|
});
|
|
370
540
|
}
|
|
371
|
-
if (this.sub) {
|
|
541
|
+
if (this.sub.length) {
|
|
372
542
|
info.push({
|
|
373
543
|
key: 'SQL File',
|
|
374
544
|
value: `${_chalk.default.blueBright(this.sub)}`
|
|
@@ -441,7 +611,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
441
611
|
}
|
|
442
612
|
case 'import-media':
|
|
443
613
|
{
|
|
444
|
-
const isUrl = this.sub && (String(this.sub).startsWith('http://') || String(this.sub).startsWith('https://'));
|
|
614
|
+
const isUrl = this.sub.length && (String(this.sub).startsWith('http://') || String(this.sub).startsWith('https://'));
|
|
445
615
|
const archiveLabel = isUrl ? 'Archive URL' : 'Archive Path';
|
|
446
616
|
info.push({
|
|
447
617
|
key: archiveLabel,
|
|
@@ -536,7 +706,7 @@ function validateOpts(opts) {
|
|
|
536
706
|
}
|
|
537
707
|
|
|
538
708
|
/**
|
|
539
|
-
* @returns {
|
|
709
|
+
* @returns {CommanderArgsCompat}
|
|
540
710
|
*/
|
|
541
711
|
function _default(opts) {
|
|
542
712
|
_opts = {
|
|
@@ -550,24 +720,25 @@ function _default(opts) {
|
|
|
550
720
|
wildcardCommand: false,
|
|
551
721
|
...opts
|
|
552
722
|
};
|
|
723
|
+
const args = new CommanderArgsCompat(_opts);
|
|
553
724
|
if (_opts.appContext || _opts.requireConfirm) {
|
|
554
|
-
|
|
725
|
+
args.option('app', 'Target an application. Accepts a string value for the application name or an integer for the application ID.');
|
|
555
726
|
}
|
|
556
727
|
if (_opts.envContext || _opts.childEnvContext) {
|
|
557
|
-
|
|
728
|
+
args.option('env', 'Target an environment. Accepts a string value for the environment type.');
|
|
558
729
|
}
|
|
559
730
|
if (_opts.requireConfirm) {
|
|
560
|
-
|
|
731
|
+
args.option('force', 'Skip confirmation.', false);
|
|
561
732
|
}
|
|
562
733
|
if (_opts.format) {
|
|
563
|
-
|
|
734
|
+
args.option('format', 'Render output in a particular format. Accepts “table“ (default), “csv“, and “json“.');
|
|
564
735
|
}
|
|
565
736
|
|
|
566
737
|
// Add help and version to all subcommands
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
return
|
|
738
|
+
args.option(['h', 'help'], 'Retrieve a description, examples, and available options for a (sub)command.', false);
|
|
739
|
+
args.option(['v', 'version'], 'Retrieve the version number of VIP-CLI currently installed on the local machine.', false);
|
|
740
|
+
args.option(['d', 'debug'], 'Generate verbose output during command execution to help identify or fix errors or bugs.');
|
|
741
|
+
return args;
|
|
571
742
|
}
|
|
572
743
|
function getEnvIdentifier(env) {
|
|
573
744
|
let identifier = env.type;
|