@sanity/export 0.136.3-gql-rtb.372 → 0.136.3-purple-unicorn-patch.5627

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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Sanity
3
+ Copyright (c) 2016 - 2022 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -20,8 +20,8 @@ exportDataset({
20
20
  // Name of dataset to export
21
21
  dataset: 'myDataset',
22
22
 
23
- // Path to write zip-file to, or `-` for stdout
24
- outputPath: '/home/your-user/myDataset.zip',
23
+ // Path to write tar.gz-archive file to, or `-` for stdout
24
+ outputPath: '/home/your-user/myDataset.tar.gz',
25
25
 
26
26
  // Whether or not to export assets. Note that this operation is currently slightly lossy;
27
27
  // metadata stored on the asset document itself (original filename, for instance) might be lost
@@ -38,14 +38,17 @@ exportDataset({
38
38
 
39
39
  // Export only given document types (`_type`)
40
40
  // Optional, default: all types
41
- types: ['products', 'shops']
41
+ types: ['products', 'shops'],
42
+
43
+ // Run 12 concurrent asset downloads
44
+ assetConcurrency: 12
42
45
  })
43
46
  ```
44
47
 
45
48
  ## Future improvements
46
49
 
47
- * Restore original filenames, keep track of duplicates, increase counter (`filename (<num>).ext`)
48
- * Skip archiving on raw/no-asset mode?
50
+ - Restore original filenames, keep track of duplicates, increase counter (`filename (<num>).ext`)
51
+ - Skip archiving on raw/no-asset mode?
49
52
 
50
53
  ## CLI-tool
51
54
 
@@ -1,37 +1,26 @@
1
1
  "use strict";
2
2
 
3
- function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
4
-
5
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
6
-
7
- function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
8
-
9
- function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
10
-
11
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
12
-
13
- function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
14
-
15
- function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
16
-
17
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
18
-
19
- function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
20
-
21
3
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
22
4
 
23
5
  const path = require('path');
24
6
 
25
7
  const crypto = require('crypto');
26
8
 
9
+ const {
10
+ parse: parseUrl,
11
+ format: formatUrl
12
+ } = require('url');
13
+
27
14
  const fse = require('fs-extra');
28
15
 
29
16
  const miss = require('mississippi');
30
17
 
31
18
  const PQueue = require('p-queue');
32
19
 
33
- const _require = require('lodash'),
34
- omit = _require.omit;
20
+ const {
21
+ omit,
22
+ noop
23
+ } = require('lodash');
35
24
 
36
25
  const pkg = require('../package.json');
37
26
 
@@ -42,51 +31,31 @@ const debug = require('./debug');
42
31
  const EXCLUDE_PROPS = ['_id', '_type', 'assetId', 'extension', 'mimeType', 'path', 'url'];
43
32
  const ACTION_REMOVE = 'remove';
44
33
  const ACTION_REWRITE = 'rewrite';
34
+ const ASSET_DOWNLOAD_CONCURRENCY = 8;
45
35
 
46
36
  class AssetHandler {
47
37
  constructor(options) {
48
- var _this = this;
49
-
50
- _defineProperty(this, "rewriteAssets", miss.through.obj(
51
- /*#__PURE__*/
52
- function () {
53
- var _ref = _asyncToGenerator(function* (doc, enc, callback) {
54
- if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
55
- const type = doc._type === 'sanity.imageAsset' ? 'image' : 'file';
56
- const filePath = `${type}s/${generateFilename(doc._id)}`;
57
-
58
- _this.assetsSeen.set(doc._id, type);
59
-
60
- _this.queueAssetDownload(doc, filePath, type);
61
-
62
- callback();
63
- return;
64
- }
65
-
66
- callback(null, (yield _this.findAndModify(doc, ACTION_REWRITE)));
67
- });
38
+ _defineProperty(this, "rewriteAssets", miss.through.obj(async (doc, enc, callback) => {
39
+ if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
40
+ const type = doc._type === 'sanity.imageAsset' ? 'image' : 'file';
41
+ const filePath = "".concat(type, "s/").concat(generateFilename(doc._id));
42
+ this.assetsSeen.set(doc._id, type);
43
+ this.queueAssetDownload(doc, filePath, type);
44
+ callback();
45
+ return;
46
+ }
68
47
 
69
- return function (_x, _x2, _x3) {
70
- return _ref.apply(this, arguments);
71
- };
72
- }()));
73
-
74
- _defineProperty(this, "stripAssets", miss.through.obj(
75
- /*#__PURE__*/
76
- function () {
77
- var _ref2 = _asyncToGenerator(function* (doc, enc, callback) {
78
- if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
79
- callback();
80
- return;
81
- }
48
+ callback(null, this.findAndModify(doc, ACTION_REWRITE));
49
+ }));
82
50
 
83
- callback(null, (yield _this.findAndModify(doc, ACTION_REMOVE)));
84
- });
51
+ _defineProperty(this, "stripAssets", miss.through.obj(async (doc, enc, callback) => {
52
+ if (['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type)) {
53
+ callback();
54
+ return;
55
+ }
85
56
 
86
- return function (_x4, _x5, _x6) {
87
- return _ref2.apply(this, arguments);
88
- };
89
- }()));
57
+ callback(null, this.findAndModify(doc, ACTION_REMOVE));
58
+ }));
90
59
 
91
60
  _defineProperty(this, "skipAssets", miss.through.obj((doc, enc, callback) => {
92
61
  const isAsset = ['sanity.imageAsset', 'sanity.fileAsset'].includes(doc._type);
@@ -101,97 +70,69 @@ class AssetHandler {
101
70
 
102
71
  _defineProperty(this, "noop", miss.through.obj((doc, enc, callback) => callback(null, doc)));
103
72
 
104
- _defineProperty(this, "findAndModify",
105
- /*#__PURE__*/
106
- function () {
107
- var _ref3 = _asyncToGenerator(function* (item, action) {
108
- if (Array.isArray(item)) {
109
- const children = yield Promise.all(item.map(child => _this.findAndModify(child, action)));
110
- return children.filter(Boolean);
111
- }
112
-
113
- if (!item || typeof item !== 'object') {
114
- return item;
115
- }
116
-
117
- const isAsset = isAssetField(item);
118
-
119
- if (isAsset && action === ACTION_REMOVE) {
120
- return undefined;
121
- }
122
-
123
- if (isAsset && action === ACTION_REWRITE) {
124
- const asset = item.asset,
125
- other = _objectWithoutProperties(item, ["asset"]);
126
-
127
- const assetId = asset._ref;
73
+ _defineProperty(this, "findAndModify", (item, action) => {
74
+ if (Array.isArray(item)) {
75
+ const children = item.map(child => this.findAndModify(child, action));
76
+ return children.filter(Boolean);
77
+ }
128
78
 
129
- if (isModernAsset(assetId)) {
130
- const assetType = getAssetType(item);
131
- const filePath = `${assetType}s/${generateFilename(assetId)}`;
132
- return _objectSpread({
133
- _sanityAsset: `${assetType}@file://./${filePath}`
134
- }, other);
135
- } // Legacy asset
79
+ if (!item || typeof item !== 'object') {
80
+ return item;
81
+ }
136
82
 
83
+ const isAsset = isAssetField(item);
137
84
 
138
- const type = _this.assetsSeen.get(assetId) || (yield _this.lookupAssetType(assetId));
139
- const filePath = `${type}s/${generateFilename(assetId)}`;
140
- return _objectSpread({
141
- _sanityAsset: `${type}@file://./${filePath}`
142
- }, other);
143
- }
85
+ if (isAsset && action === ACTION_REMOVE) {
86
+ return undefined;
87
+ }
144
88
 
145
- const newItem = {};
146
- const keys = Object.keys(item);
89
+ if (isAsset && action === ACTION_REWRITE) {
90
+ const {
91
+ asset,
92
+ ...other
93
+ } = item;
94
+ const assetId = asset._ref;
95
+ const assetType = getAssetType(item);
96
+ const filePath = "".concat(assetType, "s/").concat(generateFilename(assetId));
97
+ return {
98
+ _sanityAsset: "".concat(assetType, "@file://./").concat(filePath),
99
+ ...this.findAndModify(other, action)
100
+ };
101
+ }
147
102
 
148
- for (let i = 0; i < keys.length; i++) {
149
- const key = keys[i];
150
- const value = item[key]; // eslint-disable-next-line no-await-in-loop
103
+ const newItem = {};
104
+ const keys = Object.keys(item);
151
105
 
152
- newItem[key] = yield _this.findAndModify(value, action);
106
+ for (let i = 0; i < keys.length; i++) {
107
+ const key = keys[i];
108
+ const value = item[key];
109
+ newItem[key] = this.findAndModify(value, action);
153
110
 
154
- if (typeof newItem[key] === 'undefined') {
155
- delete newItem[key];
156
- }
111
+ if (typeof newItem[key] === 'undefined') {
112
+ delete newItem[key];
157
113
  }
114
+ }
158
115
 
159
- return newItem;
160
- });
161
-
162
- return function (_x7, _x8) {
163
- return _ref3.apply(this, arguments);
164
- };
165
- }());
166
-
167
- _defineProperty(this, "lookupAssetType",
168
- /*#__PURE__*/
169
- function () {
170
- var _ref4 = _asyncToGenerator(function* (assetId) {
171
- const docType = yield _this.client.fetch('*[_id == $id][0]._type', {
172
- id: assetId
173
- });
174
- return docType === 'sanity.imageAsset' ? 'image' : 'file';
175
- });
176
-
177
- return function (_x9) {
178
- return _ref4.apply(this, arguments);
179
- };
180
- }());
116
+ return newItem;
117
+ });
181
118
 
119
+ const concurrency = options.concurrency || ASSET_DOWNLOAD_CONCURRENCY;
120
+ debug('Using asset download concurrency of %d', concurrency);
182
121
  this.client = options.client;
183
122
  this.tmpDir = options.tmpDir;
184
123
  this.assetDirsCreated = false;
124
+ this.downloading = [];
185
125
  this.assetsSeen = new Map();
186
126
  this.assetMap = {};
187
127
  this.filesWritten = 0;
188
128
  this.queueSize = 0;
189
129
  this.queue = options.queue || new PQueue({
190
- concurrency: 3
130
+ concurrency
191
131
  });
132
+ this.rejectedError = null;
192
133
 
193
- this.reject = () => {
194
- throw new Error('Asset handler errored before `finish()` was called');
134
+ this.reject = err => {
135
+ this.rejectedError = err;
195
136
  };
196
137
  }
197
138
 
@@ -203,6 +144,11 @@ class AssetHandler {
203
144
 
204
145
  finish() {
205
146
  return new Promise((resolve, reject) => {
147
+ if (this.rejectedError) {
148
+ reject(this.rejectedError);
149
+ return;
150
+ }
151
+
206
152
  this.reject = reject;
207
153
  this.queue.onIdle().then(() => resolve(this.assetMap));
208
154
  });
@@ -218,58 +164,126 @@ class AssetHandler {
218
164
 
219
165
  debug('Adding download task for %s (destination: %s)', assetDoc._id, dstPath);
220
166
  this.queueSize++;
167
+ this.downloading.push(assetDoc.url);
221
168
  this.queue.add(() => this.downloadAsset(assetDoc, dstPath));
222
169
  }
223
170
 
224
- downloadAsset(assetDoc, dstPath) {
225
- var _this2 = this;
171
+ maybeCreateAssetDirs() {
172
+ if (this.assetDirsCreated) {
173
+ return;
174
+ }
175
+ /* eslint-disable no-sync */
176
+
177
+
178
+ fse.ensureDirSync(path.join(this.tmpDir, 'files'));
179
+ fse.ensureDirSync(path.join(this.tmpDir, 'images'));
180
+ /* eslint-enable no-sync */
181
+
182
+ this.assetDirsCreated = true;
183
+ }
184
+
185
+ getAssetRequestOptions(assetDoc) {
186
+ const token = this.client.config().token;
187
+ const headers = {
188
+ 'User-Agent': "".concat(pkg.name, "@").concat(pkg.version)
189
+ };
190
+ const isImage = assetDoc._type === 'sanity.imageAsset';
191
+ const url = parseUrl(assetDoc.url, true);
226
192
 
227
- return _asyncToGenerator(function* () {
228
- const url = assetDoc.url;
229
- const headers = {
230
- 'User-Agent': `${pkg.name}@${pkg.version}`
193
+ if (isImage && ['cdn.sanity.io', 'cdn.sanity.work'].includes(url.hostname)) {
194
+ headers.Authorization = "Bearer ".concat(token);
195
+ url.query = { ...(url.query || {}),
196
+ dlRaw: 'true'
231
197
  };
232
- const stream = yield requestStream({
233
- url,
234
- headers
235
- });
198
+ }
236
199
 
237
- if (stream.statusCode !== 200) {
238
- _this2.queue.clear();
200
+ return {
201
+ url: formatUrl(url),
202
+ headers
203
+ };
204
+ }
239
205
 
240
- _this2.reject(new Error(`Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`));
206
+ async downloadAsset(assetDoc, dstPath) {
207
+ let attemptNum = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
208
+ const {
209
+ url
210
+ } = assetDoc;
211
+ const options = this.getAssetRequestOptions(assetDoc);
212
+ let stream;
213
+
214
+ try {
215
+ stream = await requestStream(options);
216
+ } catch (err) {
217
+ this.reject(err);
218
+ return false;
219
+ }
241
220
 
242
- return;
221
+ if (stream.statusCode !== 200) {
222
+ this.queue.clear();
223
+ const err = await tryGetErrorFromStream(stream);
224
+ let errMsg = "Referenced asset URL \"".concat(url, "\" returned HTTP ").concat(stream.statusCode);
225
+
226
+ if (err) {
227
+ errMsg = "".concat(errMsg, ":\n\n").concat(err);
243
228
  }
244
229
 
245
- if (!_this2.assetDirsCreated) {
246
- /* eslint-disable no-sync */
247
- fse.ensureDirSync(path.join(_this2.tmpDir, 'files'));
248
- fse.ensureDirSync(path.join(_this2.tmpDir, 'images'));
249
- /* eslint-enable no-sync */
230
+ this.reject(new Error(errMsg));
231
+ return false;
232
+ }
250
233
 
251
- _this2.assetDirsCreated = true;
252
- }
234
+ this.maybeCreateAssetDirs();
235
+ debug('Asset stream ready, writing to filesystem at %s', dstPath);
236
+ const tmpPath = path.join(this.tmpDir, dstPath);
237
+ const {
238
+ sha1,
239
+ md5,
240
+ size
241
+ } = await writeHashedStream(tmpPath, stream); // Verify it against our downloaded stream to make sure we have the same copy
242
+
243
+ const contentLength = stream.headers['content-length'];
244
+ const remoteSha1 = stream.headers['x-sanity-sha1'];
245
+ const remoteMd5 = stream.headers['x-sanity-md5'];
246
+ const hasHash = Boolean(remoteSha1 || remoteMd5);
247
+ const method = md5 ? 'md5' : 'sha1';
248
+ let differs = false;
249
+
250
+ if (remoteMd5 && md5) {
251
+ differs = remoteMd5 !== md5;
252
+ } else if (remoteSha1 && sha1) {
253
+ differs = remoteSha1 !== sha1;
254
+ }
253
255
 
254
- debug('Asset stream ready, writing to filesystem at %s', dstPath);
255
- const hash = yield writeHashedStream(path.join(_this2.tmpDir, dstPath), stream);
256
- const type = assetDoc._type === 'sanity.imageAsset' ? 'image' : 'file';
257
- const id = `${type}-${hash}`;
258
- const metaProps = omit(assetDoc, EXCLUDE_PROPS);
256
+ if (differs && attemptNum < 3) {
257
+ debug('%s does not match downloaded asset, retrying (#%d) [%s]', method, attemptNum + 1, url);
258
+ return this.downloadAsset(assetDoc, dstPath, attemptNum + 1);
259
+ } else if (differs) {
260
+ const details = [hasHash && (method === 'md5' ? "md5 should be ".concat(remoteMd5, ", got ").concat(md5) : "sha1 should be ".concat(remoteSha1, ", got ").concat(sha1)), contentLength && parseInt(contentLength, 10) !== size && "Asset should be ".concat(contentLength, " bytes, got ").concat(size), "Did not succeed after ".concat(attemptNum, " attempts.")];
261
+ const detailsString = "Details:\n - ".concat(details.filter(Boolean).join('\n - '));
262
+ await fse.unlink(tmpPath);
263
+ this.queue.clear();
264
+ const error = new Error("Failed to download asset at ".concat(assetDoc.url, ", giving up. ").concat(detailsString));
265
+ this.reject(error);
266
+ return false;
267
+ }
259
268
 
260
- if (Object.keys(metaProps).length > 0) {
261
- _this2.assetMap[id] = metaProps;
262
- }
269
+ const isImage = assetDoc._type === 'sanity.imageAsset';
270
+ const type = isImage ? 'image' : 'file';
271
+ const id = "".concat(type, "-").concat(sha1);
272
+ const metaProps = omit(assetDoc, EXCLUDE_PROPS);
263
273
 
264
- _this2.filesWritten++;
265
- })();
266
- } // eslint-disable-next-line complexity
274
+ if (Object.keys(metaProps).length > 0) {
275
+ this.assetMap[id] = metaProps;
276
+ }
267
277
 
278
+ this.downloading.splice(this.downloading.findIndex(datUrl => datUrl === url), 1);
279
+ this.filesWritten++;
280
+ return true;
281
+ }
268
282
 
269
283
  }
270
284
 
271
285
  function isAssetField(item) {
272
- return item.asset && item.asset._ref;
286
+ return item.asset && item.asset._ref && isSanityAsset(item.asset._ref);
273
287
  }
274
288
 
275
289
  function getAssetType(item) {
@@ -277,36 +291,57 @@ function getAssetType(item) {
277
291
  return null;
278
292
  }
279
293
 
280
- const _ref5 = item.asset._ref.match(/^(image|file)-/) || [],
281
- _ref6 = _slicedToArray(_ref5, 2),
282
- type = _ref6[1];
283
-
294
+ const [, type] = item.asset._ref.match(/^(image|file)-/) || [];
284
295
  return type || null;
285
296
  }
286
297
 
287
- function isModernAsset(assetId) {
288
- return /^(image|file)/.test(assetId);
298
+ function isSanityAsset(assetId) {
299
+ return /^image-[a-f0-9]{40}-\d+x\d+-[a-z]+$/.test(assetId) || /^file-[a-f0-9]{40}-[a-z0-9]+$/.test(assetId);
289
300
  }
290
301
 
291
302
  function generateFilename(assetId) {
292
- const _ref7 = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/) || [],
293
- _ref8 = _slicedToArray(_ref7, 4),
294
- asset = _ref8[2],
295
- ext = _ref8[3];
296
-
303
+ const [,, asset, ext] = assetId.match(/^(image|file)-(.*?)(-[a-z]+)?$/) || [];
297
304
  const extension = (ext || 'bin').replace(/^-/, '');
298
- return asset ? `${asset}.${extension}` : `${assetId}.bin`;
305
+ return asset ? "".concat(asset, ".").concat(extension) : "".concat(assetId, ".bin");
299
306
  }
300
307
 
301
308
  function writeHashedStream(filePath, stream) {
302
- const hash = crypto.createHash('sha1');
309
+ let size = 0;
310
+ const md5 = crypto.createHash('md5');
311
+ const sha1 = crypto.createHash('sha1');
303
312
  const hasher = miss.through((chunk, enc, cb) => {
304
- hash.update(chunk);
313
+ size += chunk.length;
314
+ md5.update(chunk);
315
+ sha1.update(chunk);
305
316
  cb(null, chunk);
306
317
  });
307
318
  return new Promise((resolve, reject) => miss.pipe(stream, hasher, fse.createWriteStream(filePath), err => {
308
- return err ? reject(err) : resolve(hash.digest('hex'));
319
+ if (err) {
320
+ reject(err);
321
+ return;
322
+ }
323
+
324
+ resolve({
325
+ size,
326
+ sha1: sha1.digest('hex'),
327
+ md5: md5.digest('hex')
328
+ });
309
329
  }));
310
330
  }
311
331
 
332
+ function tryGetErrorFromStream(stream) {
333
+ return new Promise((resolve, reject) => {
334
+ miss.pipe(stream, miss.concat(parse), err => err ? reject(err) : noop);
335
+
336
+ function parse(body) {
337
+ try {
338
+ const parsed = JSON.parse(body.toString('utf8'));
339
+ resolve(parsed.message || parsed.error || null);
340
+ } catch (err) {
341
+ resolve(body.toString('utf8').slice(0, 16000));
342
+ }
343
+ }
344
+ });
345
+ }
346
+
312
347
  module.exports = AssetHandler;