@sanity/export 3.26.0 → 3.26.2-canary.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/export",
3
- "version": "3.26.0",
3
+ "version": "3.26.2-canary.47+dc645319c7",
4
4
  "description": "Export Sanity documents and assets",
5
5
  "keywords": [
6
6
  "sanity",
@@ -31,6 +31,7 @@
31
31
  "test": "jest"
32
32
  },
33
33
  "dependencies": {
34
+ "@sanity/util": "3.26.2-canary.47+dc645319c7",
34
35
  "archiver": "^5.0.0",
35
36
  "debug": "^3.2.7",
36
37
  "get-it": "^8.4.4",
@@ -49,5 +50,5 @@
49
50
  "publishConfig": {
50
51
  "access": "public"
51
52
  },
52
- "gitHead": "c9bbd2d8971b67ebc63f0e89b7930e65829a9f67"
53
+ "gitHead": "dc645319c7d109039871bc4b3385a0df47a10d55"
53
54
  }
@@ -15,6 +15,24 @@ const ACTION_REMOVE = 'remove'
15
15
  const ACTION_REWRITE = 'rewrite'
16
16
  const ASSET_DOWNLOAD_CONCURRENCY = 8
17
17
 
18
+ const retryHelper = (times, fn, onError) => {
19
+ let attempt = 0
20
+ const caller = (...args) => {
21
+ return fn(...args).catch((err) => {
22
+ if (onError) {
23
+ onError(err, attempt)
24
+ }
25
+ if (attempt < times) {
26
+ attempt++
27
+ return caller(...args)
28
+ }
29
+
30
+ throw err
31
+ })
32
+ }
33
+ return caller
34
+ }
35
+
18
36
  class AssetHandler {
19
37
  constructor(options) {
20
38
  const concurrency = options.concurrency || ASSET_DOWNLOAD_CONCURRENCY
@@ -104,7 +122,27 @@ class AssetHandler {
104
122
  debug('Adding download task for %s (destination: %s)', assetDoc._id, dstPath)
105
123
  this.queueSize++
106
124
  this.downloading.push(assetDoc.url)
107
- this.queue.add(() => this.downloadAsset(assetDoc, dstPath))
125
+
126
+ const doDownload = retryHelper(
127
+ 10, // try 10 times
128
+ () => this.downloadAsset(assetDoc, dstPath),
129
+ (err, attempt) => {
130
+ debug(
131
+ `Error downloading asset %s (destination: %s), attempt %d`,
132
+ assetDoc._id,
133
+ dstPath,
134
+ attempt,
135
+ err,
136
+ )
137
+ },
138
+ )
139
+ this.queue.add(() =>
140
+ doDownload().catch((err) => {
141
+ debug('Error downloading asset', err)
142
+ this.queue.clear()
143
+ this.reject(err)
144
+ }),
145
+ )
108
146
  }
109
147
 
110
148
  maybeCreateAssetDirs() {
@@ -133,7 +171,8 @@ class AssetHandler {
133
171
  return {url: formatUrl(url), headers}
134
172
  }
135
173
 
136
- async downloadAsset(assetDoc, dstPath, attemptNum = 0) {
174
+ // eslint-disable-next-line max-statements
175
+ async downloadAsset(assetDoc, dstPath) {
137
176
  const {url} = assetDoc
138
177
  const options = this.getAssetRequestOptions(assetDoc)
139
178
 
@@ -141,27 +180,39 @@ class AssetHandler {
141
180
  try {
142
181
  stream = await requestStream(options)
143
182
  } catch (err) {
144
- this.reject(err)
145
- return false
183
+ throw new Error('Failed create asset stream', {cause: err})
146
184
  }
147
185
 
148
186
  if (stream.statusCode !== 200) {
149
187
  this.queue.clear()
150
- const err = await tryGetErrorFromStream(stream)
151
- let errMsg = `Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`
152
- if (err) {
153
- errMsg = `${errMsg}:\n\n${err}`
154
- }
188
+ try {
189
+ const err = await tryGetErrorFromStream(stream)
190
+ let errMsg = `Referenced asset URL "${url}" returned HTTP ${stream.statusCode}`
191
+ if (err) {
192
+ errMsg = `${errMsg}:\n\n${err}`
193
+ }
155
194
 
156
- this.reject(new Error(errMsg))
157
- return false
195
+ throw new Error(errMsg)
196
+ } catch (err) {
197
+ throw new Error('Failed to parse error response from asset stream', {cause: err})
198
+ }
158
199
  }
159
200
 
160
201
  this.maybeCreateAssetDirs()
161
202
 
162
203
  debug('Asset stream ready, writing to filesystem at %s', dstPath)
163
204
  const tmpPath = path.join(this.tmpDir, dstPath)
164
- const {sha1, md5, size} = await writeHashedStream(tmpPath, stream)
205
+ let sha1 = ''
206
+ let md5 = ''
207
+ let size = 0
208
+ try {
209
+ const res = await writeHashedStream(tmpPath, stream)
210
+ sha1 = res.sha1
211
+ md5 = res.md5
212
+ size = res.size
213
+ } catch (err) {
214
+ throw new Error('Failed to write asset stream to filesystem', {cause: err})
215
+ }
165
216
 
166
217
  // Verify it against our downloaded stream to make sure we have the same copy
167
218
  const contentLength = stream.headers['content-length']
@@ -177,10 +228,7 @@ class AssetHandler {
177
228
  const md5Differs = remoteMd5 && md5 !== remoteMd5
178
229
  const differs = sha1Differs && md5Differs
179
230
 
180
- if (differs && attemptNum < 3) {
181
- debug('%s does not match downloaded asset, retrying (#%d) [%s]', method, attemptNum + 1, url)
182
- return this.downloadAsset(assetDoc, dstPath, attemptNum + 1)
183
- } else if (differs) {
231
+ if (differs) {
184
232
  const details = [
185
233
  hasHash &&
186
234
  (method === 'md5'
@@ -197,14 +245,8 @@ class AssetHandler {
197
245
  const detailsString = `Details:\n - ${details.filter(Boolean).join('\n - ')}`
198
246
 
199
247
  await rimraf(tmpPath)
200
- this.queue.clear()
201
-
202
- const error = new Error(
203
- `Failed to download asset at ${assetDoc.url}, giving up. ${detailsString}`,
204
- )
205
248
 
206
- this.reject(error)
207
- return false
249
+ throw new Error(`Failed to download asset at ${assetDoc.url}. ${detailsString}`)
208
250
  }
209
251
 
210
252
  const isImage = assetDoc._type === 'sanity.imageAsset'
@@ -1,21 +1,13 @@
1
- module.exports = (line) => {
2
- try {
3
- return JSON.parse(line)
4
- } catch (err) {
5
- // Catch half-done lines with an error at the end
6
- const errorPosition = line.lastIndexOf('{"error":')
7
- if (errorPosition === -1) {
8
- err.message = `${err.message} (${line})`
9
- throw err
10
- }
1
+ const {createSafeJsonParser} = require('@sanity/util/createSafeJsonParser')
11
2
 
12
- const errorJson = line.slice(errorPosition)
13
- const errorLine = JSON.parse(errorJson)
14
- const error = errorLine && errorLine.error
15
- if (error && error.description) {
16
- throw new Error(`Error streaming dataset: ${error.description}\n\n${errorJson}\n`)
17
- }
18
-
19
- throw err
20
- }
21
- }
3
+ /**
4
+ * Safe JSON parser that is able to handle lines interrupted by an error object.
5
+ *
6
+ * This may occur when streaming NDJSON from the Export HTTP API.
7
+ *
8
+ * @internal
9
+ * @see {@link https://github.com/sanity-io/sanity/pull/1787 | Initial pull request}
10
+ */
11
+ module.exports = createSafeJsonParser({
12
+ errorLabel: 'Error streaming dataset',
13
+ })