@automattic/vip 4.0.13 → 4.1.1

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.
@@ -16,10 +16,14 @@ var _downloadFile = require("../lib/http/download-file");
16
16
  var _liveBackupCopy = _interopRequireWildcard(require("../lib/live-backup-copy"));
17
17
  var liveBackupCopy = _liveBackupCopy;
18
18
  var _retry = require("../lib/retry");
19
+ var _userError = _interopRequireDefault(require("../lib/user-error"));
19
20
  var _utils = require("../lib/utils");
20
21
  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); }
21
22
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
22
23
  const EXPORT_SQL_PROGRESS_POLL_INTERVAL = 1000;
24
+
25
+ // Maximum allowed size for a live backup copy configuration file (500 KB).
26
+ const MAX_LIVE_BACKUP_CONFIG_SIZE_BYTES = 500 * 1024;
23
27
  const BACKUP_AND_JOB_STATUS_QUERY = (0, _graphqlTag.default)`
24
28
  query AppBackupAndJobStatus($appId: Int!, $envId: Int!) {
25
29
  app(id: $appId) {
@@ -455,10 +459,10 @@ class ExportSQLCommand {
455
459
  }
456
460
  async generateLiveBackupCopy() {
457
461
  if (!this.liveBackupCopyCLIOptions) {
458
- throw new Error('Configuration file is required for live backup copy.');
462
+ throw new _userError.default('Configuration file is required for live backup copy.');
459
463
  }
460
464
  if (!this.app.id || !this.env.id) {
461
- throw new Error('App ID and Environment ID are required to start live backup copy.');
465
+ throw new _userError.default('App ID and Environment ID are required to start live backup copy.');
462
466
  }
463
467
  const config = this.getLiveBackupConfigFromCLIOptions();
464
468
  try {
@@ -478,7 +482,7 @@ class ExportSQLCommand {
478
482
  this.progressTracker.stepSuccess(this.steps.DOWNLOAD_LINK, [this.generateDownloadURLOutputString(result.url)]);
479
483
  return result;
480
484
  } catch (err) {
481
- const message = err instanceof Error ? err.message : 'Unknown error';
485
+ const message = (0, _utils.parseApiError)(err) ?? 'Unknown error';
482
486
  await this.track('error', {
483
487
  error_type: 'live_backup_copy',
484
488
  error_message: message,
@@ -512,17 +516,23 @@ class ExportSQLCommand {
512
516
  };
513
517
  }
514
518
  loadLiveBackupCopyConfig(configFile) {
515
- if (!_fs.default.existsSync(configFile)) {
516
- throw new Error(`Configuration file not found: ${configFile}`);
517
- }
518
519
  try {
519
- return JSON.parse(_fs.default.readFileSync(configFile, 'utf-8'));
520
+ // Read the file once to avoid a check-then-use race condition.
521
+ const fileContents = _fs.default.readFileSync(configFile, 'utf-8');
522
+ const size = Buffer.byteLength(fileContents);
523
+ if (size > MAX_LIVE_BACKUP_CONFIG_SIZE_BYTES) {
524
+ throw new _userError.default(`Configuration file is too large (${(0, _format.formatBytes)(size)}); the maximum allowed size is ${(0, _format.formatBytes)(MAX_LIVE_BACKUP_CONFIG_SIZE_BYTES)}. For large or complex exports, use the \`--wpcli-command\` option instead.`);
525
+ }
526
+ return JSON.parse(fileContents);
520
527
  } catch (err) {
528
+ if (err instanceof _userError.default) {
529
+ throw err;
530
+ }
521
531
  const errMessage = err instanceof Error ? err.message : 'Unknown error';
522
532
  if (err instanceof SyntaxError) {
523
- throw new Error(`Invalid JSON in configuration file: ${configFile} - ${errMessage}`);
533
+ throw new _userError.default(`Invalid JSON in configuration file: ${configFile} - ${errMessage}`);
524
534
  }
525
- throw new Error(`Error reading configuration file: ${configFile} - ${errMessage}`);
535
+ throw new _userError.default(`Error reading configuration file: ${configFile} - ${errMessage}`);
526
536
  }
527
537
  }
528
538
  }
package/dist/lib/utils.js CHANGED
@@ -79,19 +79,49 @@ function getAbsolutePath(filePath) {
79
79
  }
80
80
 
81
81
  /**
82
- * Parse error object and return probable error.
82
+ * Extract a message from a raw HTTP response body (Apollo's `ServerError.bodyText`).
83
83
  *
84
- * @param {Error} Error object
84
+ * The body may be either `{"status":"error","message":"..."}` (e.g. a 413 whose
85
+ * top-level message is only `Received status code 413`) or a GraphQL
86
+ * `{"errors":[{"message":"..."}]}` payload.
87
+ */
88
+ function parseJSONBodyErrorMessage(bodyText) {
89
+ try {
90
+ const body = JSON.parse(bodyText);
91
+ if (body?.errors?.[0]?.message) {
92
+ return body.errors[0].message;
93
+ }
94
+ if (body?.message) {
95
+ return body.message;
96
+ }
97
+ } catch {
98
+ // Body wasn't JSON; nothing to extract.
99
+ }
100
+ return null;
101
+ }
102
+ /**
103
+ * Parse error object and return probable error.
85
104
  *
86
- * @return {string|null} Error string when error was found, otherwise null.
105
+ * Accepts either a known API error shape or an `unknown` value (e.g. a caught
106
+ * error), so callers do not need to cast before passing it in.
87
107
  */
88
- function parseApiError(err) {
108
+ function parseApiError(error) {
109
+ const err = error;
89
110
  if (err?.networkError?.message) {
90
111
  return err?.networkError?.message;
91
112
  }
92
113
  if (err?.graphQLErrors?.[0]?.message) {
93
114
  return err.graphQLErrors[0].message;
94
115
  }
116
+
117
+ // Apollo's `ServerError` exposes the raw HTTP response body as `bodyText`,
118
+ // which often holds a more specific message than the generic top-level one.
119
+ if (typeof err?.bodyText === 'string') {
120
+ const bodyTextMessage = parseJSONBodyErrorMessage(err.bodyText);
121
+ if (bodyTextMessage) {
122
+ return bodyTextMessage;
123
+ }
124
+ }
95
125
  if (err?.message) {
96
126
  return err?.message;
97
127
  }
package/docs/RELEASING.md CHANGED
@@ -76,6 +76,12 @@ You can release either using GitHub Actions or locally.
76
76
 
77
77
  This is the preferred method for pushing out the latest release. The workflow runs a bunch of validations, generates a build, bump versions + tags, pushes out to npm, and bumps to the next dev version.
78
78
 
79
+ The repository uses `package-lock.json` for npm 12 development and CI installs. An identical
80
+ `npm-shrinkwrap.json` is published with npm 11 so npm 11 and older consumers retain a locked CLI
81
+ dependency tree. Run `npm run sync:shrinkwrap` after changing `package-lock.json`; CI rejects drift
82
+ between the two files. The publish workflows intentionally use npm 11 because npm 12 excludes
83
+ `npm-shrinkwrap.json` from package tarballs.
84
+
79
85
  Please keep in mind internal guidelines before releasing.
80
86
 
81
87
  To release, follow these steps:
@@ -119,7 +125,7 @@ To publish locally, follow these steps:
119
125
  1. Push the tag to GitHub (`git push --tags`)
120
126
  1. Push the trunk branch `git push`
121
127
  1. Make sure you're part of the Automattic organization in npm
122
- 1. Publish the release to npm (`npm publish --access public`) the script will do some extra checks (
128
+ 1. Use npm 11 and publish the release (`npx --yes --package=npm@11 npm publish --access public`). The script will do some extra checks (
123
129
  node version, branch, etc) to ensure everything is correct. If all looks good, the new version
124
130
  will be published and you can proceed.
125
131
  1. Edit [the release on GitHub](https://github.com/Automattic/vip-cli/releases) to include a description
@@ -137,8 +143,8 @@ In order to do that, please follow this:
137
143
 
138
144
  <summary><details>
139
145
 
140
- 1. Manually change the version in `package.json` and `package-lock.json` to a dev version. Example: `1.4.0-dev1`
141
- 1. Run `npm publish --tag next` (When `--tag` is specified, we bypass the usual branch protection that doesn't allow you to publish form a brunch other than `trunk`).
146
+ 1. Set the dev version with npm 11 so both lockfiles remain synchronized. Example: `npx --yes --package=npm@11 npm version --no-git-tag-version 1.4.0-dev1`.
147
+ 1. Run `npx --yes --package=npm@11 npm publish --tag next` (When `--tag` is specified, we bypass the usual branch protection that doesn't allow you to publish form a brunch other than `trunk`).
142
148
 
143
149
  You can repeat this with every new version until you're happy with your version and ready to a public release. We currently don't support multiple branches for multiple versions. When it's the case, this process needs to be done for every version in every branch.
144
150
 
@@ -0,0 +1,32 @@
1
+ const { copyFileSync, readFileSync } = require( 'node:fs' );
2
+ const path = require( 'node:path' );
3
+
4
+ const PACKAGE_LOCK = 'package-lock.json';
5
+ const NPM_SHRINKWRAP = 'npm-shrinkwrap.json';
6
+
7
+ function syncShrinkwrap( { cwd = process.cwd(), check = false } = {} ) {
8
+ const packageLockPath = path.join( cwd, PACKAGE_LOCK );
9
+ const shrinkwrapPath = path.join( cwd, NPM_SHRINKWRAP );
10
+
11
+ if ( check ) {
12
+ const packageLock = readFileSync( packageLockPath );
13
+ const shrinkwrap = readFileSync( shrinkwrapPath );
14
+ if ( ! packageLock.equals( shrinkwrap ) ) {
15
+ throw new Error( `${ NPM_SHRINKWRAP } is out of sync with ${ PACKAGE_LOCK }` );
16
+ }
17
+ return;
18
+ }
19
+
20
+ copyFileSync( packageLockPath, shrinkwrapPath );
21
+ }
22
+
23
+ if ( require.main === module ) {
24
+ try {
25
+ syncShrinkwrap( { check: process.argv.includes( '--check' ) } );
26
+ } catch ( error ) {
27
+ console.error( error.message );
28
+ process.exitCode = 1;
29
+ }
30
+ }
31
+
32
+ module.exports = { syncShrinkwrap };