@contentstack/cli-cm-export 0.1.1-beta.7 → 1.0.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,188 +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
- let self = this
41
- config = credentialConfig
42
- assetsFolderPath = path.resolve(config.data, assetConfig.dirName)
43
- assetContentsFile = path.resolve(assetsFolderPath, 'assets.json')
44
- folderJSONPath = path.resolve(assetsFolderPath, 'folders.json')
45
- client = stack.Client(config)
46
-
47
- 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');
48
54
  // Create asset folder
49
- mkdirp.sync(assetsFolderPath)
55
+ mkdirp.sync(assetsFolderPath);
50
56
  return new Promise(function (resolve, reject) {
51
- return self.getAssetCount().then(function (count) {
52
- if (typeof count !== 'number' || count === 0) {
53
- addlogs(config, 'No assets found', 'success')
54
- return resolve()
55
- }
56
- const assetBatches = []
57
- for (let i = 0; i <= count; i += bLimit) {
58
- assetBatches.push(i)
59
- }
60
- return Promise.map(assetBatches, function (batch) {
61
- return self.getAssetJSON(batch).then(function (assetsJSON) {
62
- return Promise.map(assetsJSON, function (assetJSON) {
63
- return self.getVersionedAssetJSON(assetJSON.uid, assetJSON._version).then(
64
- function () {
65
- self.assetContents[assetJSON.uid] = assetJSON
66
- // log.success(chalk.white('The following asset has been downloaded successfully: ' +
67
- // assetJSON.uid))
68
- }).catch(function (error) {
69
- addlogs(self.configchalk.red('The following asset failed to download\n' + JSON.stringify(
70
- assetJSON)))
71
- addlogs(config, error, 'error')
72
- })
73
- }, {
74
- concurrency: vLimit,
75
- }).then(function () {
76
- addlogs(config, 'Batch no ' + (batch + 1) + ' of assets is complete', 'success')
77
- helper.writeFile(assetContentsFile, self.assetContents)
78
- }).catch(function (error) {
79
- addlogs(config, 'Asset batch ' + (batch + 1) + ' failed to download', 'error')
80
- addlogs(config, error, 'error')
81
- // log this error onto a file - send over retries
57
+ //TBD: getting all the assets should have optimized
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
+ });
82
126
  })
83
- }).catch(function (error) {
84
- return reject(error)
85
- })
86
- }, {
87
- concurrency: 1,
88
- }).then(function () {
89
- return self.exportFolders().then(function () {
90
- addlogs(config, chalk.green('Asset export completed successfully'), 'success')
91
- return resolve()
92
- }).catch(function (error) {
93
- return reject(error)
94
- })
95
- }).catch(function (error) {
96
- addlogs(config, chalk.red('Asset export failed due to the following errrors ' + JSON.stringify(
97
- error), 'error'))
98
- 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
+ });
99
134
  })
100
- }).catch(function (error) {
101
- // addlogs(config, chalk.red('Failed to download assets due to the following error: ' + JSON.stringify(
102
- // error)));
103
- return reject(error)
104
- })
105
- })
135
+ .catch(function (error) {
136
+ return reject(error);
137
+ });
138
+ });
106
139
  },
107
140
  exportFolders: function () {
108
- let self = this
141
+ let self = this;
109
142
  return new Promise(function (resolve, reject) {
110
- return self.getAssetCount(true).then(function (fCount) {
111
- if (fCount === 0) {
112
- addlogs(config, 'No folders were found in the stack!', 'success')
113
- return resolve()
114
- }
115
- return self.getFolderJSON(0, fCount).then(function () {
116
- // asset folders have been successfully exported
117
- addlogs(config, 'Asset-folders have been successfully exported!', 'success')
118
- return resolve()
119
- }).catch(function (error) {
120
- addlogs(config, chalk.red('Error while exporting asset-folders!'), 'error')
121
- 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
+ });
122
161
  })
123
- }).catch(function (error) {
124
- addlogs(config, error, 'error')
125
- // error while fetching asset folder count
126
- return reject(error)
127
- })
128
- })
162
+ .catch(function (error) {
163
+ addlogs(config, error, 'error');
164
+ // error while fetching asset folder count
165
+ return reject(error);
166
+ });
167
+ });
129
168
  },
130
169
  getFolderJSON: function (skip, fCount) {
131
- let self = this
170
+ let self = this;
132
171
  return new Promise(function (resolve, reject) {
133
172
  if (typeof skip !== 'number') {
134
- skip = 0
173
+ skip = 0;
135
174
  }
136
175
  if (skip >= fCount) {
137
- helper.writeFile(folderJSONPath, self.folderData)
138
- return resolve()
176
+ helper.writeFile(folderJSONPath, self.folderData);
177
+ return resolve();
139
178
  }
140
179
 
141
180
  const queryRequestObj = {
142
181
  include_folders: true,
143
- query: {'is_dir': true},
182
+ query: { is_dir: true },
144
183
  skip: skip,
145
- }
184
+ };
146
185
 
147
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
148
- .then(response => {
149
- response.items.forEach(function (folder) {
150
- self.folderData.push(folder)
151
- })
152
- skip += 100
153
- return self.getFolderJSON(skip, fCount)
154
- .then(resolve)
155
- .catch(reject)
156
- })
157
- })
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
+ });
158
199
  },
159
200
  getAssetCount: function (folder) {
160
- let self = this
161
201
  return new Promise(function (resolve, reject) {
162
202
  if (folder && typeof folder === 'boolean') {
163
- let queryOptions = {include_folders: true, query: {'is_dir': true}, include_count: true}
164
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryOptions).find()
165
- .then(asset => {
166
- return resolve(asset.count)
167
- })
168
- .catch(error => {
169
- addlogs(config, error, 'error')
170
- })
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
+ });
171
215
  } else {
172
- let queryOptions = {include_count: true}
173
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryOptions).find()
174
- .then(asset => {
175
- return resolve(asset.count)
176
- })
177
- .catch(error => {
178
- addlogs(config, error, 'error')
179
- return reject()
180
- })
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
+ });
181
229
  }
182
- })
230
+ });
183
231
  },
184
232
  getAssetJSON: function (skip) {
185
- let self = this
186
233
  return new Promise(function (resolve, reject) {
187
234
  if (typeof skip !== 'number') {
188
- skip = 0
235
+ skip = 0;
189
236
  }
190
237
  const queryRequestObj = {
191
238
  skip: skip,
@@ -194,25 +241,35 @@ ExportAssets.prototype = {
194
241
  except: {
195
242
  BASE: invalidKeys,
196
243
  },
197
- }
244
+ };
198
245
 
199
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
200
- .then(assetResponse => {
201
- return resolve(assetResponse.items)
202
- }).catch(error => {
203
- addlogs(config, error, 'error')
204
- return reject()
205
- })
206
- })
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
+ });
207
259
  },
208
260
  getVersionedAssetJSON: function (uid, version, bucket) {
209
- let self = this
210
- let assetVersionInfo = bucket || []
261
+ let self = this;
262
+ let assetVersionInfo = bucket || [];
211
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
+
212
269
  if (version <= 0) {
213
- const assetVersionInfoFile = path.resolve(assetsFolderPath, uid, '_contentstack_' + uid + '.json')
214
- helper.writeFile(assetVersionInfoFile, assetVersionInfo)
215
- return resolve()
270
+ const assetVersionInfoFile = path.resolve(assetsFolderPath, uid, '_contentstack_' + uid + '.json');
271
+ helper.writeFile(assetVersionInfoFile, assetVersionInfo);
272
+ return resolve();
216
273
  }
217
274
  let queryrequestOption = {
218
275
  version: version,
@@ -220,97 +277,142 @@ ExportAssets.prototype = {
220
277
  except: {
221
278
  BASE: invalidKeys,
222
279
  },
223
- }
280
+ };
224
281
 
225
- client.stack({ api_key: config.source_stack, management_token: config.management_token}).asset(uid).fetch(queryrequestOption)
226
- .then(versionedAssetJSONResponse => {
227
- self.downloadAsset(versionedAssetJSONResponse).then(function () {
228
- assetVersionInfo.splice(0, 0, versionedAssetJSONResponse)
229
- // Remove duplicates
230
- assetVersionInfo = _.uniqWith(assetVersionInfo, _.isEqual)
231
- self.getVersionedAssetJSON(uid, --version, assetVersionInfo)
232
- .then(resolve)
233
- .catch(reject)
234
- }).catch(reject)
235
- })
236
- })
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
+ });
237
313
  },
238
314
  downloadAsset: function (asset) {
239
- let self = this
240
- return new Promise(function (resolve, reject) {
241
- const assetFolderPath = path.resolve(assetsFolderPath, asset.uid)
242
- 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);
243
319
  if (fs.existsSync(assetFilePath)) {
244
- addlogs(config, 'Skipping download of { title: ' + asset.filename + ', uid: ' +
245
- asset.uid + ' }, as they already exist', 'success')
246
- 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();
247
326
  }
248
327
  self.assetStream = {
249
- url: config.securedAssets
250
- ? `${asset.url}?authtoken=${config.authtoken || config.auth_token}`
251
- : asset.url,
328
+ url: config.securedAssets ? `${asset.url}?authtoken=${config.authtoken || config.auth_token}` : asset.url,
252
329
  };
253
330
 
254
- const assetStreamRequest = nativeRequest(self.assetStream)
255
- assetStreamRequest.on('response', function () {
256
- helper.makeDirectory(assetFolderPath)
257
- const assetFileStream = fs.createWriteStream(assetFilePath)
258
- assetStreamRequest.pipe(assetFileStream)
259
- assetFileStream.on('close', function () {
260
- addlogs(config, 'Downloaded ' + asset.filename + ': ' + asset.uid + ' successfully!', 'success')
261
- 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();
262
355
  })
263
- }).on('error', reject)
264
- })
356
+ .on('error', reject);
357
+ });
265
358
  },
266
359
  getFolders: function () {
267
- let self = this
360
+ let self = this;
268
361
  return new Promise(function (resolve, reject) {
269
- return self.getAssetCount(true).then(function (count) {
270
- if (count === 0) {
271
- addlogs(config, 'No folders were found in the stack', 'success')
272
- return resolve()
273
- }
274
- return self.getFolderDetails(0, count).then(function () {
275
- addlogs(config, chalk.green('Exported asset-folders successfully!'), 'success')
276
- return resolve()
277
- }).catch(function (error) {
278
- 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
+ });
279
378
  })
280
- }).catch(function (error) {
281
- return reject(error)
282
- })
283
- })
379
+ .catch(function (error) {
380
+ return reject(error);
381
+ });
382
+ });
284
383
  },
285
384
  getFolderDetails: function (skip, tCount) {
286
- let self = this
385
+ let self = this;
287
386
  return new Promise(function (resolve, reject) {
288
387
  if (typeof skip !== 'number') {
289
- skip = 0
388
+ skip = 0;
290
389
  }
291
390
  if (skip > tCount) {
292
- helper.writeFile(folderJSONPath, self.folderContents)
293
- return resolve()
391
+ helper.writeFile(folderJSONPath, self.folderContents);
392
+ return resolve();
294
393
  }
295
394
  let queryRequestObj = {
296
395
  include_folders: true,
297
- query: {'is_dir': true},
396
+ query: { is_dir: true },
298
397
  skip: skip,
299
- }
300
- client.stack({api_key: config.source_stack, management_token: config.management_token}).asset().query(queryRequestObj).find()
301
- .then(folderDetailsResponse => {
302
- for(let i in folderDetailsResponse.items) {
303
- self.folderContents.push(folderDetailsResponse.items[i])
304
- }
305
- skip += 100
306
- return self.getFolderDetails(skip, tCount)
307
- .then(resolve)
308
- .catch(reject)
309
- }).catch(error => {
310
- addlogs(config, error, 'error')
311
- })
312
- })
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
+ });
313
415
  },
314
- }
416
+ };
315
417
 
316
- module.exports = new ExportAssets()
418
+ module.exports = new ExportAssets();