@contentstack/cli-cm-export 0.1.1-beta.9 → 1.1.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.
@@ -4,189 +4,235 @@
4
4
  * MIT Licensed
5
5
  */
6
6
 
7
- const nativeRequest = require('request')
8
- const mkdirp = require('mkdirp')
9
- const path = require('path')
10
- const fs = require('fs')
11
- const Promise = require('bluebird')
12
- const _ = require('lodash')
13
- const chalk = require('chalk')
7
+ const mkdirp = require('mkdirp');
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const Promise = require('bluebird');
11
+ const _ = require('lodash');
12
+ const chalk = require('chalk');
13
+ const progress = require('progress-stream');
14
+ const { HttpClient } = require('@contentstack/cli-utilities');
14
15
 
15
- const helper = require('../util/helper')
16
- const {addlogs} = require('../util/log')
16
+ const helper = require('../util/helper');
17
+ const { addlogs } = require('../util/log');
17
18
 
18
- let config = require('../../config/default')
19
- const stack = require('../util/contentstack-management-sdk')
20
- const assetConfig = config.modules.assets
21
- const invalidKeys = assetConfig.invalidKeys
19
+ let config = require('../../config/default');
20
+ const stack = require('../util/contentstack-management-sdk');
21
+ const assetConfig = config.modules.assets;
22
+ const invalidKeys = assetConfig.invalidKeys;
23
+ const httpClient = HttpClient.create();
22
24
 
23
25
  // The no. of assets fetched and processed in a batch
24
- const bLimit = assetConfig.batchLimit || 15
26
+ const bLimit = assetConfig.batchLimit || 15;
25
27
 
26
28
  // The no. of asset files downloaded at a time
27
- const vLimit = assetConfig.downloadLimit || 3
28
- let assetsFolderPath
29
- let assetContentsFile
30
- let folderJSONPath
31
- let client
29
+ const vLimit = assetConfig.downloadLimit || 3;
30
+ let assetsFolderPath;
31
+ let assetContentsFile;
32
+ let folderJSONPath;
33
+ let client;
32
34
 
33
35
  function ExportAssets() {
34
- this.assetContents = {}
35
- this.folderData = []
36
+ this.assetContents = {};
37
+ this.folderData = [];
36
38
  }
37
39
 
38
40
  ExportAssets.prototype = {
39
41
  start: function (credentialConfig) {
40
- this.assetContents = {}
41
- this.folderData = []
42
- let self = this
43
- config = credentialConfig
44
- assetsFolderPath = path.resolve(config.data, (config.branchName || ""), assetConfig.dirName)
45
- assetContentsFile = path.resolve(assetsFolderPath, 'assets.json')
46
- folderJSONPath = path.resolve(assetsFolderPath, 'folders.json')
47
- client = stack.Client(config)
48
-
49
- addlogs(config, 'Starting assets export', 'success')
42
+ this.assetContents = {};
43
+ this.folderData = [];
44
+ this.assetDownloadRetry = {};
45
+ this.assetDownloadRetryLimit = 3;
46
+ let self = this;
47
+ config = credentialConfig;
48
+ assetsFolderPath = path.resolve(config.data, config.branchName || '', assetConfig.dirName);
49
+ assetContentsFile = path.resolve(assetsFolderPath, 'assets.json');
50
+ folderJSONPath = path.resolve(assetsFolderPath, 'folders.json');
51
+ client = stack.Client(config);
52
+
53
+ addlogs(config, 'Starting assets export', 'success');
50
54
  // Create asset folder
51
- mkdirp.sync(assetsFolderPath)
55
+ mkdirp.sync(assetsFolderPath);
52
56
  return new Promise(function (resolve, reject) {
53
57
  //TBD: getting all the assets should have optimized
54
- return self.getAssetCount().then(function (count) {
55
- if (typeof count !== 'number' || count === 0) {
56
- addlogs(config, 'No assets found', 'success')
57
- return resolve()
58
- }
59
- const assetBatches = []
60
- for (let i = 0; i <= count; i += bLimit) {
61
- assetBatches.push(i)
62
- }
63
- return Promise.map(assetBatches, function (batch) {
64
- return self.getAssetJSON(batch).then(function (assetsJSON) {
65
- return Promise.map(assetsJSON, function (assetJSON) {
66
- return self.getVersionedAssetJSON(assetJSON.uid, assetJSON._version).then(
67
- function () {
68
- self.assetContents[assetJSON.uid] = assetJSON
69
- // log.success(chalk.white('The following asset has been downloaded successfully: ' +
70
- // assetJSON.uid))
71
- }).catch(function (error) {
72
- addlogs(self.configchalk.red('The following asset failed to download\n' + JSON.stringify(
73
- assetJSON)))
74
- addlogs(config, error, 'error')
75
- })
76
- }, {
77
- concurrency: vLimit,
78
- }).then(function () {
79
- addlogs(config, 'Batch no ' + (batch + 1) + ' of assets is complete', 'success')
80
- helper.writeFile(assetContentsFile, self.assetContents)
81
- }).catch(function (error) {
82
- addlogs(config, 'Asset batch ' + (batch + 1) + ' failed to download', 'error')
83
- addlogs(config, error, 'error')
84
- // log this error onto a file - send over retries
58
+ return self
59
+ .getAssetCount()
60
+ .then(function (count) {
61
+ if (typeof count !== 'number' || count === 0) {
62
+ addlogs(config, 'No assets found', 'success');
63
+ return resolve();
64
+ }
65
+ const assetBatches = [];
66
+ for (let i = 0; i <= count; i += bLimit) {
67
+ assetBatches.push(i);
68
+ }
69
+ return Promise.map(
70
+ assetBatches,
71
+ function (batch) {
72
+ return self
73
+ .getAssetJSON(batch)
74
+ .then(function (assetsJSON) {
75
+ return Promise.map(
76
+ assetsJSON,
77
+ function (assetJSON) {
78
+ return self
79
+ .getVersionedAssetJSON(assetJSON.uid, assetJSON._version)
80
+ .then(function () {
81
+ self.assetContents[assetJSON.uid] = assetJSON;
82
+ // log.success(chalk.white('The following asset has been downloaded successfully: ' +
83
+ // assetJSON.uid))
84
+ })
85
+ .catch(function (error) {
86
+ addlogs(
87
+ config,
88
+ chalk.red('The following asset failed to download\n' + JSON.stringify(assetJSON)),
89
+ );
90
+ addlogs(config, error, 'error');
91
+ });
92
+ },
93
+ {
94
+ concurrency: vLimit,
95
+ },
96
+ )
97
+ .then(function () {
98
+ addlogs(config, 'Batch no ' + (batch + 1) + ' of assets is complete', 'success');
99
+ helper.writeFile(assetContentsFile, self.assetContents);
100
+ })
101
+ .catch(function (error) {
102
+ console.log('Error fetch/download the asset', error && error.message);
103
+ addlogs(config, 'Asset batch ' + (batch + 1) + ' failed to download', 'error');
104
+ addlogs(config, error, 'error');
105
+ // log this error onto a file - send over retries
106
+ });
107
+ })
108
+ .catch(function (error) {
109
+ return reject(error);
110
+ });
111
+ },
112
+ {
113
+ concurrency: 1,
114
+ },
115
+ )
116
+ .then(function () {
117
+ return self
118
+ .exportFolders()
119
+ .then(function () {
120
+ addlogs(config, chalk.green('Asset export completed successfully'), 'success');
121
+ return resolve();
122
+ })
123
+ .catch(function (error) {
124
+ return reject(error);
125
+ });
85
126
  })
86
- }).catch(function (error) {
87
- return reject(error)
88
- })
89
- }, {
90
- concurrency: 1,
91
- }).then(function () {
92
- return self.exportFolders().then(function () {
93
- addlogs(config, chalk.green('Asset export completed successfully'), 'success')
94
- return resolve()
95
- }).catch(function (error) {
96
- return reject(error)
97
- })
98
- }).catch(function (error) {
99
- addlogs(config, chalk.red('Asset export failed due to the following errrors ' + JSON.stringify(
100
- error), 'error'))
101
- return reject(error)
127
+ .catch(function (error) {
128
+ addlogs(
129
+ config,
130
+ chalk.red('Asset export failed due to the following errrors ' + JSON.stringify(error), 'error'),
131
+ );
132
+ return reject(error);
133
+ });
102
134
  })
103
- }).catch(function (error) {
104
- // addlogs(config, chalk.red('Failed to download assets due to the following error: ' + JSON.stringify(
105
- // error)));
106
- return reject(error)
107
- })
108
- })
135
+ .catch(function (error) {
136
+ return reject(error);
137
+ });
138
+ });
109
139
  },
110
140
  exportFolders: function () {
111
- let self = this
141
+ let self = this;
112
142
  return new Promise(function (resolve, reject) {
113
- return self.getAssetCount(true).then(function (fCount) {
114
- if (fCount === 0) {
115
- addlogs(config, 'No folders were found in the stack!', 'success')
116
- return resolve()
117
- }
118
- return self.getFolderJSON(0, fCount).then(function () {
119
- // asset folders have been successfully exported
120
- addlogs(config, 'Asset-folders have been successfully exported!', 'success')
121
- return resolve()
122
- }).catch(function (error) {
123
- addlogs(config, chalk.red('Error while exporting asset-folders!'), 'error')
124
- return reject(error)
143
+ return self
144
+ .getAssetCount(true)
145
+ .then(function (fCount) {
146
+ if (fCount === 0) {
147
+ addlogs(config, 'No folders were found in the stack!', 'success');
148
+ return resolve();
149
+ }
150
+ return self
151
+ .getFolderJSON(0, fCount)
152
+ .then(function () {
153
+ // asset folders have been successfully exported
154
+ addlogs(config, 'Asset-folders have been successfully exported!', 'success');
155
+ return resolve();
156
+ })
157
+ .catch(function (error) {
158
+ addlogs(config, chalk.red('Error while exporting asset-folders!'), 'error');
159
+ return reject(error);
160
+ });
125
161
  })
126
- }).catch(function (error) {
127
- addlogs(config, error, 'error')
128
- // error while fetching asset folder count
129
- return reject(error)
130
- })
131
- })
162
+ .catch(function (error) {
163
+ addlogs(config, error, 'error');
164
+ // error while fetching asset folder count
165
+ return reject(error);
166
+ });
167
+ });
132
168
  },
133
169
  getFolderJSON: function (skip, fCount) {
134
- let self = this
170
+ let self = this;
135
171
  return new Promise(function (resolve, reject) {
136
172
  if (typeof skip !== 'number') {
137
- skip = 0
173
+ skip = 0;
138
174
  }
139
175
  if (skip >= fCount) {
140
- helper.writeFile(folderJSONPath, self.folderData)
141
- return resolve()
176
+ helper.writeFile(folderJSONPath, self.folderData);
177
+ return resolve();
142
178
  }
143
179
 
144
180
  const queryRequestObj = {
145
181
  include_folders: true,
146
- query: {'is_dir': true},
182
+ query: { is_dir: true },
147
183
  skip: skip,
148
- }
184
+ };
149
185
 
150
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
151
- .then(response => {
152
- response.items.forEach(function (folder) {
153
- self.folderData.push(folder)
154
- })
155
- skip += 100
156
- return self.getFolderJSON(skip, fCount)
157
- .then(resolve)
158
- .catch(reject)
159
- })
160
- })
186
+ client
187
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
188
+ .asset()
189
+ .query(queryRequestObj)
190
+ .find()
191
+ .then((response) => {
192
+ response.items.forEach(function (folder) {
193
+ self.folderData.push(folder);
194
+ });
195
+ skip += 100;
196
+ return self.getFolderJSON(skip, fCount).then(resolve).catch(reject);
197
+ });
198
+ });
161
199
  },
162
200
  getAssetCount: function (folder) {
163
201
  return new Promise(function (resolve, reject) {
164
202
  if (folder && typeof folder === 'boolean') {
165
- let queryOptions = {include_folders: true, query: {'is_dir': true}, include_count: true}
166
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryOptions).find()
167
- .then(asset => {
168
- return resolve(asset.count)
169
- })
170
- .catch(error => {
171
- addlogs(config, error, 'error')
172
- })
203
+ let queryOptions = { include_folders: true, query: { is_dir: true }, include_count: true };
204
+ client
205
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
206
+ .asset()
207
+ .query(queryOptions)
208
+ .find()
209
+ .then((asset) => {
210
+ return resolve(asset.count);
211
+ })
212
+ .catch((error) => {
213
+ addlogs(config, error, 'error');
214
+ });
173
215
  } else {
174
- let queryOptions = {include_count: true}
175
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryOptions).find()
176
- .then(asset => {
177
- return resolve(asset.count)
178
- })
179
- .catch(error => {
180
- addlogs(config, error, 'error')
181
- return reject()
182
- })
216
+ let queryOptions = { include_count: true };
217
+ client
218
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
219
+ .asset()
220
+ .query(queryOptions)
221
+ .find()
222
+ .then((asset) => {
223
+ return resolve(asset.count);
224
+ })
225
+ .catch((error) => {
226
+ addlogs(config, error, 'error');
227
+ return reject();
228
+ });
183
229
  }
184
- })
230
+ });
185
231
  },
186
232
  getAssetJSON: function (skip) {
187
233
  return new Promise(function (resolve, reject) {
188
234
  if (typeof skip !== 'number') {
189
- skip = 0
235
+ skip = 0;
190
236
  }
191
237
  const queryRequestObj = {
192
238
  skip: skip,
@@ -195,25 +241,35 @@ ExportAssets.prototype = {
195
241
  except: {
196
242
  BASE: invalidKeys,
197
243
  },
198
- }
244
+ };
199
245
 
200
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
201
- .then(assetResponse => {
202
- return resolve(assetResponse.items)
203
- }).catch(error => {
204
- addlogs(config, error, 'error')
205
- return reject()
206
- })
207
- })
246
+ client
247
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
248
+ .asset()
249
+ .query(queryRequestObj)
250
+ .find()
251
+ .then((assetResponse) => {
252
+ return resolve(assetResponse.items);
253
+ })
254
+ .catch((error) => {
255
+ addlogs(config, error, 'error');
256
+ return reject();
257
+ });
258
+ });
208
259
  },
209
260
  getVersionedAssetJSON: function (uid, version, bucket) {
210
- let self = this
211
- let assetVersionInfo = bucket || []
261
+ let self = this;
262
+ let assetVersionInfo = bucket || [];
212
263
  return new Promise(function (resolve, reject) {
264
+ if (self.assetDownloadRetry[uid + version] > self.assetDownloadRetryLimit) {
265
+ console.log('Reached max', self.assetDownloadRetry[uid + version]);
266
+ return reject(new Error('Asset Max download retry limit exceeded! ' + uid));
267
+ }
268
+
213
269
  if (version <= 0) {
214
- const assetVersionInfoFile = path.resolve(assetsFolderPath, uid, '_contentstack_' + uid + '.json')
215
- helper.writeFile(assetVersionInfoFile, assetVersionInfo)
216
- return resolve()
270
+ const assetVersionInfoFile = path.resolve(assetsFolderPath, uid, '_contentstack_' + uid + '.json');
271
+ helper.writeFile(assetVersionInfoFile, assetVersionInfo);
272
+ return resolve();
217
273
  }
218
274
  let queryrequestOption = {
219
275
  version: version,
@@ -221,97 +277,142 @@ ExportAssets.prototype = {
221
277
  except: {
222
278
  BASE: invalidKeys,
223
279
  },
224
- }
280
+ };
225
281
 
226
- client.stack({ api_key: config.source_stack, management_token: config.management_token}).asset(uid).fetch(queryrequestOption)
227
- .then(versionedAssetJSONResponse => {
228
- self.downloadAsset(versionedAssetJSONResponse).then(function () {
229
- assetVersionInfo.splice(0, 0, versionedAssetJSONResponse)
230
- // Remove duplicates
231
- assetVersionInfo = _.uniqWith(assetVersionInfo, _.isEqual)
232
- self.getVersionedAssetJSON(uid, --version, assetVersionInfo)
233
- .then(resolve)
234
- .catch(reject)
235
- }).catch(reject)
236
- })
237
- })
282
+ client
283
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
284
+ .asset(uid)
285
+ .fetch(queryrequestOption)
286
+ .then((versionedAssetJSONResponse) => {
287
+ self
288
+ .downloadAsset(versionedAssetJSONResponse)
289
+ .then(function () {
290
+ assetVersionInfo.splice(0, 0, versionedAssetJSONResponse);
291
+ // Remove duplicates
292
+ assetVersionInfo = _.uniqWith(assetVersionInfo, _.isEqual);
293
+ self
294
+ .getVersionedAssetJSON(uid, --version, assetVersionInfo)
295
+ .then(resolve)
296
+ .catch(reject);
297
+ })
298
+ .catch(reject);
299
+ })
300
+ .catch((error) => {
301
+ console.log('Error on fetch', error && error.message);
302
+ if (error.status === 408) {
303
+ console.log('retrying', uid);
304
+ // retrying when timeout
305
+ self.assetDownloadRetry[uid + version]
306
+ ? ++self.assetDownloadRetry[uid + version]
307
+ : (self.assetDownloadRetry[uid + version] = 1);
308
+ return self.getVersionedAssetJSON(uid, version, assetVersionInfo).then(resolve).catch(reject);
309
+ }
310
+ reject(error);
311
+ });
312
+ });
238
313
  },
239
314
  downloadAsset: function (asset) {
240
- let self = this
241
- return new Promise(function (resolve, reject) {
242
- const assetFolderPath = path.resolve(assetsFolderPath, asset.uid)
243
- const assetFilePath = path.resolve(assetFolderPath, asset.filename)
315
+ let self = this;
316
+ return new Promise(async function (resolve, reject) {
317
+ const assetFolderPath = path.resolve(assetsFolderPath, asset.uid);
318
+ const assetFilePath = path.resolve(assetFolderPath, asset.filename);
244
319
  if (fs.existsSync(assetFilePath)) {
245
- addlogs(config, 'Skipping download of { title: ' + asset.filename + ', uid: ' +
246
- asset.uid + ' }, as they already exist', 'success')
247
- return resolve()
320
+ addlogs(
321
+ config,
322
+ 'Skipping download of { title: ' + asset.filename + ', uid: ' + asset.uid + ' }, as they already exist',
323
+ 'success',
324
+ );
325
+ return resolve();
248
326
  }
249
327
  self.assetStream = {
250
- url: config.securedAssets
251
- ? `${asset.url}?authtoken=${config.authtoken || config.auth_token}`
252
- : asset.url,
328
+ url: config.securedAssets ? `${asset.url}?authtoken=${config.authtoken || config.auth_token}` : asset.url,
253
329
  };
254
330
 
255
- const assetStreamRequest = nativeRequest(self.assetStream)
256
- assetStreamRequest.on('response', function () {
257
- helper.makeDirectory(assetFolderPath)
258
- const assetFileStream = fs.createWriteStream(assetFilePath)
259
- assetStreamRequest.pipe(assetFileStream)
260
- assetFileStream.on('close', function () {
261
- addlogs(config, 'Downloaded ' + asset.filename + ': ' + asset.uid + ' successfully!', 'success')
262
- return resolve()
331
+ helper.makeDirectory(assetFolderPath);
332
+ const assetFileStream = fs.createWriteStream(assetFilePath);
333
+ self.assetStream.url = encodeURI(self.assetStream.url);
334
+ httpClient
335
+ .options({ responseType: 'stream' })
336
+ .get(self.assetStream.url)
337
+ .then(({ data: assetStreamRequest }) => {
338
+ if (assetConfig.enableDownloadStatus) {
339
+ const str = progress({
340
+ time: 5000,
341
+ length: assetStreamRequest.headers['content-length'],
342
+ });
343
+ str.on('progress', function (progressData) {
344
+ console.log(`${asset.filename}: ${Math.round(progressData.percentage)}%`);
345
+ });
346
+ assetStreamRequest.pipe(str).pipe(assetFileStream);
347
+ }
348
+ assetStreamRequest.pipe(assetFileStream);
349
+ })
350
+ .catch(reject);
351
+ assetFileStream
352
+ .on('close', function () {
353
+ addlogs(config, 'Downloaded ' + asset.filename + ': ' + asset.uid + ' successfully!', 'success');
354
+ return resolve();
263
355
  })
264
- }).on('error', reject)
265
- })
356
+ .on('error', reject);
357
+ });
266
358
  },
267
359
  getFolders: function () {
268
- let self = this
360
+ let self = this;
269
361
  return new Promise(function (resolve, reject) {
270
- return self.getAssetCount(true).then(function (count) {
271
- if (count === 0) {
272
- addlogs(config, 'No folders were found in the stack', 'success')
273
- return resolve()
274
- }
275
- return self.getFolderDetails(0, count).then(function () {
276
- addlogs(config, chalk.green('Exported asset-folders successfully!'), 'success')
277
- return resolve()
278
- }).catch(function (error) {
279
- return reject(error)
362
+ return self
363
+ .getAssetCount(true)
364
+ .then(function (count) {
365
+ if (count === 0) {
366
+ addlogs(config, 'No folders were found in the stack', 'success');
367
+ return resolve();
368
+ }
369
+ return self
370
+ .getFolderDetails(0, count)
371
+ .then(function () {
372
+ addlogs(config, chalk.green('Exported asset-folders successfully!'), 'success');
373
+ return resolve();
374
+ })
375
+ .catch(function (error) {
376
+ return reject(error);
377
+ });
280
378
  })
281
- }).catch(function (error) {
282
- return reject(error)
283
- })
284
- })
379
+ .catch(function (error) {
380
+ return reject(error);
381
+ });
382
+ });
285
383
  },
286
384
  getFolderDetails: function (skip, tCount) {
287
- let self = this
385
+ let self = this;
288
386
  return new Promise(function (resolve, reject) {
289
387
  if (typeof skip !== 'number') {
290
- skip = 0
388
+ skip = 0;
291
389
  }
292
390
  if (skip > tCount) {
293
- helper.writeFile(folderJSONPath, self.folderContents)
294
- return resolve()
391
+ helper.writeFile(folderJSONPath, self.folderContents);
392
+ return resolve();
295
393
  }
296
394
  let queryRequestObj = {
297
395
  include_folders: true,
298
- query: {'is_dir': true},
396
+ query: { is_dir: true },
299
397
  skip: skip,
300
- }
301
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
302
- .then(folderDetailsResponse => {
303
- for(let i in folderDetailsResponse.items) {
304
- self.folderContents.push(folderDetailsResponse.items[i])
305
- }
306
- skip += 100
307
- return self.getFolderDetails(skip, tCount)
308
- .then(resolve)
309
- .catch(reject)
310
- }).catch(error => {
311
- addlogs(config, error, 'error')
312
- })
313
- })
398
+ };
399
+ client
400
+ .stack({ api_key: config.source_stack, management_token: config.management_token })
401
+ .asset()
402
+ .query(queryRequestObj)
403
+ .find()
404
+ .then((folderDetailsResponse) => {
405
+ for (let i in folderDetailsResponse.items) {
406
+ self.folderContents.push(folderDetailsResponse.items[i]);
407
+ }
408
+ skip += 100;
409
+ return self.getFolderDetails(skip, tCount).then(resolve).catch(reject);
410
+ })
411
+ .catch((error) => {
412
+ addlogs(config, error, 'error');
413
+ });
414
+ });
314
415
  },
315
- }
416
+ };
316
417
 
317
- module.exports = new ExportAssets()
418
+ module.exports = new ExportAssets();