@automattic/vip 3.19.3-dev.0 → 3.20.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.
@@ -3,6 +3,7 @@
3
3
 
4
4
  exports.__esModule = true;
5
5
  exports.gates = gates;
6
+ exports.parseHeaders = parseHeaders;
6
7
  exports.promptToContinue = void 0;
7
8
  exports.validateAndGetTableNames = validateAndGetTableNames;
8
9
  var _chalk = _interopRequireDefault(require("chalk"));
@@ -86,8 +87,8 @@ const SQL_IMPORT_PREFLIGHT_PROGRESS_STEPS = [{
86
87
  */
87
88
  function isValidUrl(str) {
88
89
  try {
89
- new URL(str);
90
- return true;
90
+ const url = new URL(str);
91
+ return !/^[a-z]:$/iu.test(url.protocol);
91
92
  } catch {
92
93
  return false;
93
94
  }
@@ -102,6 +103,35 @@ function isValidMd5(md5) {
102
103
  return /^[a-f0-9]{32}$/i.test(md5);
103
104
  }
104
105
 
106
+ /**
107
+ * Parse and validate headers from CLI input
108
+ * @param {string|string[]} headers - Header(s) in "Name: Value" format
109
+ * @returns {Array<{name: string, value: string}>} Parsed headers
110
+ */
111
+ function parseHeaders(headers) {
112
+ if (!headers) {
113
+ return [];
114
+ }
115
+ const headerArray = Array.isArray(headers) ? headers : [headers];
116
+ const parsedHeaders = [];
117
+ for (const header of headerArray) {
118
+ const colonIndex = header.indexOf(':');
119
+ if (colonIndex === -1) {
120
+ exit.withError(`Invalid header format: "${header}". Expected format: "Name: Value"`);
121
+ }
122
+ const name = header.substring(0, colonIndex).trim();
123
+ const value = header.substring(colonIndex + 1).trim();
124
+ if (!name) {
125
+ exit.withError(`Invalid header format: "${header}". Header name cannot be empty.`);
126
+ }
127
+ parsedHeaders.push({
128
+ name,
129
+ value
130
+ });
131
+ }
132
+ return parsedHeaders;
133
+ }
134
+
105
135
  /**
106
136
  * @param {import('../lib/site-import/db-file-import').AppForImport} app
107
137
  * @param {import('../lib/site-import/db-file-import').EnvForImport} env
@@ -226,6 +256,11 @@ const examples = [
226
256
  usage: 'vip @example-app.develop import sql https://example.org/file.sql',
227
257
  description: 'Import a remote SQL database backup file from the URL with MD5 hash verification to the develop environment of the "example-app" application.'
228
258
  },
259
+ // URL import with HTTP Basic Auth
260
+ {
261
+ usage: 'vip @example-app.develop import sql https://username:password@example.org/file.sql',
262
+ description: 'Import a remote SQL database backup file from a URL that requires HTTP Basic Authentication.'
263
+ },
229
264
  // `search-replace` flag
230
265
  {
231
266
  usage: 'vip @example-app.develop import sql file.sql --search-replace="from.example.com,to.example.com" --search-replace="example.com/from,example.com/to"',
@@ -241,6 +276,16 @@ const examples = [
241
276
  usage: 'vip @example-app.develop import sql file.sql --search-replace="https://from.example.com,https://to.example.com" --output="updated-file.sql"',
242
277
  description: 'Create a copy of the imported file with the completed search and replace operations and save it locally to a file named "updated-file.sql".'
243
278
  },
279
+ // URL import with headers
280
+ {
281
+ usage: 'vip @example-app.develop import sql https://example.org/file.sql --header "Authorization: Bearer token"',
282
+ description: 'Import a remote SQL database backup file from a URL with custom authorization header.'
283
+ },
284
+ // URL import with multiple headers
285
+ {
286
+ usage: 'vip @example-app.develop import sql https://example.org/file.sql --header "Authorization: Bearer token" --header "User-Agent: VIP CLI"',
287
+ description: 'Import a remote SQL database backup file from a URL with multiple custom headers.'
288
+ },
244
289
  // `sql status` subcommand
245
290
  {
246
291
  usage: 'vip @example-app.develop import sql status',
@@ -374,7 +419,7 @@ const displayPlaybook = ({
374
419
  requiredArgs: 1,
375
420
  module: 'import-sql',
376
421
  usage
377
- }).command('status', 'Check the status of a SQL database import currently in progress.').option('skip-validate', 'Do not perform file validation prior to import. If the file contains unsupported entries, the import is likely to fail.').option('search-replace', 'Search for a string in the SQL file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to").').option('in-place', 'Overwrite the local input file with the results of the search and replace operation prior to import.').option('output', 'The local file path to save a copy of the results from the search and replace operation when the --search-replace option is passed. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Imports data without putting the environment in maintenance mode. Available only for unlaunched environments. Caution: This may cause site instability during import.').option('md5', 'MD5 hash of the remote SQL file for verification. If not provided, the verification will not be performed.').examples(examples)
422
+ }).command('status', 'Check the status of a SQL database import currently in progress.').option('skip-validate', 'Do not perform file validation prior to import. If the file contains unsupported entries, the import is likely to fail.').option('search-replace', 'Search for a string in the SQL file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to").').option('in-place', 'Overwrite the local input file with the results of the search and replace operation prior to import.').option('output', 'The local file path to save a copy of the results from the search and replace operation when the --search-replace option is passed. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Imports data without putting the environment in maintenance mode. Available only for unlaunched environments. Caution: This may cause site instability during import.').option('md5', 'MD5 hash of the remote SQL file for verification. If not provided, the verification will not be performed.').option('header', 'Add a header to the request when downloading from a URL. Format: "Name: Value". Can be used multiple times.').examples(examples)
378
423
  // eslint-disable-next-line complexity
379
424
  .argv(process.argv, async (arg, opts) => {
380
425
  const {
@@ -385,7 +430,8 @@ const displayPlaybook = ({
385
430
  skipValidate,
386
431
  searchReplace,
387
432
  skipMaintenanceMode,
388
- md5
433
+ md5,
434
+ header
389
435
  } = opts;
390
436
  const {
391
437
  id: envId,
@@ -394,6 +440,12 @@ const displayPlaybook = ({
394
440
  const [fileNameOrURL] = arg;
395
441
  const isMultiSite = await (0, _isMultiSite.isMultiSiteInSiteMeta)(appId, envId);
396
442
  const isUrl = isValidUrl(fileNameOrURL);
443
+
444
+ // Parse and validate headers
445
+ const headers = parseHeaders(header);
446
+ if (!isUrl && headers.length > 0) {
447
+ console.log(_chalk.default.yellowBright('The --header parameter is only used for URL imports. It will be ignored for local file imports.'));
448
+ }
397
449
  if (isUrl && opts.inPlace) {
398
450
  console.log(_chalk.default.yellowBright('The --in-place option is not supported when importing from a URL. This option will be ignored.'));
399
451
  opts.inPlace = false;
@@ -527,7 +579,8 @@ Processing the SQL import for your environment...
527
579
  ...startImportVariables.input,
528
580
  url: fileNameOrURL,
529
581
  searchReplace: [],
530
- md5
582
+ md5,
583
+ urlHeaders: headers
531
584
  };
532
585
  } else {
533
586
  progressTracker.stepRunning('upload');
@@ -4,12 +4,11 @@ exports.__esModule = true;
4
4
  exports.BackupStorageAvailability = void 0;
5
5
  var _checkDiskSpace = _interopRequireDefault(require("check-disk-space"));
6
6
  var _enquirer = require("enquirer");
7
- var _os = _interopRequireDefault(require("os"));
8
7
  var _path = _interopRequireDefault(require("path"));
9
8
  var _shelljs = require("shelljs");
10
- var _xdgBasedir = _interopRequireDefault(require("xdg-basedir"));
11
9
  var _dockerMachineNotFoundError = require("./docker-machine-not-found-error");
12
10
  var _format = require("../cli/format");
11
+ var _xdgData = require("../xdg-data");
13
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
13
  const oneGiBInBytes = 1024 * 1024 * 1024;
15
14
  class BackupStorageAvailability {
@@ -31,7 +30,7 @@ class BackupStorageAvailability {
31
30
  return (0, _format.formatMetricBytes)(bytes);
32
31
  }
33
32
  async getStorageAvailableInVipPath() {
34
- const vipDir = _path.default.join(_xdgBasedir.default.data ?? _os.default.tmpdir(), 'vip');
33
+ const vipDir = _path.default.join((0, _xdgData.xdgData)(), 'vip');
35
34
  const diskSpace = await (0, _checkDiskSpace.default)(vipDir);
36
35
  return diskSpace.free;
37
36
  }
@@ -2,20 +2,20 @@
2
2
 
3
3
  exports.__esModule = true;
4
4
  exports.withError = withError;
5
- var _chalk = require("chalk");
5
+ var _chalk = _interopRequireDefault(require("chalk"));
6
6
  var _debug = _interopRequireDefault(require("debug"));
7
7
  var _env = _interopRequireDefault(require("../../lib/env"));
8
8
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
9
  function withError(message) {
10
10
  const msg = message instanceof Error ? message.message : message;
11
- console.error(`${(0, _chalk.red)('Error: ')} ${msg.replace(/^Error:\s*/, '')}`);
11
+ console.error(`${_chalk.default.red('Error: ')} ${msg.replace(/^Error:\s*/, '')}`);
12
12
 
13
13
  // Debug ouput is printed below error output both for information
14
14
  // hierarchy and to make it more likely that the user copies it to their
15
15
  // clipboard when dragging across output.
16
- console.log(`${(0, _chalk.yellow)('Debug: ')} VIP-CLI v${_env.default.app.version}, Node ${_env.default.node.version}, ${_env.default.os.name} ${_env.default.os.version} ${_env.default.os.arch}`);
16
+ console.log(`${_chalk.default.yellow('Debug: ')} VIP-CLI v${_env.default.app.version}, Node ${_env.default.node.version}, ${_env.default.os.name} ${_env.default.os.version} ${_env.default.os.arch}`);
17
17
  if (_debug.default.names.length > 0 && message instanceof Error) {
18
- console.error((0, _chalk.yellow)('Debug: '), message);
18
+ console.error(_chalk.default.yellow('Debug: '), message);
19
19
  }
20
20
  process.exit(1);
21
21
  }
@@ -35,7 +35,6 @@ var _promises = require("node:fs/promises");
35
35
  var _nodePath = _interopRequireDefault(require("node:path"));
36
36
  var _semver = _interopRequireDefault(require("semver"));
37
37
  var _uuid = require("uuid");
38
- var _xdgBasedir = _interopRequireDefault(require("xdg-basedir"));
39
38
  var _devEnvironmentCli = require("./dev-environment-cli");
40
39
  var _devEnvironmentLando = require("./dev-environment-lando");
41
40
  var _app = _interopRequireDefault(require("../api/app"));
@@ -44,6 +43,7 @@ var _devEnvironment = require("../constants/dev-environment");
44
43
  var _proxyAgent = require("../http/proxy-agent");
45
44
  var _searchAndReplace = require("../search-and-replace");
46
45
  var _userError = _interopRequireDefault(require("../user-error"));
46
+ var _xdgData = require("../xdg-data");
47
47
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
48
48
  const debug = (0, _debug.default)('@automattic/vip:bin:dev-environment');
49
49
  const landoFileTemplatePath = _nodePath.default.join(__dirname, '..', '..', '..', 'assets', 'dev-env.lando.template.yml.ejs');
@@ -58,15 +58,6 @@ const integrationsConfigBackupFileName = 'integrations.json.bak';
58
58
  const uploadPathString = 'uploads';
59
59
  const nginxPathString = 'nginx';
60
60
  const integrationsConfigPathString = 'integrations-config';
61
- function xdgDataDirectory() {
62
- if (_xdgBasedir.default.data) {
63
- return _xdgBasedir.default.data;
64
- }
65
-
66
- // This should not happen. If it does, this means that the system was unable to find user's home directory.
67
- // If so, this does not leave us many options as to where to store the data.
68
- throw new Error('Unable to determine data directory.');
69
- }
70
61
  async function startEnvironment(lando, slug, options) {
71
62
  debug('Will start an environment', slug);
72
63
  const instancePath = getEnvironmentPath(slug);
@@ -175,7 +166,7 @@ async function destroyEnvironment(lando, slug, removeFiles) {
175
166
  } else {
176
167
  debug("Lando file doesn't exist, skipping lando destroy.");
177
168
  }
178
- await _nodeFs.default.promises.rm(_nodePath.default.join(xdgDataDirectory(), 'vip', 'lando', 'compose', (0, _utils.dockerComposify)(slug)), {
169
+ await _nodeFs.default.promises.rm(_nodePath.default.join((0, _xdgData.xdgData)(), 'vip', 'lando', 'compose', (0, _utils.dockerComposify)(slug)), {
179
170
  force: true,
180
171
  recursive: true
181
172
  });
@@ -406,7 +397,7 @@ async function prepareLandoEnv(lando, instanceData, instancePath, integrationsCo
406
397
  }
407
398
  }
408
399
  function getAllEnvironmentNames() {
409
- const mainEnvironmentPath = xdgDataDirectory();
400
+ const mainEnvironmentPath = (0, _xdgData.xdgData)();
410
401
  const baseDir = _nodePath.default.join(mainEnvironmentPath, 'vip', 'dev-environment');
411
402
  const doWeHaveAnyEnvironment = _nodeFs.default.existsSync(baseDir);
412
403
  let envNames = [];
@@ -423,7 +414,7 @@ function getEnvironmentPath(name) {
423
414
  if (!name) {
424
415
  throw new Error('Name was not provided');
425
416
  }
426
- const mainEnvironmentPath = xdgDataDirectory();
417
+ const mainEnvironmentPath = (0, _xdgData.xdgData)();
427
418
  return _nodePath.default.join(mainEnvironmentPath, 'vip', 'dev-environment', String(name));
428
419
  }
429
420
  async function getApplicationInformation(appId, envType) {
@@ -721,7 +712,7 @@ async function isVersionListExpired(cacheFile, ttl) {
721
712
  */
722
713
  async function getVersionList() {
723
714
  let res;
724
- const mainEnvironmentPath = xdgDataDirectory();
715
+ const mainEnvironmentPath = (0, _xdgData.xdgData)();
725
716
  const cacheFilePath = _nodePath.default.join(mainEnvironmentPath, 'vip');
726
717
  const cacheFile = _nodePath.default.join(cacheFilePath, _devEnvironment.DEV_ENVIRONMENT_WORDPRESS_CACHE_KEY);
727
718
  // Handle from cache
@@ -27,11 +27,11 @@ var _promises2 = require("node:fs/promises");
27
27
  var _nodeOs = require("node:os");
28
28
  var _nodePath = _interopRequireWildcard(require("node:path"));
29
29
  var _semver = require("semver");
30
- var _xdgBasedir = _interopRequireDefault(require("xdg-basedir"));
31
30
  var _devEnvironmentCore = require("./dev-environment-core");
32
31
  var _dockerUtils = require("./docker-utils");
33
32
  var _devEnvironment = require("../constants/dev-environment");
34
33
  var _userError = _interopRequireDefault(require("../user-error"));
34
+ var _xdgData = require("../xdg-data");
35
35
  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); }
36
36
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
37
37
  /**
@@ -57,9 +57,7 @@ async function getLandoConfig() {
57
57
  } else {
58
58
  logLevelConsole = 'warn';
59
59
  }
60
-
61
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
62
- const vipDir = _nodePath.default.join(_xdgBasedir.default.data || (0, _nodeOs.tmpdir)(), 'vip'); // NOSONAR
60
+ const vipDir = _nodePath.default.join((0, _xdgData.xdgData)(), 'vip');
63
61
  const landoDir = _nodePath.default.join(vipDir, 'lando');
64
62
  const fakeHomeDir = _nodePath.default.join(landoDir, 'home');
65
63
  try {
@@ -4,7 +4,7 @@ exports.__esModule = true;
4
4
  exports.getReadAndWriteStreams = getReadAndWriteStreams;
5
5
  exports.searchAndReplace = void 0;
6
6
  var _vipSearchReplace = require("@automattic/vip-search-replace");
7
- var _chalk = require("chalk");
7
+ var _chalk = _interopRequireDefault(require("chalk"));
8
8
  var _debug = _interopRequireDefault(require("debug"));
9
9
  var _fs = _interopRequireDefault(require("fs"));
10
10
  var _promises = require("node:stream/promises");
@@ -138,7 +138,7 @@ const searchAndReplace = async (fileName, pairs, {
138
138
  try {
139
139
  await (0, _promises.pipeline)(streams);
140
140
  } catch (error) {
141
- console.log((0, _chalk.red)("Oh no! We couldn't write to the output file. Please check your available disk space and file/folder permissions."));
141
+ 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
142
  throw error;
143
143
  }
144
144
  const endTime = process.hrtime(startTime);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.xdgData = xdgData;
5
+ var _nodeOs = require("node:os");
6
+ var _nodePath = require("node:path");
7
+ function xdgData() {
8
+ // Use the XDG_DATA_HOME environment variable if set, otherwise default to ~/.local/share
9
+ const xdgDataHome = process.env.XDG_DATA_HOME;
10
+ if (xdgDataHome) {
11
+ return xdgDataHome;
12
+ }
13
+ const homeDirectory = (0, _nodeOs.homedir)();
14
+ return (0, _nodePath.join)(homeDirectory, '.local', 'share');
15
+ }