@automattic/vip 4.0.3 → 4.0.6
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 +110 -52
- 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 +873 -1043
- package/package.json +23 -17
- package/test-utils/undici-mock.js +25 -0
|
@@ -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
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.createProxyDispatcher = createProxyDispatcher;
|
|
5
|
+
var _debug = _interopRequireDefault(require("debug"));
|
|
6
|
+
var _undici = require("undici");
|
|
7
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
const debug = (0, _debug.default)('vip:proxy-dispatcher');
|
|
9
|
+
const proxyDispatchers = new Map();
|
|
10
|
+
function normalizeNoProxyToken(token) {
|
|
11
|
+
return token.trim().toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function isNoProxyMatch(hostname, port, token) {
|
|
14
|
+
// eslint-disable-next-line security/detect-possible-timing-attacks
|
|
15
|
+
if (token === '*') {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const [tokenHostRaw, tokenPort] = token.split(':');
|
|
19
|
+
const tokenHost = tokenHostRaw.replace(/^\./u, '');
|
|
20
|
+
if (tokenPort && tokenPort !== port) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (hostname === tokenHost) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return hostname.endsWith(`.${tokenHost}`);
|
|
27
|
+
}
|
|
28
|
+
function isCoveredByNoProxy(url) {
|
|
29
|
+
const NO_PROXY = process.env.NO_PROXY || process.env.no_proxy || null; // NOSONAR
|
|
30
|
+
|
|
31
|
+
if (!NO_PROXY) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const parsedUrl = new URL(url);
|
|
35
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
36
|
+
const port = parsedUrl.port || (parsedUrl.protocol === 'http:' ? '80' : '443');
|
|
37
|
+
const noProxyTokens = NO_PROXY.split(',').map(normalizeNoProxyToken).filter(Boolean);
|
|
38
|
+
return noProxyTokens.some(token => isNoProxyMatch(hostname, port, token));
|
|
39
|
+
}
|
|
40
|
+
function resolveProxyUrl(url) {
|
|
41
|
+
const VIP_PROXY = process.env.VIP_PROXY || process.env.vip_proxy || null; // NOSONAR
|
|
42
|
+
const SOCKS_PROXY = process.env.SOCKS_PROXY || process.env.socks_proxy || null; // NOSONAR
|
|
43
|
+
const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.https_proxy || null; // NOSONAR
|
|
44
|
+
|
|
45
|
+
if (VIP_PROXY) {
|
|
46
|
+
return VIP_PROXY;
|
|
47
|
+
}
|
|
48
|
+
if (process.env.VIP_USE_SYSTEM_PROXY && !isCoveredByNoProxy(url)) {
|
|
49
|
+
if (SOCKS_PROXY) {
|
|
50
|
+
return SOCKS_PROXY;
|
|
51
|
+
}
|
|
52
|
+
if (HTTPS_PROXY) {
|
|
53
|
+
return HTTPS_PROXY;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function redactProxyUrl(proxyUrl) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = new URL(proxyUrl);
|
|
61
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
62
|
+
} catch {
|
|
63
|
+
return 'invalid-proxy-url';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let listeners = null;
|
|
67
|
+
|
|
68
|
+
// Suppress ExperimentalWarning: SOCKS5 proxy support is experimental and subject to change
|
|
69
|
+
function suppressExperimentalSocksWarning(warning) {
|
|
70
|
+
listeners?.forEach(listener => process.on('warning', listener));
|
|
71
|
+
if (warning.name !== 'ExperimentalWarning' || !/SOCKS5 proxy/u.test(warning.message)) {
|
|
72
|
+
listeners?.forEach(listener => listener(warning));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build an undici dispatcher from the existing proxy resolution logic.
|
|
78
|
+
*
|
|
79
|
+
* This keeps env precedence in one place while allowing fetch callers to
|
|
80
|
+
* provide `dispatcher` instead of `agent`.
|
|
81
|
+
*/
|
|
82
|
+
function createProxyDispatcher(url) {
|
|
83
|
+
const proxyUrl = resolveProxyUrl(url);
|
|
84
|
+
if (!proxyUrl) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!proxyDispatchers.has(proxyUrl)) {
|
|
88
|
+
debug(`Enabling fetch dispatcher proxy support using config: ${redactProxyUrl(proxyUrl)}`);
|
|
89
|
+
proxyDispatchers.set(proxyUrl, new _undici.ProxyAgent(proxyUrl));
|
|
90
|
+
}
|
|
91
|
+
if (listeners === null) {
|
|
92
|
+
listeners = process.rawListeners('warning');
|
|
93
|
+
process.removeAllListeners('warning');
|
|
94
|
+
process.once('warning', suppressExperimentalSocksWarning);
|
|
95
|
+
}
|
|
96
|
+
return proxyDispatchers.get(proxyUrl) ?? null;
|
|
97
|
+
}
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.default = void 0;
|
|
5
|
-
var
|
|
5
|
+
var _keytar = _interopRequireDefault(require("@github/keytar"));
|
|
6
6
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
7
|
class Secure {
|
|
8
8
|
getPassword(service) {
|
|
9
|
-
return
|
|
9
|
+
return _keytar.default.getPassword(service, service);
|
|
10
10
|
}
|
|
11
11
|
async setPassword(service, password) {
|
|
12
|
-
await
|
|
12
|
+
await _keytar.default.setPassword(service, service, password);
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
15
|
deletePassword(service) {
|
|
16
|
-
return
|
|
16
|
+
return _keytar.default.deletePassword(service, service);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
exports.default = Secure;
|
package/dist/lib/keychain.js
CHANGED
|
@@ -10,7 +10,7 @@ const debug = (0, _debug.default)('@automattic/vip:keychain');
|
|
|
10
10
|
try {
|
|
11
11
|
// Try using Secure keychain ("keytar") first
|
|
12
12
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
13
|
-
const Secure = require('./keychain/secure');
|
|
13
|
+
const Secure = require('./keychain/secure').default;
|
|
14
14
|
keychain = new Secure();
|
|
15
15
|
} catch (error) {
|
|
16
16
|
debug('Cannot use Secure keychain; falling back to Insecure keychain (Details: %o)', error);
|
|
@@ -9,9 +9,11 @@ var _enquirer = require("enquirer");
|
|
|
9
9
|
var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
|
|
10
10
|
var _promises = require("node:fs/promises");
|
|
11
11
|
var _nodePath = require("node:path");
|
|
12
|
+
var _undici = require("undici");
|
|
12
13
|
var _api = _interopRequireDefault(require("../../lib/api"));
|
|
13
14
|
var _format = require("../../lib/cli/format");
|
|
14
15
|
var _mediaFileImport = require("../../lib/media-import/media-file-import");
|
|
16
|
+
var _proxyDispatcher = require("../http/proxy-dispatcher");
|
|
15
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
18
|
const IMPORT_MEDIA_PROGRESS_POLL_INTERVAL = 1000;
|
|
17
19
|
const ONE_MINUTE_IN_MILLISECONDS = 1000 * 60;
|
|
@@ -237,7 +239,10 @@ Downloading errors details from ${fileErrorsUrl}
|
|
|
237
239
|
clearAfter: true
|
|
238
240
|
});
|
|
239
241
|
try {
|
|
240
|
-
const
|
|
242
|
+
const proxyDispatcher = (0, _proxyDispatcher.createProxyDispatcher)(fileErrorsUrl);
|
|
243
|
+
const response = await (0, _undici.fetch)(fileErrorsUrl, {
|
|
244
|
+
dispatcher: proxyDispatcher ?? undefined
|
|
245
|
+
});
|
|
241
246
|
return await response.json();
|
|
242
247
|
} catch (err) {
|
|
243
248
|
progressTracker.suffix += `${_chalk.default.red(`Could not download import errors report\n${err.message}`)}`;
|
package/dist/lib/retry.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.__esModule = true;
|
|
|
4
4
|
exports.EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_200_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_100_MS = void 0;
|
|
5
5
|
exports.exponentialBackoff = exponentialBackoff;
|
|
6
6
|
exports.retry = retry;
|
|
7
|
+
var _promises = require("node:timers/promises");
|
|
7
8
|
// copied over from our internal lib
|
|
8
9
|
|
|
9
10
|
const EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exports.EXPONENTIAL_BACKOFF_STARTING_IN_50_MS = exponentialBackoff(50);
|
|
@@ -108,7 +109,7 @@ function isValidInterval(interval) {
|
|
|
108
109
|
}
|
|
109
110
|
return typeof interval === 'number' && Number.isInteger(interval) && interval >= 0 && interval <= MAX_INTERVAL_BETWEEN_ATTEMPTS;
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
+
function awaitInterval(interval, attemptNumber) {
|
|
112
113
|
let newInterval;
|
|
113
114
|
if (typeof interval === 'function') {
|
|
114
115
|
newInterval = interval(attemptNumber);
|
|
@@ -118,7 +119,7 @@ async function awaitInterval(interval, attemptNumber) {
|
|
|
118
119
|
} else {
|
|
119
120
|
newInterval = interval;
|
|
120
121
|
}
|
|
121
|
-
return
|
|
122
|
+
return (0, _promises.setTimeout)(newInterval);
|
|
122
123
|
}
|
|
123
124
|
function validateTask(task) {
|
|
124
125
|
if (typeof task !== 'function') {
|
|
@@ -130,13 +130,12 @@ const searchAndReplace = async (fileName, pairs, {
|
|
|
130
130
|
// replace() throws strings... OMG
|
|
131
131
|
exit.withError(replaceError);
|
|
132
132
|
}
|
|
133
|
-
const
|
|
133
|
+
const transforms = [];
|
|
134
134
|
if (isMyDumper) {
|
|
135
|
-
|
|
135
|
+
transforms.push((0, _database.fixMyDumperTransform)());
|
|
136
136
|
}
|
|
137
|
-
streams.push(writeStream);
|
|
138
137
|
try {
|
|
139
|
-
await (0, _promises.pipeline)(
|
|
138
|
+
await (0, _promises.pipeline)(replacedStream, ...transforms, writeStream);
|
|
140
139
|
} catch (error) {
|
|
141
140
|
console.log(_chalk.default.red("Oh no! We couldn't write to the output file. Please check your available disk space and file/folder permissions."));
|
|
142
141
|
throw error;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
|
+
exports.LARGE_ARCHIVE_FILE_SIZE_LIMIT = void 0;
|
|
5
|
+
exports.findLargeArchiveFilesInDeployArchive = findLargeArchiveFilesInDeployArchive;
|
|
4
6
|
exports.validateDeployFileExt = validateDeployFileExt;
|
|
5
7
|
exports.validateFilename = validateFilename;
|
|
6
8
|
exports.validateName = validateName;
|
|
@@ -11,6 +13,7 @@ var _nodeFs = require("node:fs");
|
|
|
11
13
|
var _path = _interopRequireDefault(require("path"));
|
|
12
14
|
var tar = _interopRequireWildcard(require("tar"));
|
|
13
15
|
var exit = _interopRequireWildcard(require("../../lib/cli/exit"));
|
|
16
|
+
var _fileSize = require("../../lib/constants/file-size");
|
|
14
17
|
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); }
|
|
15
18
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
19
|
const errorMessages = {
|
|
@@ -22,6 +25,62 @@ const errorMessages = {
|
|
|
22
25
|
};
|
|
23
26
|
const symlinkIgnorePattern = /\/node_modules\/[^/]+\/\.bin\//;
|
|
24
27
|
const macosxDir = '__MACOSX';
|
|
28
|
+
const LARGE_ARCHIVE_FILE_SIZE_LIMIT = exports.LARGE_ARCHIVE_FILE_SIZE_LIMIT = 20 * _fileSize.MB_IN_BYTES;
|
|
29
|
+
function getDeployFileExt(filename) {
|
|
30
|
+
const lower = filename.toLowerCase();
|
|
31
|
+
if (lower.endsWith('.tar.gz')) {
|
|
32
|
+
return '.tar.gz';
|
|
33
|
+
}
|
|
34
|
+
if (lower.endsWith('.tgz')) {
|
|
35
|
+
return '.tgz';
|
|
36
|
+
}
|
|
37
|
+
if (lower.endsWith('.zip')) {
|
|
38
|
+
return '.zip';
|
|
39
|
+
}
|
|
40
|
+
return _path.default.extname(lower);
|
|
41
|
+
}
|
|
42
|
+
function isDeployArchiveFile(filename) {
|
|
43
|
+
return ['.zip', '.tar.gz', '.tgz'].includes(getDeployFileExt(filename));
|
|
44
|
+
}
|
|
45
|
+
async function findLargeArchiveFilesInZipArchive(filePath) {
|
|
46
|
+
const zipFile = new _nodeStreamZip.default.async({
|
|
47
|
+
file: filePath
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
const zipEntries = await zipFile.entries();
|
|
51
|
+
return Object.values(zipEntries).filter(entry => !entry.isDirectory && isDeployArchiveFile(entry.name) && entry.size > LARGE_ARCHIVE_FILE_SIZE_LIMIT).map(entry => ({
|
|
52
|
+
path: entry.name,
|
|
53
|
+
size: entry.size
|
|
54
|
+
}));
|
|
55
|
+
} finally {
|
|
56
|
+
await zipFile.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function findLargeArchiveFilesInTarArchive(filePath) {
|
|
60
|
+
const largeArchiveFiles = [];
|
|
61
|
+
await tar.list({
|
|
62
|
+
file: filePath,
|
|
63
|
+
onReadEntry: entry => {
|
|
64
|
+
if (entry.type === 'File' && isDeployArchiveFile(entry.path) && entry.size > LARGE_ARCHIVE_FILE_SIZE_LIMIT) {
|
|
65
|
+
largeArchiveFiles.push({
|
|
66
|
+
path: entry.path,
|
|
67
|
+
size: entry.size
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return largeArchiveFiles;
|
|
73
|
+
}
|
|
74
|
+
async function findLargeArchiveFilesInDeployArchive(filePath) {
|
|
75
|
+
const ext = getDeployFileExt(filePath);
|
|
76
|
+
if (ext === '.zip') {
|
|
77
|
+
return findLargeArchiveFilesInZipArchive(filePath);
|
|
78
|
+
}
|
|
79
|
+
if (ext === '.tar.gz' || ext === '.tgz') {
|
|
80
|
+
return findLargeArchiveFilesInTarArchive(filePath);
|
|
81
|
+
}
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
25
84
|
|
|
26
85
|
/**
|
|
27
86
|
* Check if a file has a valid extension
|
|
@@ -30,10 +89,7 @@ const macosxDir = '__MACOSX';
|
|
|
30
89
|
* @returns {boolean} True if the extension is valid
|
|
31
90
|
*/
|
|
32
91
|
function validateDeployFileExt(filename) {
|
|
33
|
-
|
|
34
|
-
if (ext === '.gz' && _path.default.extname(_path.default.basename(filename, ext)) === '.tar') {
|
|
35
|
-
ext = '.tar.gz';
|
|
36
|
-
}
|
|
92
|
+
const ext = getDeployFileExt(filename);
|
|
37
93
|
if (!['.zip', '.tar.gz', '.tgz'].includes(ext)) {
|
|
38
94
|
exit.withError(errorMessages.invalidExt);
|
|
39
95
|
}
|
|
@@ -4,9 +4,8 @@ exports.__esModule = true;
|
|
|
4
4
|
exports.fileLineValidations = fileLineValidations;
|
|
5
5
|
exports.getReadInterface = getReadInterface;
|
|
6
6
|
var _debug = _interopRequireDefault(require("debug"));
|
|
7
|
-
var
|
|
7
|
+
var _nodeEvents = require("node:events");
|
|
8
8
|
var _promises = require("node:fs/promises");
|
|
9
|
-
var _nodeReadline = require("node:readline");
|
|
10
9
|
var exit = _interopRequireWildcard(require("../../lib/cli/exit"));
|
|
11
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); }
|
|
12
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -18,11 +17,8 @@ async function getReadInterface(filename) {
|
|
|
18
17
|
} catch {
|
|
19
18
|
exit.withError('The file at the provided path is either missing or not readable. Please check the input and try again.');
|
|
20
19
|
}
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
fd
|
|
24
|
-
}),
|
|
25
|
-
output: undefined
|
|
20
|
+
return fd.readLines({
|
|
21
|
+
encoding: 'binary'
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
24
|
async function fileLineValidations(appId, envId, fileName, validations, searchReplace) {
|
|
@@ -41,8 +37,7 @@ async function fileLineValidations(appId, envId, fileName, validations, searchRe
|
|
|
41
37
|
});
|
|
42
38
|
|
|
43
39
|
// Block until the processing completes
|
|
44
|
-
await
|
|
45
|
-
readInterface.close();
|
|
40
|
+
await (0, _nodeEvents.once)(readInterface, 'close');
|
|
46
41
|
return Promise.all(validations.map(validation => {
|
|
47
42
|
if (Object.hasOwn(validation, 'postLineExecutionProcessing') && typeof validation.postLineExecutionProcessing === 'function') {
|
|
48
43
|
return validation.postLineExecutionProcessing({
|
|
@@ -6,6 +6,7 @@ exports.validateFilename = validateFilename;
|
|
|
6
6
|
exports.validateImportFileExtension = void 0;
|
|
7
7
|
var _singleLineLog = require("@wwa/single-line-log");
|
|
8
8
|
var _chalk = _interopRequireDefault(require("chalk"));
|
|
9
|
+
var _nodeEvents = require("node:events");
|
|
9
10
|
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
10
11
|
var exit = _interopRequireWildcard(require("../../lib/cli/exit"));
|
|
11
12
|
var _clientFileUploader = require("../../lib/client-file-uploader");
|
|
@@ -472,8 +473,7 @@ const validate = async (filename, options = DEFAULT_VALIDATION_OPTIONS) => {
|
|
|
472
473
|
});
|
|
473
474
|
|
|
474
475
|
// Block until the processing completes
|
|
475
|
-
await
|
|
476
|
-
readInterface.close();
|
|
476
|
+
await (0, _nodeEvents.once)(readInterface, 'close');
|
|
477
477
|
await postLineExecutionProcessing({
|
|
478
478
|
isImport: options.isImport,
|
|
479
479
|
skipChecks: options.skipChecks
|
|
@@ -16,6 +16,7 @@ Goal: remove the abandoned `args` package, keep CLI behavior stable, and support
|
|
|
16
16
|
- `_opts` controls are still honored: app/env context fetch, confirmation gating, output formatting, wildcard command handling, required positional args.
|
|
17
17
|
- Shared formatting/output and telemetry hooks are still in the wrapper path.
|
|
18
18
|
- Local nested subcommand dispatch still works via sibling executable resolution.
|
|
19
|
+
- Short-option equals normalization: `-x=value` is rewritten to `-x value` for short options expecting values before parsing; boolean short flags are not affected. Tracked option defaults are applied after parsing when the option remains undefined post-parse, preserving default+parser parity.
|
|
19
20
|
|
|
20
21
|
## Post-Migration Hardening
|
|
21
22
|
|
package/docs/RELEASING.md
CHANGED
|
@@ -51,6 +51,8 @@ A few steps should be completed before releasing:
|
|
|
51
51
|
|
|
52
52
|
1. You have completed [final testing before deployment](TESTING.md#final-testing-before-releasing).
|
|
53
53
|
|
|
54
|
+
1. Run the release smoke tests to verify high-risk CLI parser/dispatch commands. First ensure the binaries are built (`npm run build`), then run `npm run smoke:release`. This tests option parsing, short-option equals syntax, and command routing against the built binaries.
|
|
55
|
+
|
|
54
56
|
1. The pre-publish [script](https://github.com/Automattic/vip-cli/blob/trunk/helpers/prepublishOnly.js) has been run. This script performs some confidence checks to avoid common mistakes.
|
|
55
57
|
|
|
56
58
|
1. Finally, release your changes as a [new minor or major NPM version](#releasing-a-new-version).
|
package/docs/TESTING.md
CHANGED
|
@@ -64,4 +64,19 @@ VIP_PROXY="" API_HOST=http://localhost:4000 node ./dist/bin/vip -- wp option get
|
|
|
64
64
|
|
|
65
65
|
## Final testing before releasing
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Before releasing a new version, run the release smoke tests to verify high-risk CLI commands:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run smoke:release
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This script tests critical parser/dispatch paths for commands like `logs`, `slowlogs`, `wp`, and `dev-env shell`. It verifies:
|
|
74
|
+
|
|
75
|
+
- Short-option equals syntax (e.g., `-l=10`)
|
|
76
|
+
- Default value explicit matches
|
|
77
|
+
- Option-only and separator/subcommand handling
|
|
78
|
+
- Command routing without requiring credentials, API access, or Docker/Lando
|
|
79
|
+
|
|
80
|
+
The tests target the built `dist/bin/*.js` binaries, so ensure `npm run build` has been run first. All tests use `--help` or other non-executing forms to avoid side effects.
|
|
81
|
+
|
|
82
|
+
Exit code 0 indicates all tests passed; non-zero indicates failures that should be reviewed before release.
|
package/helpers/build-sea.js
CHANGED