@automattic/vip 3.21.2 → 3.21.3-dev.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.
@@ -156,7 +156,7 @@ Processing the file for deployment to your environment...
156
156
  },
157
157
  checksum,
158
158
  result
159
- } = await (0, _clientFileUploader.uploadImportSqlFileToS3)(uploadParams);
159
+ } = await (0, _clientFileUploader.uploadImportFileToS3)(uploadParams);
160
160
  startDeployVariables.input = {
161
161
  id: appId,
162
162
  environmentId: envId,
@@ -7,8 +7,10 @@ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
7
7
  var _api = _interopRequireDefault(require("../lib/api"));
8
8
  var _command = _interopRequireDefault(require("../lib/cli/command"));
9
9
  var _format = require("../lib/cli/format");
10
+ var _clientFileUploader = require("../lib/client-file-uploader");
10
11
  var _progress = require("../lib/media-import/progress");
11
12
  var _status = require("../lib/media-import/status");
13
+ var _utils = require("../lib/media-import/utils");
12
14
  var _tracker = require("../lib/tracker");
13
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
16
  // eslint-disable-next-line no-duplicate-imports
@@ -43,10 +45,18 @@ const START_IMPORT_MUTATION = (0, _graphqlTag.default)`
43
45
  const usage = 'vip import media';
44
46
  const debug = (0, _debug.default)('vip:vip-import-media');
45
47
 
48
+ // Support legacy direct invocation: when users run the built binary directly
49
+ // like `node ./dist/bin/vip-import-media.js @1234.production /path/to/archive.tar.gz --flag`
50
+ // inject the `import media` subcommand into process.argv so the command parser
51
+ // sees the same shape as the full CLI: `vip import media ...`.
52
+
46
53
  // Command examples for the `vip import media` help prompt
47
54
  const examples = [{
48
55
  usage: 'vip @example-app.production import media https://example.com/uploads.tar.gz',
49
56
  description: 'Import the archived file "uploads.tar.gz" from a publicly accessible URL to a production environment.'
57
+ }, {
58
+ usage: 'vip @example-app.production import media /path/to/uploads.tar.gz',
59
+ description: 'Import a local archive file (e.g. .tar.gz, .tgz, .zip) from your machine into a production environment. The file will be uploaded temporarily and then imported.'
50
60
  },
51
61
  // Format error logs
52
62
  {
@@ -95,13 +105,58 @@ Are you sure you want to import the contents of the URL?
95
105
  overwriteExistingFiles,
96
106
  importIntermediateImages
97
107
  } = opts;
98
- const [url] = args;
99
- if (!isSupportedUrl(url)) {
100
- console.log(_chalk.default.red(`
101
- Error:
102
- Invalid URL provided: ${url}
103
- Please make sure that it is a publicly accessible web URL containing an archive of the media files to import.`));
104
- return;
108
+ const [fileNameOrURL] = args;
109
+ let url = '';
110
+ let sourceIsLocal = false;
111
+ if (String(fileNameOrURL).startsWith('http://') || String(fileNameOrURL).startsWith('https://')) {
112
+ url = fileNameOrURL;
113
+ // validate supported URL
114
+ if (!isSupportedUrl(url)) {
115
+ console.log(_chalk.default.red(`
116
+ Error:
117
+ Invalid URL provided: ${url}
118
+ Please make sure that it is a publicly accessible web URL containing an archive of the media files to import.`));
119
+ return;
120
+ }
121
+ } else {
122
+ // treat as local archive path
123
+ if (!(await (0, _utils.isLocalArchive)(fileNameOrURL))) {
124
+ console.log(_chalk.default.red(`
125
+ Error:
126
+ Invalid local archive provided: ${fileNameOrURL}
127
+ Please make sure the file exists and is one of: .tar.gz, .tgz, .zip`));
128
+ return;
129
+ }
130
+
131
+ // upload archive to S3 and get presigned URL for import
132
+ sourceIsLocal = true;
133
+ const fileMeta = await (0, _clientFileUploader.getFileMeta)(fileNameOrURL);
134
+ fileMeta.fileName = fileNameOrURL;
135
+ const {
136
+ fileMeta: {
137
+ basename
138
+ },
139
+ checksum: uploadedMD5,
140
+ result
141
+ } = await (0, _clientFileUploader.uploadImportFileToS3)({
142
+ app,
143
+ env,
144
+ fileMeta,
145
+ progressCallback: percentage => console.log(`Upload progress: ${percentage}%`)
146
+ });
147
+
148
+ // small debug info to keep variables used
149
+ debug('Uploaded file basename:', basename);
150
+ debug('Uploaded checksum:', uploadedMD5);
151
+ debug('Upload result:', result && typeof result === 'object' ? Object.keys(result) : result);
152
+ ({
153
+ url
154
+ } = await (0, _clientFileUploader.getSignedUploadRequestData)({
155
+ appId: app.id,
156
+ envId: env.id,
157
+ basename,
158
+ action: 'GetObject'
159
+ }));
105
160
  }
106
161
  const track = _tracker.trackEventWithEnv.bind(null, app.id, env.id);
107
162
  const api = (0, _api.default)();
@@ -114,7 +169,11 @@ Error:
114
169
  Importing Media into your App...
115
170
  `;
116
171
  console.log();
117
- console.log(`Importing archive from: ${url}`);
172
+ if (sourceIsLocal) {
173
+ console.log(`Importing local archive: ${fileNameOrURL} (uploaded to temporary URL)`);
174
+ } else {
175
+ console.log(`Importing archive from: ${url}`);
176
+ }
118
177
  console.log(`to: ${env.primaryDomain.name} (${(0, _format.formatEnvironment)(env.type)})`);
119
178
  try {
120
179
  await api.mutate({
@@ -591,7 +591,7 @@ Processing the SQL import for your environment...
591
591
  },
592
592
  checksum: uploadedMD5,
593
593
  result
594
- } = await (0, _clientFileUploader.uploadImportSqlFileToS3)({
594
+ } = await (0, _clientFileUploader.uploadImportFileToS3)({
595
595
  app,
596
596
  env,
597
597
  fileMeta,
package/dist/lib/api.js CHANGED
@@ -5,11 +5,15 @@ exports.PRODUCTION_API_HOST = exports.API_URL = exports.API_HOST = void 0;
5
5
  exports.default = API;
6
6
  exports.disableGlobalGraphQLErrorHandling = disableGlobalGraphQLErrorHandling;
7
7
  exports.enableGlobalGraphQLErrorHandling = enableGlobalGraphQLErrorHandling;
8
+ exports.shouldRetryRequest = shouldRetryRequest;
8
9
  var _core = require("@apollo/client/core");
9
10
  var _context = require("@apollo/client/link/context");
10
11
  var _core2 = require("@apollo/client/link/core");
11
12
  var _error = require("@apollo/client/link/error");
13
+ var _retry = require("@apollo/client/link/retry");
12
14
  var _chalk = _interopRequireDefault(require("chalk"));
15
+ var _debug = _interopRequireDefault(require("debug"));
16
+ var _nodeFetch = require("node-fetch");
13
17
  var _http = _interopRequireDefault(require("./api/http"));
14
18
  var _env = _interopRequireDefault(require("./env"));
15
19
  var _token = _interopRequireDefault(require("./token"));
@@ -21,12 +25,39 @@ const PRODUCTION_API_HOST = exports.PRODUCTION_API_HOST = 'https://api.wpvip.com
21
25
  const API_HOST = exports.API_HOST = process.env.API_HOST || PRODUCTION_API_HOST; // NOSONAR
22
26
  const API_URL = exports.API_URL = `${API_HOST}/graphql`;
23
27
  let globalGraphQLErrorHandlingEnabled = true;
28
+ const RETRY_LINK_MAX_ATTEMPTS = 5;
29
+ const RETRY_LINK_INITIAL_DELAY_MS = 1000; // 1 second
30
+ const RETRY_LINK_MAX_DELAY_MS = 5000; // 5 seconds
31
+
32
+ const debug = (0, _debug.default)('@automattic/vip:http:graphql');
24
33
  function disableGlobalGraphQLErrorHandling() {
25
34
  globalGraphQLErrorHandlingEnabled = false;
26
35
  }
27
36
  function enableGlobalGraphQLErrorHandling() {
28
37
  globalGraphQLErrorHandlingEnabled = true;
29
38
  }
39
+ function shouldRetryRequest(attempt, operation, error) {
40
+ const debugSuffix = `Operation: ${operation.operationName}. Attempt: ${attempt}.`;
41
+ if (!error || operation.query.definitions.some(def => def.kind === 'OperationDefinition' && def.operation !== 'query')) {
42
+ debug(`Request failed. ${debugSuffix}`);
43
+ return false;
44
+ }
45
+ if (attempt > RETRY_LINK_MAX_ATTEMPTS) {
46
+ debug(`Request failed and max retry attempts reached. ${debugSuffix}`, error);
47
+ return false;
48
+ }
49
+ if (error instanceof _nodeFetch.FetchError && error.code === 'ECONNREFUSED') {
50
+ debug(`Request failed. Retrying request due to connection refused error. ${debugSuffix}`);
51
+ return true;
52
+ }
53
+ const statusCode = error?.statusCode;
54
+ if (statusCode && statusCode !== 429 && statusCode < 500) {
55
+ debug(`Request failed. Status code: ${statusCode}. ${debugSuffix}`, error);
56
+ return false;
57
+ }
58
+ debug(`Request failed. Retrying request due to server error. ${debugSuffix}`, error);
59
+ return true;
60
+ }
30
61
  function isServerError(networkError) {
31
62
  if (!networkError) {
32
63
  return false;
@@ -35,7 +66,8 @@ function isServerError(networkError) {
35
66
  }
36
67
  function API({
37
68
  exitOnError = true,
38
- silenceAuthErrors = false
69
+ silenceAuthErrors = false,
70
+ customRetryLink
39
71
  } = {}) {
40
72
  const errorLink = (0, _error.onError)(({
41
73
  networkError,
@@ -90,8 +122,15 @@ function API({
90
122
  agent: proxyAgent
91
123
  }
92
124
  });
125
+ const retryLink = new _retry.RetryLink({
126
+ delay: {
127
+ initial: RETRY_LINK_INITIAL_DELAY_MS,
128
+ max: RETRY_LINK_MAX_DELAY_MS
129
+ },
130
+ attempts: shouldRetryRequest
131
+ });
93
132
  return new _core.ApolloClient({
94
- link: _core2.ApolloLink.from([withToken, errorLink, authLink, httpLink]),
133
+ link: _core2.ApolloLink.from([withToken, errorLink, customRetryLink ? customRetryLink : retryLink, authLink, httpLink]),
95
134
  cache: new _core.InMemoryCache({
96
135
  typePolicies: {
97
136
  WPSite: {
@@ -8,9 +8,10 @@ exports.getFileHash = void 0;
8
8
  exports.getFileMeta = getFileMeta;
9
9
  exports.getFileSize = getFileSize;
10
10
  exports.getPartBoundaries = getPartBoundaries;
11
+ exports.getSignedUploadRequestData = getSignedUploadRequestData;
11
12
  exports.isFile = isFile;
12
13
  exports.unzipFile = void 0;
13
- exports.uploadImportSqlFileToS3 = uploadImportSqlFileToS3;
14
+ exports.uploadImportFileToS3 = uploadImportFileToS3;
14
15
  exports.uploadParts = uploadParts;
15
16
  var _chalk = _interopRequireDefault(require("chalk"));
16
17
  var _crypto = require("crypto");
@@ -121,7 +122,7 @@ async function getFileMeta(fileName) {
121
122
  isCompressed
122
123
  };
123
124
  }
124
- async function uploadImportSqlFileToS3({
125
+ async function uploadImportFileToS3({
125
126
  app,
126
127
  env,
127
128
  fileMeta,
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.isLocalArchive = isLocalArchive;
5
+ var _promises = require("node:fs/promises");
6
+ async function isLocalArchive(filePath) {
7
+ const lower = filePath.toLowerCase();
8
+ const isArchive = lower.endsWith('.tar.gz') || lower.endsWith('.tgz') || lower.endsWith('.zip');
9
+ if (!isArchive) {
10
+ return false;
11
+ }
12
+ try {
13
+ const fileStat = await (0, _promises.stat)(filePath);
14
+ return fileStat.isFile();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.21.2",
3
+ "version": "3.21.3-dev.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@automattic/vip",
9
- "version": "3.21.2",
9
+ "version": "3.21.3-dev.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -3989,9 +3989,9 @@
3989
3989
  "dev": true
3990
3990
  },
3991
3991
  "node_modules/@types/node": {
3992
- "version": "24.10.0",
3993
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
3994
- "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
3992
+ "version": "24.10.1",
3993
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
3994
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
3995
3995
  "license": "MIT",
3996
3996
  "dependencies": {
3997
3997
  "undici-types": "~7.16.0"
@@ -10913,9 +10913,10 @@
10913
10913
  "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
10914
10914
  },
10915
10915
  "node_modules/js-yaml": {
10916
- "version": "4.1.0",
10917
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
10918
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
10916
+ "version": "4.1.1",
10917
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
10918
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
10919
+ "license": "MIT",
10919
10920
  "dependencies": {
10920
10921
  "argparse": "^2.0.1"
10921
10922
  },
@@ -17112,9 +17113,9 @@
17112
17113
  "dev": true
17113
17114
  },
17114
17115
  "@types/node": {
17115
- "version": "24.10.0",
17116
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
17117
- "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
17116
+ "version": "24.10.1",
17117
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
17118
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
17118
17119
  "requires": {
17119
17120
  "undici-types": "~7.16.0"
17120
17121
  }
@@ -21845,9 +21846,9 @@
21845
21846
  "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
21846
21847
  },
21847
21848
  "js-yaml": {
21848
- "version": "4.1.0",
21849
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
21850
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
21849
+ "version": "4.1.1",
21850
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
21851
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
21851
21852
  "requires": {
21852
21853
  "argparse": "^2.0.1"
21853
21854
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.21.2",
3
+ "version": "3.21.3-dev.0",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {