@automattic/vip 3.3.1 → 3.4.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/dist/bin/vip-app-deploy-validate.js +50 -0
- package/dist/bin/vip-app-deploy.js +1 -1
- package/dist/bin/vip-config-software-update.js +1 -1
- package/dist/bin/vip-dev-env-create.js +1 -1
- package/dist/bin/vip-sync.js +5 -5
- package/dist/lib/cli/command.js +8 -8
- package/dist/lib/dev-environment/dev-environment-cli.js +2 -1
- package/dist/lib/dev-environment/dev-environment-core.js +1 -0
- package/dist/lib/dev-environment/dev-environment-lando.js +0 -2
- package/dist/lib/validations/custom-deploy.js +194 -4
- package/docs/CHANGELOG.md +11 -0
- package/npm-shrinkwrap.json +342 -160
- package/package.json +6 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* External dependencies
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
exports.__esModule = true;
|
|
9
|
+
exports.appDeployValidateCmd = appDeployValidateCmd;
|
|
10
|
+
var _chalk = _interopRequireDefault(require("chalk"));
|
|
11
|
+
var _debug = _interopRequireDefault(require("debug"));
|
|
12
|
+
var _path = require("path");
|
|
13
|
+
var _command = _interopRequireDefault(require("../lib/cli/command"));
|
|
14
|
+
var _clientFileUploader = require("../lib/client-file-uploader");
|
|
15
|
+
var _customDeploy = require("../lib/custom-deploy/custom-deploy");
|
|
16
|
+
var _tracker = require("../lib/tracker");
|
|
17
|
+
var _customDeploy2 = require("../lib/validations/custom-deploy");
|
|
18
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
19
|
+
/**
|
|
20
|
+
* Internal dependencies
|
|
21
|
+
*/
|
|
22
|
+
const debug = (0, _debug.default)('@automattic/vip:bin:vip-app-deploy-validate');
|
|
23
|
+
async function appDeployValidateCmd(arg = [], opts = {}) {
|
|
24
|
+
const app = opts.app;
|
|
25
|
+
const env = opts.env;
|
|
26
|
+
const [fileName] = arg;
|
|
27
|
+
const fileMeta = await (0, _clientFileUploader.getFileMeta)(fileName);
|
|
28
|
+
debug('Options: ', opts);
|
|
29
|
+
debug('Args: ', arg);
|
|
30
|
+
const track = _tracker.trackEventWithEnv.bind(null, app, env);
|
|
31
|
+
debug('Validating file...');
|
|
32
|
+
await (0, _customDeploy.validateFile)(app, env, fileMeta);
|
|
33
|
+
await track('deploy_validate_app_command_execute');
|
|
34
|
+
const ext = (0, _path.extname)(fileName);
|
|
35
|
+
if (ext === '.zip') {
|
|
36
|
+
(0, _customDeploy2.validateZipFile)(fileName);
|
|
37
|
+
} else {
|
|
38
|
+
await (0, _customDeploy2.validateTarFile)(fileName);
|
|
39
|
+
}
|
|
40
|
+
console.log(_chalk.default.green('✓ Compressed file has been successfully validated with no errors!'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Command examples for the `vip app deploy validate` help prompt
|
|
44
|
+
const examples = [{
|
|
45
|
+
usage: 'vip app @mysite.develop deploy validate <file.zip|file.tar.gz>',
|
|
46
|
+
description: 'Validate the compressed file to see if it can be deployed to your site'
|
|
47
|
+
}];
|
|
48
|
+
void (0, _command.default)({
|
|
49
|
+
requiredArgs: 1
|
|
50
|
+
}).examples(examples).option('app', 'The application name or ID').option('env', 'The environment name or ID').argv(process.argv, appDeployValidateCmd);
|
|
@@ -232,4 +232,4 @@ const examples = [
|
|
|
232
232
|
}];
|
|
233
233
|
void (0, _command.default)({
|
|
234
234
|
requiredArgs: 1
|
|
235
|
-
}).examples(examples).option('message', 'Custom message for deploy').option('skip-confirmation', 'Skip confirmation prompt').option('force', 'Skip confirmation prompt (deprecated)').option('app', 'The application name or ID').option('env', 'The environment name or ID').argv(process.argv, appDeployCmd);
|
|
235
|
+
}).command('validate', 'Validate a file before deploying in Custom Deployments').examples(examples).option('message', 'Custom message for deploy').option('skip-confirmation', 'Skip confirmation prompt').option('force', 'Skip confirmation prompt (deprecated)').option('app', 'The application name or ID').option('env', 'The environment name or ID').argv(process.argv, appDeployCmd);
|
|
@@ -41,7 +41,7 @@ const cmd = (0, _command.default)({
|
|
|
41
41
|
wildcardCommand: true,
|
|
42
42
|
usage
|
|
43
43
|
}).examples(examples);
|
|
44
|
-
cmd.option('yes', '
|
|
44
|
+
cmd.option('yes', 'Skip the confirmation prompt and automatically submit "y".');
|
|
45
45
|
cmd.argv(process.argv, async (arg, opt) => {
|
|
46
46
|
const {
|
|
47
47
|
app,
|
|
@@ -33,7 +33,7 @@ const examples = [{
|
|
|
33
33
|
}];
|
|
34
34
|
const cmd = (0, _command.default)({
|
|
35
35
|
usage
|
|
36
|
-
}).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('title', 'A descriptive value for the WordPress Site Title. Default is "VIP Dev"
|
|
36
|
+
}).option('slug', 'A unique name for a local environment. Default is "vip-local".', undefined, _devEnvironmentCli.processSlug).option('title', 'A descriptive value for the WordPress Site Title. Default is "VIP Dev".').option('multisite', 'Create environment as a multisite. Accepts "y" for a subdomain multisite, "subdirectory" (recommended) for a subdirectory multisite, or "false". Default is "y".', undefined, _devEnvironmentCli.processStringOrBooleanOption);
|
|
37
37
|
(0, _devEnvironmentCli.addDevEnvConfigurationOptions)(cmd);
|
|
38
38
|
cmd.examples(examples);
|
|
39
39
|
cmd.argv(process.argv, async (arg, opt) => {
|
package/dist/bin/vip-sync.js
CHANGED
|
@@ -82,10 +82,10 @@ const appQuery = `id,name,environments{
|
|
|
82
82
|
let environment = application.environments.find(env => env.id === opts.env.id);
|
|
83
83
|
if (syncing) {
|
|
84
84
|
if (environment.syncProgress.status === 'running') {
|
|
85
|
-
console.log(_chalk.default.yellow('Note:'), 'A data sync is already running');
|
|
85
|
+
console.log(_chalk.default.yellow('Note:'), 'A data sync is already running.');
|
|
86
86
|
} else {
|
|
87
|
-
console.log(_chalk.default.yellow('Note:'), 'Someone recently ran a data sync on this site');
|
|
88
|
-
console.log(_chalk.default.yellow('Note:'), 'Please wait a few minutes before trying again');
|
|
87
|
+
console.log(_chalk.default.yellow('Note:'), 'Someone recently ran a data sync on this site.');
|
|
88
|
+
console.log(_chalk.default.yellow('Note:'), 'Please wait a few minutes before trying again.');
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
console.log();
|
|
@@ -157,14 +157,14 @@ const appQuery = `id,name,environments{
|
|
|
157
157
|
await (0, _tracker.trackEvent)('sync_command_error', {
|
|
158
158
|
error: 'API returned `failed` status'
|
|
159
159
|
});
|
|
160
|
-
out.push(`${marks.failed} Data Sync is finished for ${opts.app.name}
|
|
160
|
+
out.push(`${marks.failed} Data Sync is finished for ${opts.app.name}.`);
|
|
161
161
|
out.push('');
|
|
162
162
|
break;
|
|
163
163
|
case 'success':
|
|
164
164
|
default:
|
|
165
165
|
clearInterval(progress);
|
|
166
166
|
await (0, _tracker.trackEvent)('sync_command_success');
|
|
167
|
-
out.push(`${marks.success} Data Sync is finished for ${opts.app.name}
|
|
167
|
+
out.push(`${marks.success} Data Sync is finished for ${opts.app.name}.`);
|
|
168
168
|
out.push('');
|
|
169
169
|
break;
|
|
170
170
|
}
|
package/dist/lib/cli/command.js
CHANGED
|
@@ -426,7 +426,7 @@ _args.default.argv = async function (argv, cb) {
|
|
|
426
426
|
});
|
|
427
427
|
options.exportFileErrorsToJson = Object.hasOwn(options, 'exportFileErrorsToJson') && Boolean(options.exportFileErrorsToJson) && !['false', 'no'].includes(options.exportFileErrorsToJson);
|
|
428
428
|
info.push({
|
|
429
|
-
key: 'Export any file errors encountered to a JSON file instead of a plain text file',
|
|
429
|
+
key: 'Export any file errors encountered to a JSON file instead of a plain text file.',
|
|
430
430
|
value: options.exportFileErrorsToJson ? '✅ Yes' : `${_chalk.default.red('x')} No`
|
|
431
431
|
});
|
|
432
432
|
break;
|
|
@@ -508,22 +508,22 @@ function _default(opts) {
|
|
|
508
508
|
...opts
|
|
509
509
|
};
|
|
510
510
|
if (_opts.appContext || _opts.requireConfirm) {
|
|
511
|
-
_args.default.option('app', '
|
|
511
|
+
_args.default.option('app', 'Target an application. Accepts a string value for the application name or an integer for the application ID.');
|
|
512
512
|
}
|
|
513
513
|
if (_opts.envContext || _opts.childEnvContext) {
|
|
514
|
-
_args.default.option('env', '
|
|
514
|
+
_args.default.option('env', 'Target an environment. Accepts a string value for the environment type.');
|
|
515
515
|
}
|
|
516
516
|
if (_opts.requireConfirm) {
|
|
517
|
-
_args.default.option('force', 'Skip confirmation', false);
|
|
517
|
+
_args.default.option('force', 'Skip confirmation.', false);
|
|
518
518
|
}
|
|
519
519
|
if (_opts.format) {
|
|
520
|
-
_args.default.option('format', '
|
|
520
|
+
_args.default.option('format', 'Render output in a particular format. Accepts "table" (default), "csv", and "json".', 'table');
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
// Add help and version to all subcommands
|
|
524
|
-
_args.default.option('help', '
|
|
525
|
-
_args.default.option('version', '
|
|
526
|
-
_args.default.option('debug', '
|
|
524
|
+
_args.default.option('help', 'Retrieve a description, examples, and available options for a (sub)command.');
|
|
525
|
+
_args.default.option('version', 'Retrieve the version number of VIP-CLI currently installed on the local machine.');
|
|
526
|
+
_args.default.option('debug', 'Generate verbose output during command execution to help identify or fix errors or bugs.');
|
|
527
527
|
return _args.default;
|
|
528
528
|
}
|
|
529
529
|
function getEnvIdentifier(env) {
|
|
@@ -620,9 +620,10 @@ function processVersionOption(value) {
|
|
|
620
620
|
}
|
|
621
621
|
return value?.toString() ?? '';
|
|
622
622
|
}
|
|
623
|
+
const phpVersionsSupported = Object.keys(_devEnvironment.DEV_ENVIRONMENT_PHP_VERSIONS).join(', ');
|
|
623
624
|
function addDevEnvConfigurationOptions(command) {
|
|
624
625
|
// We leave the third parameter to undefined on some because the defaults are handled in preProcessInstanceData()
|
|
625
|
-
return command.option('wordpress', '
|
|
626
|
+
return command.option('wordpress', 'Manage the version of WordPress. Accepts a string value for major versions (6.x). Defaults to the most recent version of WordPress.', undefined, processVersionOption).option(['u', 'mu-plugins'], 'Manage the source for VIP MU plugins. Accepts "demo" (default) for a read-only image of the staging branch, or a path to a built copy of VIP MU plugins on the local machine.').option('app-code', 'Manage the source for application code. Accepts "demo" (default) for a read-only image of WordPress VIP skeleton application code, or a path to a VIP formatted application repo on the local machine.').option('phpmyadmin', 'Enable or disable phpMyAdmin, disabled by default. Accepts "y" (default value) to enable or "n" to disable. When enabled, refer to the value of "PHPMYADMIN URLS" in the information output for a local environment for the URL to access phpMyAdmin.', undefined, processBooleanOption).option('xdebug', 'Enable or disable XDebug, disabled by default. Accepts "y" (default value) to enable or "n" to disable.', undefined, processBooleanOption).option('xdebug_config', 'Override some default configuration settings for Xdebug. Accepts a string value that is assigned to the XDEBUG_CONFIG environment variable.').option('elasticsearch', 'Enable or disable Elasticsearch (required by Enterprise Search), disabled by default. Accepts "y" (default value) to enable or "n" to disable.', undefined, processBooleanOption).option(['r', 'media-redirect-domain'], 'Configure media files to be proxied from a VIP Platform environment. Accepts a string value for the primary domain of the VIP Platform environment or "n" to disable the media proxy.', undefined, processMediaRedirectDomainOption).option('php', `Manage the version of PHP. Accepts a string value for minor versions: ${phpVersionsSupported}`, undefined, processVersionOption).option(['A', 'mailpit'], 'Enable or disable Mailpit, disabled by default. Accepts "y" (default value) to enable or "n" to disable.', undefined, processBooleanOption).option(['H', 'photon'], 'Enable or disable Photon, disabled by default. Accepts "y" (default value) to enable or "n" to disable.', undefined, processBooleanOption);
|
|
626
627
|
}
|
|
627
628
|
|
|
628
629
|
/**
|
|
@@ -611,6 +611,7 @@ async function maybeUpdateVersion(lando, slug) {
|
|
|
611
611
|
const currentVersion = envData.version;
|
|
612
612
|
console.log('Current local environment version is: ' + _chalk.default.yellow(currentVersion));
|
|
613
613
|
if (!currentVersion || _semver.default.lt(currentVersion, _devEnvironment.DEV_ENVIRONMENT_VERSION)) {
|
|
614
|
+
envData.pullAfter = undefined;
|
|
614
615
|
await updateEnvironment(lando, envData);
|
|
615
616
|
console.log('Local environment version updated to: ' + _chalk.default.green(_devEnvironment.DEV_ENVIRONMENT_VERSION));
|
|
616
617
|
return true;
|
|
@@ -180,13 +180,11 @@ async function bootstrapLando() {
|
|
|
180
180
|
registryResolvable = false;
|
|
181
181
|
}
|
|
182
182
|
const pull = registryResolvable && (instanceData.pullAfter ?? 0) < Date.now();
|
|
183
|
-
console.log(pull);
|
|
184
183
|
if (Array.isArray(data.opts.pullable) && Array.isArray(data.opts.local) && data.opts.local.length === 0 && !pull) {
|
|
185
184
|
// Setting `data.opts.pullable` to an empty array prevents Lando from pulling images with `docker pull`.
|
|
186
185
|
// Note that if some of the images are not available, they will still be pulled by `docker-compose`.
|
|
187
186
|
data.opts.local = data.opts.pullable;
|
|
188
187
|
data.opts.pullable = [];
|
|
189
|
-
console.log(data.opts);
|
|
190
188
|
}
|
|
191
189
|
if (pull || !instanceData.pullAfter) {
|
|
192
190
|
instanceData.pullAfter = Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
@@ -3,11 +3,27 @@
|
|
|
3
3
|
exports.__esModule = true;
|
|
4
4
|
exports.validateDeployFileExt = validateDeployFileExt;
|
|
5
5
|
exports.validateFilename = validateFilename;
|
|
6
|
+
exports.validateName = validateName;
|
|
7
|
+
exports.validateTarFile = validateTarFile;
|
|
8
|
+
exports.validateZipFile = validateZipFile;
|
|
9
|
+
var _admZip = _interopRequireDefault(require("adm-zip"));
|
|
10
|
+
var _nodeFs = require("node:fs");
|
|
6
11
|
var _path = _interopRequireDefault(require("path"));
|
|
12
|
+
var tar = _interopRequireWildcard(require("tar"));
|
|
7
13
|
var exit = _interopRequireWildcard(require("../../lib/cli/exit"));
|
|
8
14
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
9
15
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
10
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
|
+
const errorMessages = {
|
|
18
|
+
missingThemes: 'Missing `themes` directory from root folder!',
|
|
19
|
+
symlink: 'Symlink detected: ',
|
|
20
|
+
singleRootDir: 'The compressed file must contain a single root directory!',
|
|
21
|
+
invalidExt: 'Invalid file extension. Please provide a .zip, .tar.gz, or a .tgz file.',
|
|
22
|
+
invalidChars: (filename, invalidChars) => `Filename ${filename} contains disallowed characters: ${invalidChars}`
|
|
23
|
+
};
|
|
24
|
+
const symlinkIgnorePattern = /\/node_modules\/[^/]+\/\.bin\//;
|
|
25
|
+
const macosxDir = '__MACOSX';
|
|
26
|
+
|
|
11
27
|
/**
|
|
12
28
|
* Check if a file has a valid extension
|
|
13
29
|
*
|
|
@@ -20,7 +36,7 @@ function validateDeployFileExt(filename) {
|
|
|
20
36
|
ext = '.tar.gz';
|
|
21
37
|
}
|
|
22
38
|
if (!['.zip', '.tar.gz', '.tgz'].includes(ext)) {
|
|
23
|
-
exit.withError(
|
|
39
|
+
exit.withError(errorMessages.invalidExt);
|
|
24
40
|
}
|
|
25
41
|
}
|
|
26
42
|
|
|
@@ -31,9 +47,183 @@ function validateDeployFileExt(filename) {
|
|
|
31
47
|
*/
|
|
32
48
|
function validateFilename(filename) {
|
|
33
49
|
const re = /^[a-z0-9\-_.]+$/i;
|
|
34
|
-
|
|
35
|
-
// Exits if filename contains anything outside a-z A-Z - _ .
|
|
36
50
|
if (!re.test(filename)) {
|
|
37
|
-
exit.withError('
|
|
51
|
+
exit.withError(errorMessages.invalidChars(filename, '[0-9,a-z,A-Z,-,_,.]'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate the name of a file for disallowed characters
|
|
57
|
+
*
|
|
58
|
+
* @param {string} name The name of the file
|
|
59
|
+
* @param {bool} isDirectory Whether the file is a directory
|
|
60
|
+
*/
|
|
61
|
+
function validateName(name, isDirectory) {
|
|
62
|
+
if (name.startsWith('._')) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const invalidCharsPattern = isDirectory ? /[!:*?"<>|']|^\.\..*$/ : /[!/:*?"<>|']|^\.\..*$/;
|
|
66
|
+
const errorMessage = errorMessages.invalidChars(name, isDirectory ? '[!:*?"<>|\'/^..]+' : '[!/:*?"<>|\'/^..]+');
|
|
67
|
+
if (invalidCharsPattern.test(name)) {
|
|
68
|
+
exit.withError(errorMessage);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate the existence of a symlink in a zip file. Ignores symlinks in node_modules/.bin/
|
|
74
|
+
*
|
|
75
|
+
* @param {IZipEntry} entry The zip entry to validate
|
|
76
|
+
*/
|
|
77
|
+
function validateZipSymlink(entry) {
|
|
78
|
+
if (symlinkIgnorePattern.test(entry.entryName)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const madeBy = entry.header.made >> 8; // eslint-disable-line no-bitwise
|
|
82
|
+
const errorMsg = errorMessages.symlink + entry.name;
|
|
83
|
+
|
|
84
|
+
// DOS
|
|
85
|
+
/* eslint-disable no-bitwise */
|
|
86
|
+
if (madeBy === 0 && (entry.attr & 0x0400) === 0x0400) {
|
|
87
|
+
exit.withError(errorMsg);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Unix
|
|
91
|
+
if (madeBy === 3 && (entry.attr >>> 16 & _nodeFs.constants.S_IFLNK) === _nodeFs.constants.S_IFLNK) {
|
|
92
|
+
/* eslint-enable no-bitwise */
|
|
93
|
+
exit.withError(errorMsg);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate a zip entry for disallowed characters and symlinks.
|
|
99
|
+
* Ignores __MACOSX directories.
|
|
100
|
+
*
|
|
101
|
+
* @param {IZipEntry} entry The zip entry to validate
|
|
102
|
+
*/
|
|
103
|
+
function validateZipEntry(entry) {
|
|
104
|
+
if (entry.entryName.startsWith(macosxDir)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
validateName(entry.isDirectory ? entry.entryName : entry.name, entry.isDirectory);
|
|
108
|
+
validateZipSymlink(entry);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validate the existence of a themes directory in the root folder.
|
|
113
|
+
*
|
|
114
|
+
* @param {IZipEntry[]} zipEntries The zip entries to validate
|
|
115
|
+
*/
|
|
116
|
+
function validateZipThemes(rootFolder, zipEntries) {
|
|
117
|
+
const hasThemesDir = zipEntries.some(entry => entry.isDirectory && entry.entryName.startsWith(_path.default.join(rootFolder, 'themes/')));
|
|
118
|
+
if (!hasThemesDir) {
|
|
119
|
+
exit.withError(errorMessages.missingThemes);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validate a zip file for Custom Deployments.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} filePath The path to the zip file
|
|
127
|
+
*/
|
|
128
|
+
function validateZipFile(filePath) {
|
|
129
|
+
try {
|
|
130
|
+
const zipFile = new _admZip.default(filePath);
|
|
131
|
+
const zipEntries = zipFile.getEntries();
|
|
132
|
+
const rootDirs = zipEntries.filter(entry => entry.isDirectory && !entry.entryName.startsWith(macosxDir) && (entry.entryName.match(/\//g) || []).length === 1);
|
|
133
|
+
if (rootDirs.length !== 1) {
|
|
134
|
+
exit.withError(errorMessages.singleRootDir);
|
|
135
|
+
}
|
|
136
|
+
const rootFolder = rootDirs[0].entryName;
|
|
137
|
+
validateZipThemes(rootFolder, zipEntries);
|
|
138
|
+
zipEntries.forEach(entry => validateZipEntry(entry));
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const err = error;
|
|
141
|
+
exit.withError(`Error reading file: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate the existence of a themes directory in the root folder in a tar file.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} rootFolder The root folder of the tar file
|
|
149
|
+
* @param {TarEntry[]} tarEntries The list of tar entries
|
|
150
|
+
*/
|
|
151
|
+
function validateTarThemes(rootFolder, tarEntries) {
|
|
152
|
+
const themesFolderPath = _path.default.join(rootFolder, 'themes/');
|
|
153
|
+
const themesFolderExists = tarEntries.some(entry => entry.path === themesFolderPath && entry.type === 'Directory');
|
|
154
|
+
if (!themesFolderExists) {
|
|
155
|
+
exit.withError(errorMessages.missingThemes);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate a tar entry for disallowed characters and symlinks.
|
|
161
|
+
*
|
|
162
|
+
* @param {TarEntry} entry The tar entry to validate
|
|
163
|
+
*/
|
|
164
|
+
function validateTarEntry(entry) {
|
|
165
|
+
if (entry.path.startsWith(macosxDir)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
validateTarSymlink(entry);
|
|
169
|
+
validateName(_path.default.basename(entry.path), entry.type === 'Directory');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate the existence of a symlink in a tar file. Ignores symlinks in node_modules/.bin/
|
|
174
|
+
*
|
|
175
|
+
* @param {TarEntry} entry The tar entry to validate for symlinks
|
|
176
|
+
*/
|
|
177
|
+
function validateTarSymlink(entry) {
|
|
178
|
+
if (symlinkIgnorePattern.test(entry.path)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (entry.type === 'SymbolicLink') {
|
|
182
|
+
exit.withError(errorMessages.symlink + entry.path);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate a tar file for Custom Deployments.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} filePath The path to the tar file
|
|
190
|
+
*/
|
|
191
|
+
async function validateTarFile(filePath) {
|
|
192
|
+
const tarEntries = [];
|
|
193
|
+
let rootFolder = null;
|
|
194
|
+
try {
|
|
195
|
+
await tar.list({
|
|
196
|
+
file: filePath,
|
|
197
|
+
onReadEntry: entry => {
|
|
198
|
+
if (entry.path.startsWith(macosxDir)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (entry.type !== 'File' && entry.type !== 'Directory' && entry.type !== 'SymbolicLink') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const isRootFolder = entry.type === 'Directory' && entry.path.endsWith('/') && (entry.path.match(/\//g) || []).length === 1;
|
|
205
|
+
if (isRootFolder) {
|
|
206
|
+
if (rootFolder === null) {
|
|
207
|
+
rootFolder = entry.path;
|
|
208
|
+
} else if (rootFolder !== entry.path) {
|
|
209
|
+
exit.withError(errorMessages.singleRootDir);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const entryInfo = {
|
|
213
|
+
path: entry.path,
|
|
214
|
+
type: entry.type,
|
|
215
|
+
mode: entry.mode
|
|
216
|
+
};
|
|
217
|
+
validateTarEntry(entryInfo);
|
|
218
|
+
tarEntries.push(entryInfo);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
if (!rootFolder) {
|
|
222
|
+
exit.withError(errorMessages.singleRootDir);
|
|
223
|
+
}
|
|
224
|
+
validateTarThemes(rootFolder, tarEntries);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const err = error;
|
|
227
|
+
exit.withError(err.message);
|
|
38
228
|
}
|
|
39
229
|
}
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 3.4.0
|
|
4
|
+
|
|
5
|
+
* Updating command option descriptions to follow the VIP-CLI style guide
|
|
6
|
+
* build(deps-dev): bump @types/node from 18.19.34 to 18.19.36
|
|
7
|
+
* Add `vip app deploy validate` command for Custom Deployments
|
|
8
|
+
* fix(dev-env): remove debug code
|
|
9
|
+
* fix(dev-env): force pull images on environment version update
|
|
10
|
+
* chore(deps): update `ws` to fix CVE-2024-37890
|
|
11
|
+
|
|
12
|
+
**Full Changelog**: https://github.com/Automattic/vip-cli/compare/3.3.1...3.4.0
|
|
13
|
+
|
|
3
14
|
### 3.3.1
|
|
4
15
|
|
|
5
16
|
* build(deps): bump step-security/harden-runner from 2.8.0 to 2.8.1
|