@automattic/vip 4.0.3 → 4.0.5
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 +2 -0
- package/dist/bin/vip-app-deploy-token-generate.js +102 -0
- package/dist/bin/vip-app-deploy-token.js +20 -0
- package/dist/bin/vip-app-deploy.js +7 -1
- package/dist/bin/vip-app.js +1 -1
- package/dist/bin/vip-dev-env-update.js +1 -5
- package/dist/commands/dev-env-import-sql.js +4 -1
- package/dist/commands/dev-env-sync-sql.js +3 -4
- package/dist/lib/analytics/clients/tracks.js +2 -2
- package/dist/lib/api/http.js +19 -14
- package/dist/lib/api.js +1 -2
- package/dist/lib/cli/command.js +37 -1
- package/dist/lib/cli/internal-bin-loader.js +2 -0
- package/dist/lib/client-file-uploader.js +33 -14
- package/dist/lib/custom-deploy/custom-deploy.js +37 -0
- package/dist/lib/dev-environment/dev-environment-core.js +5 -5
- package/dist/lib/dev-environment/dev-environment-lando.js +7 -3
- package/dist/lib/http/download-file.js +6 -1
- package/dist/lib/http/proxy-dispatcher.js +97 -0
- package/dist/lib/keychain/secure.js +4 -4
- package/dist/lib/keychain.js +1 -1
- package/dist/lib/media-import/status.js +6 -1
- package/dist/lib/retry.js +3 -2
- package/dist/lib/search-and-replace.js +3 -4
- package/dist/lib/validations/custom-deploy.js +60 -4
- package/dist/lib/validations/line-by-line.js +4 -9
- package/dist/lib/validations/sql.js +2 -2
- package/docs/COMMANDER-MIGRATION.md +1 -0
- package/docs/RELEASING.md +2 -0
- package/docs/TESTING.md +16 -1
- package/helpers/build-sea.js +2 -2
- package/helpers/release-smoke-test.js +869 -0
- package/npm-shrinkwrap.json +825 -1041
- package/package.json +22 -16
- package/test-utils/undici-mock.js +25 -0
package/AGENTS.md
CHANGED
|
@@ -96,3 +96,5 @@ Guide for future agents working on this codebase. Focus on traps, cross-cutting
|
|
|
96
96
|
- Respect exit semantics: uncaught exceptions routed to `exit.withError`; GraphQL errors call `process.exit(1)` unless explicitly disabled; 401 auth errors exit with guidance.
|
|
97
97
|
- Watch shared state: current implementation mutates a global; ensure new parser avoids cross-command bleeding when multiple command instances run in-process (tests/CLI wrappers).
|
|
98
98
|
- Login guardrails in `vip.js` let certain commands bypass auth; preserve equivalent shortcuts or add explicit non-interactive flags for CI.
|
|
99
|
+
- Normalize short-option equals syntax: rewrite `-x=value` to `-x value` before parsing for short options that expect values; track which short options take values to avoid breaking boolean flags that should remain flag-only.
|
|
100
|
+
- Apply tracked defaults after parsing: when an option has both a default and a custom parser function, apply the default only when `opts[optionName] === undefined` after the parser runs, so that explicit values and defaults maintain parity with the parser contract (see test `parses short options using equals syntax without prefixing values` in `__tests__/lib/cli/command.js`).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.appDeployTokenGenerateCmd = appDeployTokenGenerateCmd;
|
|
6
|
+
var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
|
|
7
|
+
var _api = _interopRequireDefault(require("../lib/api"));
|
|
8
|
+
var _command = _interopRequireDefault(require("../lib/cli/command"));
|
|
9
|
+
var exit = _interopRequireWildcard(require("../lib/cli/exit"));
|
|
10
|
+
var _tracker = require("../lib/tracker");
|
|
11
|
+
var _utils = require("../lib/utils");
|
|
12
|
+
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); }
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
const usage = 'vip app deploy-token generate';
|
|
15
|
+
const examples = [{
|
|
16
|
+
usage: 'vip @example-app.develop app deploy-token generate',
|
|
17
|
+
description: 'Generate a custom deploy access token for the selected environment.'
|
|
18
|
+
}, {
|
|
19
|
+
usage: 'vip @example-app.develop app deploy-token generate --format=json',
|
|
20
|
+
description: 'Generate a custom deploy access token and print the result as JSON.'
|
|
21
|
+
}];
|
|
22
|
+
const appQuery = `
|
|
23
|
+
id,
|
|
24
|
+
name,
|
|
25
|
+
environments{
|
|
26
|
+
id
|
|
27
|
+
appId
|
|
28
|
+
type
|
|
29
|
+
name
|
|
30
|
+
uniqueLabel
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
const GENERATE_CUSTOM_DEPLOY_ACCESS_MUTATION = (0, _graphqlTag.default)`
|
|
34
|
+
mutation GenerateCustomDeployAccess($input: GenerateCustomDeployAccessInput) {
|
|
35
|
+
generateCustomDeployAccess(input: $input) {
|
|
36
|
+
token
|
|
37
|
+
expiresAt
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
async function appDeployTokenGenerateCmd(_arg, {
|
|
42
|
+
app,
|
|
43
|
+
env
|
|
44
|
+
}) {
|
|
45
|
+
const trackerFn = (0, _tracker.makeCommandTracker)('deploy_token_generate', {
|
|
46
|
+
app: app.id,
|
|
47
|
+
env: env.uniqueLabel
|
|
48
|
+
});
|
|
49
|
+
await trackerFn('execute');
|
|
50
|
+
const envId = env.id;
|
|
51
|
+
if (typeof envId !== 'number') {
|
|
52
|
+
await trackerFn('error', {
|
|
53
|
+
error: 'Missing environment id'
|
|
54
|
+
});
|
|
55
|
+
exit.withError('Failed to generate deploy token: environment id is missing.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const api = (0, _api.default)();
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await api.mutate({
|
|
62
|
+
mutation: GENERATE_CUSTOM_DEPLOY_ACCESS_MUTATION,
|
|
63
|
+
variables: {
|
|
64
|
+
input: {
|
|
65
|
+
environmentIds: [envId]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const message = (0, _utils.parseApiError)(err) ?? 'Unknown error';
|
|
71
|
+
await trackerFn('error', {
|
|
72
|
+
error: message
|
|
73
|
+
});
|
|
74
|
+
exit.withError(`Failed to generate deploy token: ${message}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const tokenPayload = response.data?.generateCustomDeployAccess;
|
|
78
|
+
if (!tokenPayload?.token || !tokenPayload.expiresAt) {
|
|
79
|
+
await trackerFn('error', {
|
|
80
|
+
error: 'Missing generateCustomDeployAccess payload'
|
|
81
|
+
});
|
|
82
|
+
exit.withError('Failed to generate deploy token: API returned an empty response.');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await trackerFn('success');
|
|
86
|
+
return [{
|
|
87
|
+
appId: app.id,
|
|
88
|
+
appName: app.name,
|
|
89
|
+
envId,
|
|
90
|
+
env: env.uniqueLabel,
|
|
91
|
+
token: tokenPayload.token,
|
|
92
|
+
expiresAt: tokenPayload.expiresAt
|
|
93
|
+
}];
|
|
94
|
+
}
|
|
95
|
+
void (0, _command.default)({
|
|
96
|
+
appContext: true,
|
|
97
|
+
appQuery,
|
|
98
|
+
envContext: true,
|
|
99
|
+
format: true,
|
|
100
|
+
requiredArgs: 0,
|
|
101
|
+
usage
|
|
102
|
+
}).examples(examples).argv(process.argv, appDeployTokenGenerateCmd);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var _command = _interopRequireDefault(require("../lib/cli/command"));
|
|
5
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
const usage = 'vip app deploy-token';
|
|
7
|
+
const exampleUsage = 'vip @example-app.develop app deploy-token';
|
|
8
|
+
const examples = [{
|
|
9
|
+
usage: `${exampleUsage} generate`,
|
|
10
|
+
description: 'Generate a custom deploy access token for an environment that already has Custom Deployment enabled.'
|
|
11
|
+
}, {
|
|
12
|
+
usage: `${exampleUsage} generate --format=json | jq -r '.[0].token'`,
|
|
13
|
+
description: 'Generate a token and extract the token value for scripting workflows.'
|
|
14
|
+
}];
|
|
15
|
+
void (0, _command.default)({
|
|
16
|
+
requiredArgs: 0,
|
|
17
|
+
usage
|
|
18
|
+
}).command('generate', 'Generate a custom deploy access token for an environment.').examples(examples).argv(process.argv, () => {
|
|
19
|
+
process.exit(0);
|
|
20
|
+
});
|
|
@@ -79,6 +79,9 @@ async function appDeployCmd(arg = [], opts = {}) {
|
|
|
79
79
|
const track = _tracker.trackEventWithEnv.bind(null, appId, envId);
|
|
80
80
|
debug('Validating file...');
|
|
81
81
|
await (0, _customDeploy.validateFile)(appId, envId, fileMeta);
|
|
82
|
+
if (!opts.skipLargeFileValidation) {
|
|
83
|
+
await (0, _customDeploy.validateLargeArchiveFiles)(appId, envId, fileMeta);
|
|
84
|
+
}
|
|
82
85
|
await track('deploy_app_command_execute');
|
|
83
86
|
|
|
84
87
|
// Upload file as different name to avoid overwriting existing same named files
|
|
@@ -229,8 +232,11 @@ const examples = [
|
|
|
229
232
|
}, {
|
|
230
233
|
usage: 'WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.tar.gz --skip-confirmation',
|
|
231
234
|
description: 'Skip the confirmation prompt for the Custom Deployment of the archived file named "file.tar.gz" to the environment.'
|
|
235
|
+
}, {
|
|
236
|
+
usage: 'WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.zip --skip-large-file-validation',
|
|
237
|
+
description: 'Skip checking the deploy archive for repository archive files larger than 20 MB.'
|
|
232
238
|
}];
|
|
233
239
|
void (0, _command.default)({
|
|
234
240
|
requiredArgs: 1,
|
|
235
241
|
usage: baseUsage
|
|
236
|
-
}).command('validate', 'Validate the directory structure of an archived file.').examples(examples).option('message', 'Add a description of a deployment.').option('skip-confirmation', 'Skip the confirmation prompt.').option('force', 'Skip confirmation prompt (deprecated)').option('app', 'Target an application. Accepts a string value for the application name or an integer for the application ID.').option('env', 'Target an environment. Accepts a string value for the environment type.').argv(process.argv, appDeployCmd);
|
|
242
|
+
}).command('validate', 'Validate the directory structure of an archived file.').examples(examples).option('message', 'Add a description of a deployment.').option('skip-confirmation', 'Skip the confirmation prompt.').option('skip-large-file-validation', 'Skip checking for repository archive files over 20 MB.', false).option('force', 'Skip confirmation prompt (deprecated)').option('app', 'Target an application. Accepts a string value for the application name or an integer for the application ID.').option('env', 'Target an environment. Accepts a string value for the environment type.').argv(process.argv, appDeployCmd);
|
package/dist/bin/vip-app.js
CHANGED
|
@@ -11,7 +11,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
11
11
|
(0, _command.default)({
|
|
12
12
|
requiredArgs: 1,
|
|
13
13
|
format: true
|
|
14
|
-
}).example('vip app list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').example('vip app example-app', 'Retrieve information about the application named "example-app" and its environments.').example('WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.zip', 'Deploy a local archived file named "file.zip" that contains application code to a VIP Platform environment that has Custom Deployment enabled.').command('list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').command('deploy', 'Deploy an archived file of application code to an environment that has Custom Deployment enabled.').argv(process.argv, async arg => {
|
|
14
|
+
}).example('vip app list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').example('vip app example-app', 'Retrieve information about the application named "example-app" and its environments.').example('WPVIP_DEPLOY_TOKEN=1234 vip @example-app.develop app deploy file.zip', 'Deploy a local archived file named "file.zip" that contains application code to a VIP Platform environment that has Custom Deployment enabled.').example('vip @example-app.develop app deploy-token generate --format=json', 'Generate a custom deploy token for the environment and print it in JSON format for use with tools like jq.').command('list', 'Retrieve a list of applications that can be accessed by the current authenticated VIP-CLI user.').command('deploy', 'Deploy an archived file of application code to an environment that has Custom Deployment enabled.').command('deploy-token', 'Generate and manage custom deploy access tokens for environments that have Custom Deployment enabled.').argv(process.argv, async arg => {
|
|
15
15
|
await (0, _tracker.trackEvent)('app_command_execute');
|
|
16
16
|
let res;
|
|
17
17
|
try {
|
|
@@ -53,7 +53,6 @@ cmd.argv(process.argv, async (arg, opt) => {
|
|
|
53
53
|
...opt
|
|
54
54
|
};
|
|
55
55
|
const configurationFileOptions = await (0, _devEnvironmentConfigurationFile.getConfigurationFileOptions)();
|
|
56
|
-
const thereAreOptionsFromConfigFile = Object.keys(configurationFileOptions).length > 0;
|
|
57
56
|
const finalPreselectedOptions = (0, _devEnvironmentConfigurationFile.mergeConfigurationFileOptions)(preselectedOptions, configurationFileOptions);
|
|
58
57
|
|
|
59
58
|
/** @type {InstanceOptions} */
|
|
@@ -74,10 +73,7 @@ cmd.argv(process.argv, async (arg, opt) => {
|
|
|
74
73
|
cron: currentInstanceData.cron,
|
|
75
74
|
adminPassword: currentInstanceData.adminPassword
|
|
76
75
|
};
|
|
77
|
-
const
|
|
78
|
-
.filter(option => !['debug', 'help', 'slug'].includes(option)); // Filter out options that are not related to instance configuration
|
|
79
|
-
|
|
80
|
-
const suppressPrompts = providedOptions.length > 0 || thereAreOptionsFromConfigFile;
|
|
76
|
+
const suppressPrompts = !process.stdin.isTTY;
|
|
81
77
|
const instanceData = await (0, _devEnvironmentCli.promptForArguments)(finalPreselectedOptions, defaultOptions, suppressPrompts, false);
|
|
82
78
|
instanceData.siteSlug = slug;
|
|
83
79
|
await (0, _devEnvironmentCore.updateEnvironment)(lando, instanceData);
|
|
@@ -115,7 +115,10 @@ 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',
|
|
118
|
+
importArg = ['db-myloader',
|
|
119
|
+
// Drop existing tables before restoring. Do not use the long form:
|
|
120
|
+
// `--overwrite-tables` is a silent no-op on myloader >= 0.20.
|
|
121
|
+
'-o', ...(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
122
|
}
|
|
120
123
|
return importArg;
|
|
121
124
|
}
|
|
@@ -180,12 +180,11 @@ class DevEnvSyncSQLCommand {
|
|
|
180
180
|
const readStream = _fs.default.createReadStream(this.sqlFile);
|
|
181
181
|
const replacedStream = await (0, _vipSearchReplace.replace)(readStream, replacements);
|
|
182
182
|
const outputFile = `${this.tmpDir}/sql-export-sr.sql`;
|
|
183
|
-
const
|
|
183
|
+
const transforms = [];
|
|
184
184
|
if (this.getSqlDumpType() === _database.SqlDumpType.MYDUMPER) {
|
|
185
|
-
|
|
185
|
+
transforms.push((0, _database.fixMyDumperTransform)());
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
await (0, _promises.pipeline)(streams);
|
|
187
|
+
await (0, _promises.pipeline)(replacedStream, ...transforms, _fs.default.createWriteStream(outputFile));
|
|
189
188
|
_fs.default.renameSync(outputFile, this.sqlFile);
|
|
190
189
|
}
|
|
191
190
|
getSiteUrlsFromSDS() {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.default = void 0;
|
|
5
5
|
var _debug = _interopRequireDefault(require("debug"));
|
|
6
|
-
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
7
6
|
var _querystring = _interopRequireDefault(require("querystring"));
|
|
7
|
+
var _undici = require("undici");
|
|
8
8
|
var _apiConfig = require("../../cli/apiConfig");
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
10
|
const debug = (0, _debug.default)('@automattic/vip:analytics:clients:tracks');
|
|
@@ -94,7 +94,7 @@ class Tracks {
|
|
|
94
94
|
'User-Agent': this.userAgent
|
|
95
95
|
};
|
|
96
96
|
debug('send()', body);
|
|
97
|
-
return (0,
|
|
97
|
+
return (0, _undici.fetch)(Tracks.ENDPOINT, {
|
|
98
98
|
method,
|
|
99
99
|
body,
|
|
100
100
|
headers
|
package/dist/lib/api/http.js
CHANGED
|
@@ -3,19 +3,25 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.default = void 0;
|
|
5
5
|
var _debug = _interopRequireDefault(require("debug"));
|
|
6
|
-
var
|
|
6
|
+
var _undici = require("undici");
|
|
7
7
|
var _api = require("../../lib/api");
|
|
8
8
|
var _env = _interopRequireDefault(require("../../lib/env"));
|
|
9
|
-
var
|
|
9
|
+
var _proxyDispatcher = require("../../lib/http/proxy-dispatcher");
|
|
10
10
|
var _token = _interopRequireDefault(require("../../lib/token"));
|
|
11
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
const debug = (0, _debug.default)('@automattic/vip:http');
|
|
13
|
+
const isPlainObjectBody = value => {
|
|
14
|
+
if (!value || typeof value !== 'object') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const prototype = Object.getPrototypeOf(value);
|
|
18
|
+
return prototype === Object.prototype || prototype === null;
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Call the Public API with an arbitrary path (e.g. to connect to REST endpoints).
|
|
15
23
|
* This will include the token in an Authorization header so requests are "logged-in."
|
|
16
24
|
*
|
|
17
|
-
* This is simply a wrapper around node-fetch
|
|
18
|
-
*
|
|
19
25
|
* @param {string} path API path to pass to `fetch` -- will be prefixed by the API_HOST
|
|
20
26
|
* @param {Object} options options to pass to `fetch`
|
|
21
27
|
* @return {Promise} Return value of the `fetch` call
|
|
@@ -29,26 +35,25 @@ var _default = async (path, options = {}) => {
|
|
|
29
35
|
url = `${_api.API_HOST}${path}`;
|
|
30
36
|
}
|
|
31
37
|
const authToken = await _token.default.get();
|
|
32
|
-
const
|
|
38
|
+
const proxyDispatcher = (0, _proxyDispatcher.createProxyDispatcher)(url);
|
|
39
|
+
const shouldSerializeJsonBody = isPlainObjectBody(options.body);
|
|
33
40
|
debug('running fetch', url);
|
|
34
|
-
const headers = new Headers(
|
|
35
|
-
...options.headers
|
|
36
|
-
});
|
|
41
|
+
const headers = new _undici.Headers(options.headers);
|
|
37
42
|
if (!headers.has('Authorization')) {
|
|
38
43
|
headers.set('Authorization', `Bearer ${authToken.raw}`);
|
|
39
44
|
}
|
|
40
45
|
if (!headers.has('User-Agent')) {
|
|
41
46
|
headers.set('User-Agent', _env.default.userAgent);
|
|
42
47
|
}
|
|
43
|
-
if (!headers.has('Content-Type') && options.method !== 'GET') {
|
|
48
|
+
if (!headers.has('Content-Type') && options.method !== 'GET' && shouldSerializeJsonBody) {
|
|
44
49
|
headers.set('Content-Type', 'application/json');
|
|
45
50
|
}
|
|
46
|
-
const
|
|
51
|
+
const requestBody = shouldSerializeJsonBody ? JSON.stringify(options.body) : options.body;
|
|
52
|
+
return (0, _undici.fetch)(url, {
|
|
47
53
|
...options,
|
|
48
|
-
|
|
54
|
+
dispatcher: proxyDispatcher ?? undefined,
|
|
49
55
|
headers,
|
|
50
|
-
body:
|
|
51
|
-
};
|
|
52
|
-
return (0, _nodeFetch.default)(url, opts);
|
|
56
|
+
body: requestBody
|
|
57
|
+
});
|
|
53
58
|
};
|
|
54
59
|
exports.default = _default;
|
package/dist/lib/api.js
CHANGED
|
@@ -12,7 +12,6 @@ var _retry = require("@apollo/client/link/retry");
|
|
|
12
12
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
13
13
|
var _debug = _interopRequireDefault(require("debug"));
|
|
14
14
|
var _graphql = require("graphql");
|
|
15
|
-
var _nodeFetch = require("node-fetch");
|
|
16
15
|
var _http = _interopRequireDefault(require("./api/http"));
|
|
17
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
17
|
// Config
|
|
@@ -41,7 +40,7 @@ function shouldRetryRequest(attempt, operation, error) {
|
|
|
41
40
|
debug(`Request failed and max retry attempts reached. ${debugSuffix}`, error);
|
|
42
41
|
return false;
|
|
43
42
|
}
|
|
44
|
-
if (error
|
|
43
|
+
if (error?.code === 'ECONNREFUSED') {
|
|
45
44
|
debug(`Request failed. Retrying request due to connection refused error. ${debugSuffix}`);
|
|
46
45
|
return true;
|
|
47
46
|
}
|
package/dist/lib/cli/command.js
CHANGED
|
@@ -323,6 +323,41 @@ class CommanderArgsCompat {
|
|
|
323
323
|
}
|
|
324
324
|
return null;
|
|
325
325
|
}
|
|
326
|
+
hasWpCommandPayload(subcommandArgs) {
|
|
327
|
+
const wpFlagsWithoutValue = new Set(['--help', '-h', '--version', '-v', '--yes', '-y']);
|
|
328
|
+
let expectOptionValue = false;
|
|
329
|
+
for (const arg of subcommandArgs) {
|
|
330
|
+
if (expectOptionValue) {
|
|
331
|
+
if (!isOptionToken(arg)) {
|
|
332
|
+
expectOptionValue = false;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
expectOptionValue = false;
|
|
336
|
+
}
|
|
337
|
+
if (!isOptionToken(arg)) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (arg.startsWith('--')) {
|
|
341
|
+
if (arg.includes('=') || wpFlagsWithoutValue.has(arg)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
expectOptionValue = true;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (/^-[A-Za-z0-9]$/.test(arg)) {
|
|
348
|
+
if (wpFlagsWithoutValue.has(arg)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
expectOptionValue = true;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (/^-[A-Za-z0-9]+=/.test(arg)) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
326
361
|
async executeSubcommand(argv, parsedAlias, subcommand) {
|
|
327
362
|
const currentScript = argv[1];
|
|
328
363
|
const subcommandName = subcommand.name;
|
|
@@ -334,8 +369,9 @@ class CommanderArgsCompat {
|
|
|
334
369
|
const hasSeparatorBeforeSubcommand = rawArgsBeforeSubcommand.includes('--');
|
|
335
370
|
const argsBeforeSubcommand = rawArgsBeforeSubcommand.filter(arg => arg !== '--');
|
|
336
371
|
const subcommandArgs = parsedAlias.argv.slice(subcommand.index + 1);
|
|
372
|
+
const hasWpCommandPayload = this.hasWpCommandPayload(subcommandArgs);
|
|
337
373
|
const hasSeparator = argv.includes('--');
|
|
338
|
-
if (subcommandName === 'wp' &&
|
|
374
|
+
if (subcommandName === 'wp' && hasWpCommandPayload && !hasSeparator) {
|
|
339
375
|
exit.withError('A double dash ("--") must separate the arguments of "vip" from those of "wp". Run "vip wp --help" for examples.');
|
|
340
376
|
}
|
|
341
377
|
let childArgs = [...argsBeforeSubcommand, ...(hasSeparatorBeforeSubcommand ? ['--'] : []), ...subcommandArgs];
|
|
@@ -8,6 +8,8 @@ const internalBinLoaders = {
|
|
|
8
8
|
vip: () => import('../../bin/vip'),
|
|
9
9
|
'vip-app': () => import('../../bin/vip-app'),
|
|
10
10
|
'vip-app-deploy': () => import('../../bin/vip-app-deploy'),
|
|
11
|
+
'vip-app-deploy-token': () => import('../../bin/vip-app-deploy-token'),
|
|
12
|
+
'vip-app-deploy-token-generate': () => import('../../bin/vip-app-deploy-token-generate'),
|
|
11
13
|
'vip-app-deploy-validate': () => import('../../bin/vip-app-deploy-validate'),
|
|
12
14
|
'vip-app-list': () => import('../../bin/vip-app-list'),
|
|
13
15
|
'vip-backup': () => import('../../bin/vip-backup'),
|
|
@@ -10,6 +10,7 @@ exports.getFileSize = getFileSize;
|
|
|
10
10
|
exports.getPartBoundaries = getPartBoundaries;
|
|
11
11
|
exports.getSignedUploadRequestData = getSignedUploadRequestData;
|
|
12
12
|
exports.isFile = isFile;
|
|
13
|
+
exports.parseEtagHeader = parseEtagHeader;
|
|
13
14
|
exports.unzipFile = void 0;
|
|
14
15
|
exports.uploadImportFileToS3 = uploadImportFileToS3;
|
|
15
16
|
exports.uploadParts = uploadParts;
|
|
@@ -17,28 +18,44 @@ var _chalk = _interopRequireDefault(require("chalk"));
|
|
|
17
18
|
var _crypto = require("crypto");
|
|
18
19
|
var _debug = _interopRequireDefault(require("debug"));
|
|
19
20
|
var _fs = require("fs");
|
|
20
|
-
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
21
21
|
var _promises = require("node:fs/promises");
|
|
22
22
|
var _promises2 = require("node:stream/promises");
|
|
23
|
+
var _promises3 = require("node:timers/promises");
|
|
23
24
|
var _os = _interopRequireDefault(require("os"));
|
|
24
25
|
var _path = _interopRequireDefault(require("path"));
|
|
25
26
|
var _stream = require("stream");
|
|
27
|
+
var _undici = require("undici");
|
|
26
28
|
var _xml2js = require("xml2js");
|
|
27
29
|
var _zlib = require("zlib");
|
|
28
30
|
var _http = _interopRequireDefault(require("../lib/api/http"));
|
|
29
31
|
var _fileSize = require("../lib/constants/file-size");
|
|
30
32
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return Math.pow(2, attempt) * 1000; // 1000, 2000, 4000
|
|
33
|
+
function parseEtagHeader(etag) {
|
|
34
|
+
const normalizedEtag = etag.replace(/^W\//u, '').trim();
|
|
35
|
+
if (normalizedEtag.startsWith('"') && normalizedEtag.endsWith('"')) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(normalizedEtag);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
debug(`Unable to JSON.parse ETag header, falling back to raw value: ${err.message}`);
|
|
40
|
+
}
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
return normalizedEtag.replace(/^"(.*)"$/u, '$1');
|
|
43
|
+
}
|
|
44
|
+
async function fetchWithRetry(input, init, retries = 3) {
|
|
45
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
46
|
+
try {
|
|
47
|
+
// eslint-disable-next-line no-await-in-loop
|
|
48
|
+
return await (0, _undici.fetch)(input, init);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (attempt === retries) {
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
// eslint-disable-next-line no-await-in-loop
|
|
54
|
+
await (0, _promises3.setTimeout)(Math.pow(2, attempt) * 1000); // 1000, 2000, 4000
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new Error('unreachable');
|
|
58
|
+
}
|
|
42
59
|
const debug = (0, _debug.default)('vip:lib/client-file-uploader');
|
|
43
60
|
|
|
44
61
|
// Files smaller than COMPRESS_THRESHOLD will not be compressed before upload
|
|
@@ -510,9 +527,11 @@ async function uploadPart({
|
|
|
510
527
|
}).pipe(progressPassThrough);
|
|
511
528
|
const fetchResponse = await fetchWithRetry(partUploadRequestData.url, fetchOptions);
|
|
512
529
|
if (fetchResponse.status === 200) {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
530
|
+
const etag = fetchResponse.headers.get('etag');
|
|
531
|
+
if (!etag) {
|
|
532
|
+
throw new Error('Unable to upload file part. Missing ETag response header.');
|
|
533
|
+
}
|
|
534
|
+
return parseEtagHeader(etag);
|
|
516
535
|
}
|
|
517
536
|
const result = await fetchResponse.text();
|
|
518
537
|
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.validateCustomDeployKey = validateCustomDeployKey;
|
|
5
5
|
exports.validateFile = validateFile;
|
|
6
|
+
exports.validateLargeArchiveFiles = validateLargeArchiveFiles;
|
|
7
|
+
var _chalk = _interopRequireDefault(require("chalk"));
|
|
6
8
|
var _fs = _interopRequireDefault(require("fs"));
|
|
7
9
|
var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
|
|
8
10
|
var _api = _interopRequireDefault(require("../../lib/api"));
|
|
@@ -15,6 +17,14 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
15
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
18
|
const DEPLOY_MAX_FILE_SIZE = 4 * _fileSize.GB_IN_BYTES;
|
|
17
19
|
const WPVIP_DEPLOY_TOKEN = process.env.WPVIP_DEPLOY_TOKEN;
|
|
20
|
+
const SKIP_LARGE_FILE_VALIDATION_FLAG = '--skip-large-file-validation';
|
|
21
|
+
function formatFileSize(bytes) {
|
|
22
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
23
|
+
}
|
|
24
|
+
function getLargeArchiveFilesMessage(largeArchiveFiles) {
|
|
25
|
+
const fileList = largeArchiveFiles.map(file => `- ${file.path} (${formatFileSize(file.size)})`).join('\n');
|
|
26
|
+
return `Deploy archive contains archive file(s) larger than ${formatFileSize(_customDeploy.LARGE_ARCHIVE_FILE_SIZE_LIMIT)} in the deploy archive:\n${fileList}\nRemove these files from the repository, or rerun with ${_chalk.default.bold(SKIP_LARGE_FILE_VALIDATION_FLAG)} to skip this check.`;
|
|
27
|
+
}
|
|
18
28
|
async function validateCustomDeployKey(app, env) {
|
|
19
29
|
if (!WPVIP_DEPLOY_TOKEN) {
|
|
20
30
|
exit.withError('Valid custom deploy key is required.');
|
|
@@ -119,4 +129,31 @@ async function validateFile(appId, envId, fileMeta) {
|
|
|
119
129
|
});
|
|
120
130
|
exit.withError(`The deploy file size (${fileSize} bytes) exceeds the limit (${DEPLOY_MAX_FILE_SIZE} bytes).`);
|
|
121
131
|
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {number} appId
|
|
136
|
+
* @param {number} envId
|
|
137
|
+
* @param {FileMeta} fileMeta
|
|
138
|
+
*/
|
|
139
|
+
async function validateLargeArchiveFiles(appId, envId, fileMeta) {
|
|
140
|
+
const track = _tracker.trackEventWithEnv.bind(null, appId, envId);
|
|
141
|
+
let largeArchiveFiles;
|
|
142
|
+
try {
|
|
143
|
+
largeArchiveFiles = await (0, _customDeploy.findLargeArchiveFilesInDeployArchive)(fileMeta.fileName);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
await track('deploy_app_command_error', {
|
|
146
|
+
error_type: 'large-archive-file-verify-failed',
|
|
147
|
+
verify_error: error.message
|
|
148
|
+
});
|
|
149
|
+
return exit.withError(`Unable to verify large archive files in the deploy archive: ${error.message}. Rerun with ${SKIP_LARGE_FILE_VALIDATION_FLAG} to skip this check.`);
|
|
150
|
+
}
|
|
151
|
+
if (!largeArchiveFiles.length) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
await track('deploy_app_command_error', {
|
|
155
|
+
error_type: 'large-archive-files',
|
|
156
|
+
large_archive_files: largeArchiveFiles.map(file => file.path)
|
|
157
|
+
});
|
|
158
|
+
return exit.withError(getLargeArchiveFilesMessage(largeArchiveFiles));
|
|
122
159
|
}
|
|
@@ -28,19 +28,19 @@ var _debug = _interopRequireDefault(require("debug"));
|
|
|
28
28
|
var _ejs = _interopRequireDefault(require("ejs"));
|
|
29
29
|
var _enquirer = require("enquirer");
|
|
30
30
|
var _graphql = require("graphql");
|
|
31
|
-
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
32
31
|
var _nodeCrypto = require("node:crypto");
|
|
33
32
|
var _nodeFs = _interopRequireDefault(require("node:fs"));
|
|
34
33
|
var _promises = require("node:fs/promises");
|
|
35
34
|
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
36
35
|
var _semver = _interopRequireDefault(require("semver"));
|
|
36
|
+
var _undici = require("undici");
|
|
37
37
|
var _devEnvironmentCli = require("./dev-environment-cli");
|
|
38
38
|
var _devEnvironmentLando = require("./dev-environment-lando");
|
|
39
39
|
var _landoLoader = require("./lando-loader");
|
|
40
40
|
var _app = _interopRequireDefault(require("../api/app"));
|
|
41
41
|
var _software = require("../config/software");
|
|
42
42
|
var _devEnvironment = require("../constants/dev-environment");
|
|
43
|
-
var
|
|
43
|
+
var _proxyDispatcher = require("../http/proxy-dispatcher");
|
|
44
44
|
var _searchAndReplace = require("../search-and-replace");
|
|
45
45
|
var _userError = _interopRequireDefault(require("../user-error"));
|
|
46
46
|
var _xdgData = require("../xdg-data");
|
|
@@ -740,9 +740,9 @@ async function maybeUpdateVersion(lando, slug) {
|
|
|
740
740
|
*/
|
|
741
741
|
function fetchVersionList() {
|
|
742
742
|
const url = `https://${_devEnvironment.DEV_ENVIRONMENT_RAW_GITHUB_HOST}${_devEnvironment.DEV_ENVIRONMENT_WORDPRESS_VERSIONS_URI}`;
|
|
743
|
-
const
|
|
744
|
-
return (0,
|
|
745
|
-
|
|
743
|
+
const proxyDispatcher = (0, _proxyDispatcher.createProxyDispatcher)(url);
|
|
744
|
+
return (0, _undici.fetch)(url, {
|
|
745
|
+
dispatcher: proxyDispatcher ?? undefined
|
|
746
746
|
}).then(res => res.json());
|
|
747
747
|
}
|
|
748
748
|
|
|
@@ -487,11 +487,15 @@ async function landoInfo(lando, instancePath, options = {}) {
|
|
|
487
487
|
|
|
488
488
|
// Add login information
|
|
489
489
|
if (frontEndUrl) {
|
|
490
|
-
|
|
490
|
+
const loginURL = new URL(`${frontEndUrl}wp-admin/`);
|
|
491
491
|
if (options.autologinKey) {
|
|
492
|
-
|
|
492
|
+
loginURL.searchParams.set('vip-dev-autologin', options.autologinKey);
|
|
493
493
|
}
|
|
494
|
-
|
|
494
|
+
loginURL.protocol = 'http:';
|
|
495
|
+
const httpLoginUrl = loginURL.toString();
|
|
496
|
+
loginURL.protocol = 'https:';
|
|
497
|
+
const httpsLoginUrl = loginURL.toString();
|
|
498
|
+
appInfo['Login URL'] = [httpsLoginUrl, httpLoginUrl].join(_nodeOs.EOL);
|
|
495
499
|
appInfo['Default username'] = 'vipgo';
|
|
496
500
|
|
|
497
501
|
// Get the stored password from instance data
|
|
@@ -5,11 +5,16 @@ exports.downloadFile = void 0;
|
|
|
5
5
|
var fs = _interopRequireWildcard(require("fs"));
|
|
6
6
|
var _stream = require("stream");
|
|
7
7
|
var _promises = require("stream/promises");
|
|
8
|
+
var _undici = require("undici");
|
|
9
|
+
var _proxyDispatcher = require("./proxy-dispatcher");
|
|
8
10
|
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); }
|
|
9
11
|
const downloadFile = async (fileUrl, destinationPath, onProgress) => {
|
|
10
12
|
let response;
|
|
13
|
+
const proxyDispatcher = (0, _proxyDispatcher.createProxyDispatcher)(fileUrl);
|
|
11
14
|
try {
|
|
12
|
-
response = await fetch(fileUrl
|
|
15
|
+
response = await (0, _undici.fetch)(fileUrl, {
|
|
16
|
+
dispatcher: proxyDispatcher ?? undefined
|
|
17
|
+
});
|
|
13
18
|
} catch (error) {
|
|
14
19
|
throw new Error(`Request to ${fileUrl} failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
15
20
|
}
|