@automattic/vip 3.22.5 → 3.23.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.
Files changed (42) hide show
  1. package/assets/dev-env.lando.template.yml.ejs +4 -0
  2. package/dist/bin/vip-import-media-abort.js +3 -4
  3. package/dist/bin/vip-import-media.js +3 -4
  4. package/dist/bin/vip-import-sql.js +75 -5
  5. package/dist/bin/vip-import-validate-files.js +0 -3
  6. package/dist/bin/vip-logs.js +0 -2
  7. package/dist/bin/vip-slowlogs.js +0 -2
  8. package/dist/bin/vip-sync.js +3 -2
  9. package/dist/bin/vip-wp.js +4 -7
  10. package/dist/commands/backup-db.js +1 -6
  11. package/dist/commands/dev-env-sync-sql.js +1 -1
  12. package/dist/commands/export-sql.js +6 -5
  13. package/dist/commands/phpmyadmin.js +3 -14
  14. package/dist/commands/wp-ssh.js +2 -4
  15. package/dist/lib/api/app.js +2 -8
  16. package/dist/lib/api/feature-flags.js +2 -2
  17. package/dist/lib/api/http.js +16 -9
  18. package/dist/lib/api/user.js +1 -3
  19. package/dist/lib/api.js +24 -50
  20. package/dist/lib/app-logs/app-logs.js +1 -1
  21. package/dist/lib/app-slowlogs/app-slowlogs.js +1 -1
  22. package/dist/lib/cli/apiConfig.js +4 -7
  23. package/dist/lib/client-file-uploader.js +2 -3
  24. package/dist/lib/config/software.js +2 -4
  25. package/dist/lib/dev-environment/dev-environment-cli.js +0 -2
  26. package/dist/lib/dev-environment/dev-environment-lando.js +1 -1
  27. package/dist/lib/envvar/api-get-all.js +1 -1
  28. package/dist/lib/envvar/api-list.js +1 -1
  29. package/dist/lib/http/proxy-agent.js +0 -2
  30. package/dist/lib/keychain.js +1 -1
  31. package/dist/lib/live-backup-copy.js +0 -3
  32. package/dist/lib/media-import/config.js +1 -1
  33. package/dist/lib/media-import/status.js +4 -1
  34. package/dist/lib/site-import/status.js +6 -1
  35. package/dist/lib/validations/is-multi-site.js +1 -1
  36. package/dist/lib/validations/is-multisite-domain-mapped.js +1 -1
  37. package/dist/lib/validations/sql.js +0 -1
  38. package/dist/lib/vip-import-validate-files.js +2 -8
  39. package/eslint.config.js +24 -0
  40. package/npm-shrinkwrap.json +4169 -13130
  41. package/package.json +7 -8
  42. package/.eslintrc.js +0 -22
@@ -138,6 +138,8 @@ services:
138
138
  <% if ( phpmyadmin ) { %>
139
139
  phpmyadmin:
140
140
  type: compose
141
+ ssl: true
142
+ sslExpose: false
141
143
  services:
142
144
  image: phpmyadmin:5
143
145
  command: /docker-entrypoint.sh apache2-foreground
@@ -248,6 +250,8 @@ services:
248
250
 
249
251
  <% if ( mailpit ) { %>
250
252
  mailpit:
253
+ ssl: true
254
+ sslExpose: false
251
255
  type: compose
252
256
  services:
253
257
  image: axllent/mailpit:latest
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ var _core = require("@apollo/client/core");
4
5
  var _chalk = _interopRequireDefault(require("chalk"));
5
6
  var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
6
7
  var _api = _interopRequireDefault(require("../lib/api"));
@@ -12,8 +13,6 @@ var _status = require("../lib/media-import/status");
12
13
  var _tracker = require("../lib/tracker");
13
14
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
- // eslint-disable-next-line no-duplicate-imports
16
-
17
16
  const usage = 'vip import media abort';
18
17
  const appQuery = `
19
18
  id,
@@ -98,8 +97,8 @@ Aborting this media import.
98
97
  progressTracker
99
98
  });
100
99
  } catch (error) {
101
- if (error.graphQLErrors) {
102
- for (const err of error.graphQLErrors) {
100
+ if (_core.CombinedGraphQLErrors.is(error)) {
101
+ for (const err of error.errors) {
103
102
  console.log(_chalk.default.red('Error:'), err.message);
104
103
  }
105
104
  return;
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ var _core = require("@apollo/client/core");
4
5
  var _chalk = _interopRequireDefault(require("chalk"));
5
6
  var _debug = _interopRequireDefault(require("debug"));
6
7
  var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
@@ -13,8 +14,6 @@ var _status = require("../lib/media-import/status");
13
14
  var _utils = require("../lib/media-import/utils");
14
15
  var _tracker = require("../lib/tracker");
15
16
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
- // eslint-disable-next-line no-duplicate-imports
17
-
18
17
  const API_VERSION = 'v2';
19
18
  const appQuery = `
20
19
  id,
@@ -197,8 +196,8 @@ Importing Media into your App...
197
196
  saveErrorLog
198
197
  });
199
198
  } catch (error) {
200
- if (error.graphQLErrors) {
201
- for (const err of error.graphQLErrors) {
199
+ if (_core.CombinedGraphQLErrors.is(error)) {
200
+ for (const err of error.errors) {
202
201
  console.log(_chalk.default.red('Error:'), err.message);
203
202
  }
204
203
  return;
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
 
4
4
  exports.__esModule = true;
5
+ exports.confirmSkipBackup = void 0;
5
6
  exports.gates = gates;
6
7
  exports.parseHeaders = parseHeaders;
7
8
  exports.promptToContinue = void 0;
@@ -309,9 +310,69 @@ const promptToContinue = async ({
309
310
  };
310
311
 
311
312
  /**
312
- * @returns {Promise<string[]>}
313
+ * Show a huge warning and prompt the user to confirm twice when skipping backup
314
+ *
315
+ * @param {Function} track - Tracking function
316
+ * @returns {Promise<boolean>} True if user confirmed both times, false otherwise
313
317
  */
314
318
  exports.promptToContinue = promptToContinue;
319
+ const confirmSkipBackup = async track => {
320
+ console.log(_chalk.default.bold.red('⚠️ WARNING ⚠️'));
321
+ console.log(_chalk.default.red(_chalk.default.bold.red('YOU ARE ABOUT TO SKIP CREATING A BACKUP BEFORE IMPORTING SQL!\n')));
322
+ console.log(_chalk.default.bold.yellow('This action is EXTREMELY DANGEROUS and can result in:'));
323
+ console.log(_chalk.default.bold.yellow('• Permanent data loss'));
324
+ console.log(_chalk.default.bold.yellow('• Inability to automatically restore your database'));
325
+ console.log(_chalk.default.bold.yellow('• Complete site failure'));
326
+ console.log(_chalk.default.bold.red('There is NO way to undo this action once the import begins!\n'));
327
+ const importAbortedMsg = _chalk.default.red('✗ Import aborted.');
328
+ try {
329
+ // First confirmation: y/n prompt
330
+ const firstConfirm = await (0, _enquirer.prompt)({
331
+ type: 'confirm',
332
+ name: 'firstConfirm',
333
+ message: 'Are you absolutely certain you want to skip the backup?'
334
+ }).catch(() => {
335
+ return {
336
+ firstConfirm: false
337
+ };
338
+ });
339
+ if (!firstConfirm.firstConfirm) {
340
+ await track('import_sql_skip_backup_cancelled');
341
+ console.log(importAbortedMsg);
342
+ return false;
343
+ }
344
+
345
+ // Second confirmation: requires typing "yes"
346
+ const secondConfirm = await (0, _enquirer.prompt)({
347
+ type: 'input',
348
+ name: 'secondConfirm',
349
+ message: `Type '${_chalk.default.yellow('yes')}' (without quotes) to proceed WITHOUT creating a backup (this cannot be undone):\n`
350
+ }).catch(() => {
351
+ return {
352
+ secondConfirm: ''
353
+ };
354
+ });
355
+ if (secondConfirm.secondConfirm?.toLowerCase() !== 'yes') {
356
+ await track('import_sql_skip_backup_cancelled');
357
+ console.error('Failed to confirm!');
358
+ console.log(importAbortedMsg);
359
+ return false;
360
+ }
361
+ } catch (error) {
362
+ await track('import_sql_skip_backup_cancelled');
363
+ console.log(importAbortedMsg);
364
+ console.error(error);
365
+ return false;
366
+ }
367
+ await track('import_sql_skip_backup_confirmed');
368
+ console.log(_chalk.default.red('⚠️ Backup will be skipped. Proceeding with import...'));
369
+ return true;
370
+ };
371
+
372
+ /**
373
+ * @returns {Promise<string[]>}
374
+ */
375
+ exports.confirmSkipBackup = confirmSkipBackup;
315
376
  async function validateAndGetTableNames({
316
377
  skipValidate,
317
378
  appId,
@@ -359,7 +420,6 @@ const displayPlaybook = ({
359
420
  }
360
421
  let siteArray = [];
361
422
  if (isMultiSite) {
362
- // eslint-disable-next-line no-multi-spaces
363
423
  console.log(` multisite: ${isMultiSite.toString()}`);
364
424
  const selectedEnvironmentObj = app?.environments?.find(env => unformattedEnvironment === env.type);
365
425
  siteArray = selectedEnvironmentObj?.wpSitesSDS?.nodes;
@@ -414,7 +474,7 @@ const displayPlaybook = ({
414
474
  requiredArgs: 1,
415
475
  module: 'import-sql',
416
476
  usage
417
- }).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 a local or remote SQL database file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to"). Can be passed more than once.').option('in-place', 'Overwrite a local SQL database file with the results of a search and replace operation prior to import.').option('output', 'Save the results of a --search-replace operation that is run against a local SQL database file to a copy of that file. Accepts a local file path. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Prevent an unlaunched environment from going into maintenance mode during the import of a local or remote SQL database file. Skipping maintenance mode can cause site instability during import.').option('md5', 'Verify the integrity of a remote SQL database file. Accepts an MD5 hash value.').option('header', 'Pass a header name and value (Formatted as "Name: Value") in a request for a remote SQL database file. Can be passed more than once for multiple headers and values.').examples(examples)
477
+ }).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 a local or remote SQL database file and replace it with a new string. Separate the values by a comma only; no spaces (e.g. --search-replace="from,to"). Can be passed more than once.').option('in-place', 'Overwrite a local SQL database file with the results of a search and replace operation prior to import.').option('output', 'Save the results of a --search-replace operation that is run against a local SQL database file to a copy of that file. Accepts a local file path. Ignored when used with the --in-place option.').option('skip-maintenance-mode', 'Prevent an unlaunched environment from going into maintenance mode during the import of a local or remote SQL database file. Skipping maintenance mode can cause site instability during import.').option('md5', 'Verify the integrity of a remote SQL database file. Accepts an MD5 hash value.').option('header', 'Pass a header name and value (Formatted as "Name: Value") in a request for a remote SQL database file. Can be passed more than once for multiple headers and values.').option(['B', 'skip-backup'], 'Skip creating a backup before importing the SQL file. WARNING: This is extremely dangerous and can result in permanent data loss.').examples(examples)
418
478
  // eslint-disable-next-line complexity
419
479
  .argv(process.argv, async (arg, opts) => {
420
480
  const {
@@ -426,7 +486,8 @@ const displayPlaybook = ({
426
486
  searchReplace,
427
487
  skipMaintenanceMode,
428
488
  md5,
429
- header
489
+ header,
490
+ skipBackup
430
491
  } = opts;
431
492
  const {
432
493
  id: envId,
@@ -499,6 +560,12 @@ const displayPlaybook = ({
499
560
  isMultiSite,
500
561
  tableNames
501
562
  });
563
+ if (skipBackup) {
564
+ const confirmed = await confirmSkipBackup(track);
565
+ if (!confirmed) {
566
+ process.exit(0);
567
+ }
568
+ }
502
569
  if (!isUrl && opts.inPlace) {
503
570
  const approved = await (0, _prompt.confirm)([], 'Are you sure you want to run search and replace on your input file? This operation is not reversible.');
504
571
  if (!approved) {
@@ -565,7 +632,10 @@ Processing the SQL import for your environment...
565
632
  input: {
566
633
  id: app.id,
567
634
  environmentId: env.id,
568
- skipMaintenanceMode
635
+ skipMaintenanceMode,
636
+ ...(skipBackup && {
637
+ skipBackup: true
638
+ })
569
639
  }
570
640
  };
571
641
  if (isUrl) {
@@ -131,7 +131,6 @@ async function vipImportValidateFilesCmd(arg = []) {
131
131
 
132
132
  // Tracks events to track activity
133
133
  // Props (object keys) need to be in Snake case vs. camelCase
134
- /* eslint-disable camelcase */
135
134
  const allErrors = {
136
135
  folder_errors_length: folderValidation.length,
137
136
  int_images_errors_length: intermediateImagesTotal,
@@ -140,8 +139,6 @@ async function vipImportValidateFilesCmd(arg = []) {
140
139
  total_files: files.length,
141
140
  total_folders: nestedDirectories.length
142
141
  };
143
- /* eslint-enable camelcase */
144
-
145
142
  await (0, _tracker.trackEvent)('import_validate_files_command_success', allErrors);
146
143
  }
147
144
  const usage = 'vip import validate-files';
@@ -67,8 +67,6 @@ async function followLogs(opt) {
67
67
 
68
68
  // Set an initial default delay
69
69
  let delay = DEFAULT_POLLING_DELAY_IN_SECONDS;
70
-
71
- // eslint-disable-next-line no-constant-condition
72
70
  while (true) {
73
71
  const limit = isFirstRequest ? opt.limit : LIMIT_MAX;
74
72
  requestNumber++;
@@ -58,8 +58,6 @@ async function followLogs(opt) {
58
58
 
59
59
  // Set an initial default delay
60
60
  let delay = DEFAULT_POLLING_DELAY_IN_SECONDS;
61
-
62
- // eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition
63
61
  while (true) {
64
62
  const limit = isFirstRequest ? opt.limit : LIMIT_MAX;
65
63
  requestNumber++;
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ var _core = require("@apollo/client/core");
4
5
  var _singleLineLog = require("@wwa/single-line-log");
5
6
  var _chalk = _interopRequireDefault(require("chalk"));
6
7
  var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
@@ -44,9 +45,9 @@ const appQuery = `id,name,environments{
44
45
  }
45
46
  });
46
47
  } catch (error) {
47
- if (error.graphQLErrors) {
48
+ if (_core.CombinedGraphQLErrors.is(error)) {
48
49
  let bail = false;
49
- for (const err of error.graphQLErrors) {
50
+ for (const err of error.errors) {
50
51
  if (err.message !== 'Site is already syncing') {
51
52
  bail = true;
52
53
  console.log(_chalk.default.red('Error:'), err.message);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ var _core = require("@apollo/client/core");
4
5
  var _chalk = _interopRequireDefault(require("chalk"));
5
6
  var _debug = _interopRequireDefault(require("debug"));
6
7
  var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
@@ -331,8 +332,6 @@ const examples = [{
331
332
  if ((0, _app.isAppNodejs)(appTypeId)) {
332
333
  exit.withError('WP-CLI commands are not supported on Node.js environments.');
333
334
  }
334
-
335
- /* eslint-disable camelcase */
336
335
  const commonTrackingParams = {
337
336
  command: commandForAnalytics,
338
337
  app_id: appId,
@@ -340,8 +339,6 @@ const examples = [{
340
339
  org_id: orgId,
341
340
  method: isSubShell ? 'subshell' : 'shell'
342
341
  };
343
- /* eslint-enable camelcase */
344
-
345
342
  (0, _tracker.trackEvent)('wpcli_command_execute', commonTrackingParams).catch(() => {});
346
343
  if (isSubShell) {
347
344
  // Reset the cursor (can get messed up with enquirer)
@@ -441,10 +438,10 @@ const examples = [{
441
438
  result = await getTokenForCommand(appId, envId, wpCliCmd);
442
439
  } catch (error) {
443
440
  // If this was a GraphQL error, print that to the message to the line
444
- if (error.graphQLErrors) {
445
- error.graphQLErrors.forEach(err => {
441
+ if (_core.CombinedGraphQLErrors.is(error)) {
442
+ for (const err of error.errors) {
446
443
  console.log(_chalk.default.red('Error:'), err.message);
447
- });
444
+ }
448
445
  } else {
449
446
  // Else, other type of error, just dump it
450
447
  console.log(error);
@@ -53,12 +53,7 @@ async function getBackupJob(appId, envId) {
53
53
  },
54
54
  fetchPolicy: 'network-only'
55
55
  });
56
- const {
57
- data: {
58
- app
59
- }
60
- } = response;
61
- return app?.environments?.[0]?.jobs?.[0];
56
+ return response.data?.app?.environments?.[0]?.jobs?.[0] ?? undefined;
62
57
  }
63
58
  async function createBackupJob(appId, envId) {
64
59
  // Disable global error handling so that we can handle errors ourselves
@@ -214,7 +214,7 @@ class DevEnvSyncSQLCommand {
214
214
  do {
215
215
  // eslint-disable-next-line no-await-in-loop
216
216
  const res = await this.fetchSitesPage(api, appId, environmentId, after);
217
- if (res.data.app?.environments?.[0]?.wpSitesSDS?.nodes) {
217
+ if (res.data?.app?.environments?.[0]?.wpSitesSDS?.nodes) {
218
218
  const wpSitesSDS = res.data.app.environments[0].wpSitesSDS;
219
219
  allSites.push(...res.data.app.environments[0].wpSitesSDS.nodes.filter(node => Boolean(node)));
220
220
  after = wpSitesSDS.nextCursor;
@@ -97,10 +97,11 @@ async function fetchLatestBackupAndJobStatusBase(appId, envId) {
97
97
  },
98
98
  fetchPolicy: 'network-only'
99
99
  });
100
- const environments = response.data.app?.environments;
101
- const envSqlDumpTool = environments?.[0]?.backupsSqlDumpTool;
102
- const latestBackup = environments?.[0]?.latestBackup;
103
- const jobs = environments?.[0]?.jobs || [];
100
+ const environments = response.data?.app?.environments;
101
+ const firstEnvironment = environments?.[0];
102
+ const envSqlDumpTool = firstEnvironment?.backupsSqlDumpTool;
103
+ const latestBackup = firstEnvironment?.latestBackup ?? undefined;
104
+ const jobs = firstEnvironment?.jobs || [];
104
105
  return {
105
106
  latestBackup,
106
107
  jobs,
@@ -141,7 +142,7 @@ async function generateDownloadLink(appId, envId, backupId) {
141
142
  }
142
143
  }
143
144
  });
144
- return response.data?.generateDBBackupCopyUrl?.url;
145
+ return response.data?.generateDBBackupCopyUrl?.url ?? '';
145
146
  }
146
147
 
147
148
  /**
@@ -48,7 +48,6 @@ async function generatePhpMyAdminAccess(envId) {
48
48
  // Disable global error handling so that we can handle errors ourselves
49
49
  (0, _api.disableGlobalGraphQLErrorHandling)();
50
50
  const api = (0, _api.default)();
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
51
  const resp = await api.mutate({
53
52
  mutation: GENERATE_PHP_MY_ADMIN_URL_MUTATION,
54
53
  variables: {
@@ -60,16 +59,13 @@ async function generatePhpMyAdminAccess(envId) {
60
59
 
61
60
  // Re-enable global error handling
62
61
  (0, _api.enableGlobalGraphQLErrorHandling)();
63
-
64
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
65
- return resp?.data?.generatePHPMyAdminAccess?.url;
62
+ return resp.data?.generatePHPMyAdminAccess?.url ?? '';
66
63
  }
67
64
  async function enablePhpMyAdmin(envId) {
68
65
  // Disable global error handling so that we can handle errors ourselves
69
66
  (0, _api.disableGlobalGraphQLErrorHandling)();
70
67
  const api = (0, _api.default)();
71
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
- const resp = await api.mutate({
68
+ await api.mutate({
73
69
  mutation: ENABLE_PHP_MY_ADMIN_MUTATION,
74
70
  variables: {
75
71
  input: {
@@ -80,16 +76,11 @@ async function enablePhpMyAdmin(envId) {
80
76
 
81
77
  // Re-enable global error handling
82
78
  (0, _api.enableGlobalGraphQLErrorHandling)();
83
-
84
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
85
- return resp?.data?.generatePHPMyAdminAccess?.url;
86
79
  }
87
80
  async function getPhpMyAdminStatus(appId, envId) {
88
81
  // Disable global error handling so that we can handle errors ourselves
89
82
  (0, _api.disableGlobalGraphQLErrorHandling)();
90
83
  const api = (0, _api.default)();
91
-
92
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
84
  const resp = await api.query({
94
85
  query: GET_PHP_MY_ADMIN_STATUS_QUERY,
95
86
  variables: {
@@ -101,9 +92,7 @@ async function getPhpMyAdminStatus(appId, envId) {
101
92
 
102
93
  // Re-enable global error handling
103
94
  (0, _api.enableGlobalGraphQLErrorHandling)();
104
-
105
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
106
- return resp?.data?.app?.environments?.[0]?.phpMyAdminStatus?.status;
95
+ return resp.data?.app?.environments?.[0]?.phpMyAdminStatus?.status ?? '';
107
96
  }
108
97
  class PhpMyAdminCommand {
109
98
  silent;
@@ -209,10 +209,8 @@ class WPCliCommandOverSSH {
209
209
  }
210
210
  });
211
211
  } catch (error) {
212
- if (error instanceof _core.ApolloError) {
213
- const message = error.graphQLErrors.map(err => {
214
- return err.message;
215
- }).join('; ');
212
+ if (_core.CombinedGraphQLErrors.is(error)) {
213
+ const message = error.errors.map(err => err.message).join('; ');
216
214
  throw new APIError(message);
217
215
  }
218
216
  const message = error instanceof Error ? error.message : String(error);
@@ -23,10 +23,7 @@ async function _default(app, fields = 'id,name', fragments = '') {
23
23
  name: app
24
24
  }
25
25
  });
26
- if (!res.data.apps?.edges?.length) {
27
- return {};
28
- }
29
- return res.data.apps.edges[0];
26
+ return res.data?.apps?.edges?.[0] ?? {};
30
27
  }
31
28
  if (typeof app === 'string') {
32
29
  app = parseInt(app, 10);
@@ -51,8 +48,5 @@ async function _default(app, fields = 'id,name', fragments = '') {
51
48
  };
52
49
  }
53
50
  const res = await api.query(appQuery);
54
- if (!res.data.app) {
55
- return {};
56
- }
57
- return res.data.app;
51
+ return res.data?.app ?? {};
58
52
  }
@@ -15,8 +15,8 @@ const isVipQuery = (0, _graphqlTag.default)`
15
15
  }
16
16
  }
17
17
  `;
18
- async function get() {
19
- return await api.query({
18
+ function get() {
19
+ return api.query({
20
20
  query: isVipQuery,
21
21
  fetchPolicy: 'cache-first'
22
22
  });
@@ -31,17 +31,24 @@ var _default = async (path, options = {}) => {
31
31
  const authToken = await _token.default.get();
32
32
  const proxyAgent = (0, _proxyAgent.createProxyAgent)(url);
33
33
  debug('running fetch', url);
34
- return (0, _nodeFetch.default)(url, {
34
+ const headers = new Headers({
35
+ ...options.headers
36
+ });
37
+ if (!headers.has('Authorization')) {
38
+ headers.set('Authorization', `Bearer ${authToken.raw}`);
39
+ }
40
+ if (!headers.has('User-Agent')) {
41
+ headers.set('User-Agent', _env.default.userAgent);
42
+ }
43
+ if (!headers.has('Content-Type') && options.method !== 'GET') {
44
+ headers.set('Content-Type', 'application/json');
45
+ }
46
+ const opts = {
35
47
  ...options,
36
48
  agent: proxyAgent ?? undefined,
37
- headers: {
38
- Authorization: `Bearer ${authToken.raw}`,
39
- 'User-Agent': _env.default.userAgent,
40
- 'Content-Type': 'application/json',
41
- ...(options.headers ?? {})
42
- },
43
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
49
+ headers,
44
50
  body: typeof options.body === 'object' ? JSON.stringify(options.body) : options.body
45
- });
51
+ };
52
+ return (0, _nodeFetch.default)(url, opts);
46
53
  };
47
54
  exports.default = _default;
@@ -28,9 +28,7 @@ async function getCurrentUserInfo(silenceAuthErrors = false) {
28
28
  const response = await api.query({
29
29
  query: QUERY_CURRENT_USER
30
30
  });
31
- const {
32
- me
33
- } = response.data;
31
+ const me = response.data?.me;
34
32
  if (!me) {
35
33
  throw new Error('The API did not return any information about the user.');
36
34
  }
package/dist/lib/api.js CHANGED
@@ -7,21 +7,16 @@ exports.disableGlobalGraphQLErrorHandling = disableGlobalGraphQLErrorHandling;
7
7
  exports.enableGlobalGraphQLErrorHandling = enableGlobalGraphQLErrorHandling;
8
8
  exports.shouldRetryRequest = shouldRetryRequest;
9
9
  var _core = require("@apollo/client/core");
10
- var _context = require("@apollo/client/link/context");
11
- var _core2 = require("@apollo/client/link/core");
12
10
  var _error = require("@apollo/client/link/error");
13
11
  var _retry = require("@apollo/client/link/retry");
14
12
  var _chalk = _interopRequireDefault(require("chalk"));
15
13
  var _debug = _interopRequireDefault(require("debug"));
14
+ var _graphql = require("graphql");
16
15
  var _nodeFetch = require("node-fetch");
17
16
  var _http = _interopRequireDefault(require("./api/http"));
18
- var _env = _interopRequireDefault(require("./env"));
19
- var _token = _interopRequireDefault(require("./token"));
20
- var _proxyAgent = require("../lib/http/proxy-agent");
21
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
22
18
  // Config
23
19
  const PRODUCTION_API_HOST = exports.PRODUCTION_API_HOST = 'https://api.wpvip.com';
24
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
25
20
  const API_HOST = exports.API_HOST = process.env.API_HOST || PRODUCTION_API_HOST; // NOSONAR
26
21
  const API_URL = exports.API_URL = `${API_HOST}/graphql`;
27
22
  let globalGraphQLErrorHandlingEnabled = true;
@@ -38,7 +33,7 @@ function enableGlobalGraphQLErrorHandling() {
38
33
  }
39
34
  function shouldRetryRequest(attempt, operation, error) {
40
35
  const debugSuffix = `Operation: ${operation.operationName}. Attempt: ${attempt}.`;
41
- if (!error || operation.query.definitions.some(def => def.kind === 'OperationDefinition' && def.operation !== 'query')) {
36
+ if (!error || operation.query.definitions.some(def => def.kind === _graphql.Kind.OPERATION_DEFINITION && def.operation !== _graphql.OperationTypeNode.QUERY)) {
42
37
  debug(`Request failed. ${debugSuffix}`);
43
38
  return false;
44
39
  }
@@ -58,71 +53,50 @@ function shouldRetryRequest(attempt, operation, error) {
58
53
  debug(`Request failed. Retrying request due to server error. ${debugSuffix}`, error);
59
54
  return true;
60
55
  }
61
- function isServerError(networkError) {
62
- if (!networkError) {
63
- return false;
64
- }
65
- return 'result' in networkError;
66
- }
67
56
  function API({
68
57
  exitOnError = true,
69
58
  silenceAuthErrors = false,
70
59
  customRetryLink
71
60
  } = {}) {
72
- const errorLink = (0, _error.onError)(({
73
- networkError,
74
- graphQLErrors
61
+ const errorLink = new _error.ErrorLink(({
62
+ error
75
63
  }) => {
76
- if (!silenceAuthErrors && networkError && 'statusCode' in networkError && networkError.statusCode === 401) {
77
- let message = 'You are not authorized to perform this request; please logout with `vip logout`, then try again.';
78
- if (isServerError(networkError) && networkError.result?.code === 'token-disabled-inactivity') {
79
- message = 'Your token has expired due to inactivity; please log out with `vip logout`, then try again.';
64
+ if (!silenceAuthErrors && error instanceof _core.ServerError && error.statusCode === 401) {
65
+ let message;
66
+ try {
67
+ const result = JSON.parse(error.bodyText);
68
+ if (typeof result === 'object' && result !== null && 'code' in result && result?.code === 'token-disabled-inactivity') {
69
+ message = 'Your token has expired due to inactivity';
70
+ }
71
+ } catch {
72
+ // If we can't parse the body, use the default message
73
+ message = 'You are not authorized to perform this request';
80
74
  }
75
+ message += '; please log out with `vip logout`, then try again.';
81
76
  console.error(_chalk.default.red('Unauthorized:'), message);
82
77
  process.exit(1);
83
78
  }
84
- if (graphQLErrors?.length && globalGraphQLErrorHandlingEnabled) {
85
- graphQLErrors.forEach(error => {
86
- console.error(_chalk.default.red('Error:'), error.message);
87
- });
79
+ if (_core.CombinedGraphQLErrors.is(error) && globalGraphQLErrorHandlingEnabled) {
80
+ for (const err of error.errors) {
81
+ console.error(_chalk.default.red('Error:'), err.message);
82
+ }
88
83
  if (exitOnError) {
89
84
  process.exit(1);
90
85
  }
91
86
  }
92
87
  });
93
- const withToken = (0, _context.setContext)(async () => {
94
- const token = (await _token.default.get()).raw;
95
- return {
96
- token
97
- };
98
- });
99
- const authLink = new _core2.ApolloLink((operation, forward) => {
100
- const ctx = operation.getContext();
101
- const headers = {
102
- 'User-Agent': _env.default.userAgent,
103
- Authorization: `Bearer ${ctx.token}`,
104
- ...ctx.headers
105
- };
106
- operation.setContext({
107
- headers
108
- });
109
- return forward(operation);
110
- });
111
- const proxyAgent = (0, _proxyAgent.createProxyAgent)(API_URL);
112
88
  const httpLink = new _core.HttpLink({
113
89
  uri: operation => {
114
90
  // to make it easier to write tests, we'll skip adding x_query for tests
115
91
  if (process.env.NODE_ENV === 'test') {
116
92
  return API_URL;
117
93
  }
118
- return `${API_URL}?x_query=${decodeURIComponent(operation.operationName)}`;
94
+ const operationName = operation.operationName ?? '';
95
+ return `${API_URL}?x_query=${encodeURIComponent(operationName)}`;
119
96
  },
120
- fetch: _http.default,
121
- fetchOptions: {
122
- agent: proxyAgent
123
- }
97
+ fetch: _http.default
124
98
  });
125
- const retryLink = new _retry.RetryLink({
99
+ const retryLink = customRetryLink ?? new _retry.RetryLink({
126
100
  delay: {
127
101
  initial: RETRY_LINK_INITIAL_DELAY_MS,
128
102
  max: RETRY_LINK_MAX_DELAY_MS
@@ -130,7 +104,7 @@ function API({
130
104
  attempts: shouldRetryRequest
131
105
  });
132
106
  return new _core.ApolloClient({
133
- link: _core2.ApolloLink.from([withToken, errorLink, customRetryLink ? customRetryLink : retryLink, authLink, httpLink]),
107
+ link: _core.ApolloLink.from([errorLink, retryLink, httpLink]),
134
108
  cache: new _core.InMemoryCache({
135
109
  typePolicies: {
136
110
  WPSite: {
@@ -44,7 +44,7 @@ async function getRecentLogs(appId, envId, type, limit, after) {
44
44
  after
45
45
  }
46
46
  });
47
- const logs = response.data.app?.environments?.[0]?.logs;
47
+ const logs = response.data?.app?.environments?.[0]?.logs;
48
48
  if (!logs?.nodes) {
49
49
  throw new Error('Unable to query logs');
50
50
  }
@@ -41,7 +41,7 @@ async function getRecentSlowlogs(appId, envId, limit, after) {
41
41
  after
42
42
  }
43
43
  });
44
- const slowlogs = response.data.app?.environments?.[0]?.slowlogs;
44
+ const slowlogs = response.data?.app?.environments?.[0]?.slowlogs;
45
45
  if (!slowlogs?.nodes) {
46
46
  throw new Error('Unable to query slowlogs');
47
47
  }