@netlify/spark-ui 1.27.5 → 1.27.7

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.
@@ -1,6 +1,6 @@
1
1
  import { ComponentChildren } from 'preact';
2
2
  import { DropClient } from './drop-core';
3
- import { DeployedSite, TrackFn } from './useDropDeploy';
3
+ import { DeployedSite, DeployError, TrackFn } from './useDropDeploy';
4
4
  interface Props {
5
5
  /** Content the drop layer sits on top of. The zone is invisible until you drag over it. */
6
6
  children?: ComponentChildren;
@@ -12,11 +12,18 @@ interface Props {
12
12
  claimUrl?: (siteName: string) => string;
13
13
  /** Called once the deploy is live. */
14
14
  onDeploy?: (result: DeployedSite) => void;
15
+ /**
16
+ * Called when a deploy fails, with a humanized `message` (ready to show a
17
+ * user — the same copy the built-in overlay uses) and the raw `reason`. Pair
18
+ * this with `showStatus={false}` to render your own error UI.
19
+ */
20
+ onError?: (error: DeployError) => void;
15
21
  /**
16
22
  * Render the built-in status overlay (drag tint, spinner, progress bar, error).
17
23
  * Set to `false` to suppress it entirely and keep your children visible, then
18
24
  * drive your own feedback from the `data-state` attribute on the root element.
19
- * Note: in this mode you're responsible for surfacing errors yourself.
25
+ * Note: in this mode you're responsible for surfacing errors yourself — use
26
+ * `onError` to get the message.
20
27
  */
21
28
  showStatus?: boolean;
22
29
  /**
@@ -38,5 +45,5 @@ interface Props {
38
45
  analyticsId?: string;
39
46
  className?: string;
40
47
  }
41
- export default function DropZone({ children, apiBase, client, claimUrl, onDeploy, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
48
+ export default function DropZone({ children, apiBase, client, claimUrl, onDeploy, onError, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
42
49
  export {};
@@ -1,52 +1,54 @@
1
1
  import { u as e } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
2
- import { A as k, d as S, y as $ } from "../../../chunks/hooks.module.DRcxknIz.js";
3
- import { readFileList as E, readDataTransfer as I } from "./drop-core/readFiles.js";
4
- import { useDropDeploy as P } from "./useDropDeploy.js";
2
+ import { A as k, d as $, y as E } from "../../../chunks/hooks.module.DRcxknIz.js";
3
+ import { readFileList as I, readDataTransfer as P } from "./drop-core/readFiles.js";
4
+ import { useDropDeploy as x } from "./useDropDeploy.js";
5
5
  import { Fragment as D } from "preact";
6
- import '../../../assets/DropZone.css';const x = 120, F = (n) => `https://app.netlify.com/drop/${n}`;
7
- function K({
6
+ import '../../../assets/DropZone.css';const F = 120, M = (n) => `https://app.netlify.com/drop/${n}`;
7
+ function L({
8
8
  children: n,
9
9
  apiBase: l,
10
10
  client: i,
11
- claimUrl: s = F,
11
+ claimUrl: s = M,
12
12
  onDeploy: u,
13
+ onError: w,
13
14
  showStatus: f = !0,
14
15
  onTrack: d,
15
16
  analyticsId: g,
16
- className: w
17
+ className: A
17
18
  }) {
18
- const v = (r, t = {}) => d == null ? void 0 : d(r, g ? { dropzone_id: g, ...t } : t), { status: a, warning: h, busy: o, deploy: b, reset: A } = P({
19
+ const v = (r, t = {}) => d == null ? void 0 : d(r, g ? { dropzone_id: g, ...t } : t), { status: a, warning: h, busy: o, deploy: b, reset: C } = x({
19
20
  apiBase: l,
20
21
  client: i,
21
22
  claimUrl: s,
22
23
  onDeploy: u,
24
+ onError: w,
23
25
  track: v
24
- }), { active: p, keepActive: C, endDrag: T } = M(), m = k(null), y = (r) => {
26
+ }), { active: p, keepActive: T, endDrag: _ } = O(), m = k(null), y = (r) => {
25
27
  var t;
26
28
  v("dropzone_browse_opened", { method: r }), (t = m.current) == null || t.click();
27
- }, _ = f && (p || o || a.kind === "error"), R = [
29
+ }, R = f && (p || o || a.kind === "error"), S = [
28
30
  "n-dropzone",
29
31
  p && "is-active",
30
32
  o && "is-busy",
31
33
  !f && "is-bare",
32
- w
34
+ A
33
35
  ].filter(Boolean).join(" ");
34
36
  return /* @__PURE__ */ e(
35
37
  "div",
36
38
  {
37
- class: R,
39
+ class: S,
38
40
  "data-state": a.kind,
39
41
  role: "button",
40
42
  tabIndex: o ? -1 : 0,
41
43
  "aria-label": "Drop a site folder or zip here to deploy it, or press Enter to browse for files",
42
44
  "aria-busy": o,
43
45
  onDragOver: (r) => {
44
- o || (r.preventDefault(), C());
46
+ o || (r.preventDefault(), T());
45
47
  },
46
48
  onDrop: (r) => {
47
- if (r.preventDefault(), T(), o) return;
49
+ if (r.preventDefault(), _(), o) return;
48
50
  const t = r.dataTransfer;
49
- t && b(() => I(t), "drag");
51
+ t && b(() => P(t), "drag");
50
52
  },
51
53
  onClick: (r) => {
52
54
  !o && r.target === r.currentTarget && y("click");
@@ -68,16 +70,16 @@ function K({
68
70
  onChange: (r) => {
69
71
  var z;
70
72
  const t = r.currentTarget;
71
- (z = t.files) != null && z.length && b(() => E(t.files), "browse"), t.value = "";
73
+ (z = t.files) != null && z.length && b(() => I(t.files), "browse"), t.value = "";
72
74
  }
73
75
  }
74
76
  ),
75
- _ && /* @__PURE__ */ e(
77
+ R && /* @__PURE__ */ e(
76
78
  "div",
77
79
  {
78
80
  class: "n-dropzone-overlay n-fadeIn",
79
81
  children: [
80
- /* @__PURE__ */ e(O, { status: a, active: p, onReset: A }),
82
+ /* @__PURE__ */ e(U, { status: a, active: p, onReset: C }),
81
83
  h && a.kind !== "error" && o && /* @__PURE__ */ e("p", { class: "n-dropzone-warning", role: "status", children: h })
82
84
  ]
83
85
  },
@@ -87,15 +89,15 @@ function K({
87
89
  }
88
90
  );
89
91
  }
90
- function M() {
91
- const [n, l] = S(!1), i = k();
92
- return $(() => () => clearTimeout(i.current), []), { active: n, keepActive: () => {
93
- l(!0), clearTimeout(i.current), i.current = setTimeout(() => l(!1), x);
92
+ function O() {
93
+ const [n, l] = $(!1), i = k();
94
+ return E(() => () => clearTimeout(i.current), []), { active: n, keepActive: () => {
95
+ l(!0), clearTimeout(i.current), i.current = setTimeout(() => l(!1), F);
94
96
  }, endDrag: () => {
95
97
  clearTimeout(i.current), l(!1);
96
98
  } };
97
99
  }
98
- function O({
100
+ function U({
99
101
  status: n,
100
102
  active: l,
101
103
  onReset: i
@@ -138,5 +140,5 @@ function c({ label: n }) {
138
140
  ] });
139
141
  }
140
142
  export {
141
- K as default
143
+ L as default
142
144
  };
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Thrown when the user drops or picks a single loose file that Drop can't turn
3
+ * into a site on its own — anything that isn't a `.zip` (a site archive) or an
4
+ * HTML page (served as index.html). A folder, multiple files, a zip, or a lone
5
+ * HTML file are all fine; any other single file is rejected rather than
6
+ * deployed as a one-asset site (matches react-ui).
7
+ */
8
+ export declare class UnsupportedDropError extends Error {
9
+ constructor();
10
+ }
1
11
  /**
2
12
  * Turn a raw deploy failure into copy a non-technical user can act on.
3
13
  * The original message is still tracked via analytics — this is only for display.
@@ -1,5 +1,12 @@
1
+ class n extends Error {
2
+ constructor() {
3
+ super("unsupported single file"), this.name = "UnsupportedDropError";
4
+ }
5
+ }
1
6
  function a(e) {
2
7
  if (!(e instanceof Error)) return "Something went wrong. Please try again.";
8
+ if (e instanceof n)
9
+ return "To deploy, drop a folder or a .zip file — or a single HTML page.";
3
10
  if (e.message === "Deploy did not become ready in time")
4
11
  return "Your site is taking longer than expected to go live. Please try again in a moment.";
5
12
  if (e instanceof TypeError)
@@ -25,5 +32,6 @@ function a(e) {
25
32
  }
26
33
  }
27
34
  export {
35
+ n as UnsupportedDropError,
28
36
  a as humanizeDropError
29
37
  };
@@ -1,16 +1,17 @@
1
- import { getDigest as r } from "./digest.js";
2
- import { getSingleNonIndexHtmlFileName as n, hasMultipleHtmlFilesWithoutIndex as l, readDataTransfer as i, readFileList as m, renameSingleNonIndexHtmlToIndex as a } from "./readFiles.js";
1
+ import { getDigest as o } from "./digest.js";
2
+ import { getSingleNonIndexHtmlFileName as n, hasMultipleHtmlFilesWithoutIndex as l, readDataTransfer as i, readFileList as m, renameSingleNonIndexHtmlToIndex as p } from "./readFiles.js";
3
3
  import { AnonymousDropClient as x } from "./client.js";
4
4
  import { deployFiles as s } from "./deploy.js";
5
- import { humanizeDropError as g } from "./errors.js";
5
+ import { UnsupportedDropError as g, humanizeDropError as u } from "./errors.js";
6
6
  export {
7
7
  x as AnonymousDropClient,
8
+ g as UnsupportedDropError,
8
9
  s as deployFiles,
9
- r as getDigest,
10
+ o as getDigest,
10
11
  n as getSingleNonIndexHtmlFileName,
11
12
  l as hasMultipleHtmlFilesWithoutIndex,
12
- g as humanizeDropError,
13
+ u as humanizeDropError,
13
14
  i as readDataTransfer,
14
15
  m as readFileList,
15
- a as renameSingleNonIndexHtmlToIndex
16
+ p as renameSingleNonIndexHtmlToIndex
16
17
  };
@@ -1,18 +1,19 @@
1
- import { g as I } from "../../../../chunks/_commonjsHelpers.C6fGbg64.js";
2
- var L = { exports: {} }, F = L.exports, M;
3
- function z() {
4
- return M || (M = 1, function(g, _) {
5
- (function(m, h) {
6
- g.exports = h();
7
- })(typeof self < "u" ? self : F, function() {
1
+ import { g as P } from "../../../../chunks/_commonjsHelpers.C6fGbg64.js";
2
+ import { UnsupportedDropError as U } from "./errors.js";
3
+ var L = { exports: {} }, G = L.exports, D;
4
+ function J() {
5
+ return D || (D = 1, function(y, m) {
6
+ (function(_, h) {
7
+ y.exports = h();
8
+ })(typeof self < "u" ? self : G, function() {
8
9
  return (
9
10
  /******/
10
- function(y) {
11
- var m = {};
11
+ function(w) {
12
+ var _ = {};
12
13
  function h(v) {
13
- if (m[v])
14
- return m[v].exports;
15
- var s = m[v] = {
14
+ if (_[v])
15
+ return _[v].exports;
16
+ var s = _[v] = {
16
17
  /******/
17
18
  i: v,
18
19
  /******/
@@ -21,16 +22,16 @@ function z() {
21
22
  exports: {}
22
23
  /******/
23
24
  };
24
- return y[v].call(s.exports, s, s.exports, h), s.l = !0, s.exports;
25
+ return w[v].call(s.exports, s, s.exports, h), s.l = !0, s.exports;
25
26
  }
26
- return h.m = y, h.c = m, h.d = function(v, s, w) {
27
+ return h.m = w, h.c = _, h.d = function(v, s, g) {
27
28
  h.o(v, s) || Object.defineProperty(v, s, {
28
29
  /******/
29
30
  configurable: !1,
30
31
  /******/
31
32
  enumerable: !0,
32
33
  /******/
33
- get: w
34
+ get: g
34
35
  /******/
35
36
  });
36
37
  }, h.n = function(v) {
@@ -52,12 +53,12 @@ function z() {
52
53
  }([
53
54
  /* 0 */
54
55
  /***/
55
- function(y, m, h) {
56
+ function(w, _, h) {
56
57
  function v(e, o) {
57
58
  if (!(e instanceof o))
58
59
  throw new TypeError("Cannot call a class as a function");
59
60
  }
60
- var s = h(5), w = h(1), u = w.toHex, f = w.ceilHeapSize, l = h(6), d = function(e) {
61
+ var s = h(5), g = h(1), u = g.toHex, f = g.ceilHeapSize, l = h(6), d = function(e) {
61
62
  for (e += 9; e % 64 > 0; e += 1)
62
63
  ;
63
64
  return e;
@@ -148,43 +149,43 @@ function z() {
148
149
  return u(this.rawEnd().buffer);
149
150
  }, e;
150
151
  }();
151
- y.exports = c, y.exports._core = s;
152
+ w.exports = c, w.exports._core = s;
152
153
  },
153
154
  /* 1 */
154
155
  /***/
155
- function(y, m) {
156
+ function(w, _) {
156
157
  for (var h = new Array(256), v = 0; v < 256; v++)
157
158
  h[v] = (v < 16 ? "0" : "") + v.toString(16);
158
- y.exports.toHex = function(s) {
159
- for (var w = new Uint8Array(s), u = new Array(s.byteLength), f = 0; f < u.length; f++)
160
- u[f] = h[w[f]];
159
+ w.exports.toHex = function(s) {
160
+ for (var g = new Uint8Array(s), u = new Array(s.byteLength), f = 0; f < u.length; f++)
161
+ u[f] = h[g[f]];
161
162
  return u.join("");
162
- }, y.exports.ceilHeapSize = function(s) {
163
- var w = 0;
163
+ }, w.exports.ceilHeapSize = function(s) {
164
+ var g = 0;
164
165
  if (s <= 65536) return 65536;
165
166
  if (s < 16777216)
166
- for (w = 1; w < s; w = w << 1)
167
+ for (g = 1; g < s; g = g << 1)
167
168
  ;
168
169
  else
169
- for (w = 16777216; w < s; w += 16777216)
170
+ for (g = 16777216; g < s; g += 16777216)
170
171
  ;
171
- return w;
172
- }, y.exports.isDedicatedWorkerScope = function(s) {
173
- var w = "WorkerGlobalScope" in s && s instanceof s.WorkerGlobalScope, u = "SharedWorkerGlobalScope" in s && s instanceof s.SharedWorkerGlobalScope, f = "ServiceWorkerGlobalScope" in s && s instanceof s.ServiceWorkerGlobalScope;
174
- return w && !u && !f;
172
+ return g;
173
+ }, w.exports.isDedicatedWorkerScope = function(s) {
174
+ var g = "WorkerGlobalScope" in s && s instanceof s.WorkerGlobalScope, u = "SharedWorkerGlobalScope" in s && s instanceof s.SharedWorkerGlobalScope, f = "ServiceWorkerGlobalScope" in s && s instanceof s.ServiceWorkerGlobalScope;
175
+ return g && !u && !f;
175
176
  };
176
177
  },
177
178
  /* 2 */
178
179
  /***/
179
- function(y, m, h) {
180
- y.exports = function() {
180
+ function(w, _, h) {
181
+ w.exports = function() {
181
182
  var v = h(0), s = function(f, l, d) {
182
183
  try {
183
184
  return d(null, f.digest(l));
184
185
  } catch (i) {
185
186
  return d(i);
186
187
  }
187
- }, w = function(f, l, d, i, r) {
188
+ }, g = function(f, l, d, i, r) {
188
189
  var t = new self.FileReader();
189
190
  t.onloadend = function() {
190
191
  if (t.error)
@@ -197,7 +198,7 @@ function z() {
197
198
  r(o);
198
199
  return;
199
200
  }
200
- l < i.size ? w(f, l, d, i, r) : r(null, f.end());
201
+ l < i.size ? g(f, l, d, i, r) : r(null, f.end());
201
202
  }, t.readAsArrayBuffer(i.slice(l, l + d));
202
203
  }, u = !0;
203
204
  return self.onmessage = function(f) {
@@ -209,7 +210,7 @@ function z() {
209
210
  var c = function(e, o) {
210
211
  e ? self.postMessage({ id: i, error: e.name }) : self.postMessage({ id: i, hash: o });
211
212
  };
212
- l && s(t, l, c), d && w(t, 0, r, d, c);
213
+ l && s(t, l, c), d && g(t, 0, r, d, c);
213
214
  }
214
215
  }
215
216
  }, function() {
@@ -219,8 +220,8 @@ function z() {
219
220
  },
220
221
  /* 3 */
221
222
  /***/
222
- function(y, m, h) {
223
- var v = h(4), s = h(0), w = h(7), u = h(2), f = h(1), l = f.isDedicatedWorkerScope, d = typeof self < "u" && l(self);
223
+ function(w, _, h) {
224
+ var v = h(4), s = h(0), g = h(7), u = h(2), f = h(1), l = f.isDedicatedWorkerScope, d = typeof self < "u" && l(self);
224
225
  s.disableWorkerBehaviour = d ? u() : function() {
225
226
  }, s.createWorker = function() {
226
227
  var i = v(
@@ -230,11 +231,11 @@ function z() {
230
231
  return i.terminate = function() {
231
232
  URL.revokeObjectURL(i.objectURL), r.call(i);
232
233
  }, i;
233
- }, s.createHash = w, y.exports = s;
234
+ }, s.createHash = g, w.exports = s;
234
235
  },
235
236
  /* 4 */
236
237
  /***/
237
- function(y, m, h) {
238
+ function(w, _, h) {
238
239
  function v(i) {
239
240
  var r = {};
240
241
  function t(e) {
@@ -286,7 +287,7 @@ function z() {
286
287
  var c = t(t.s = ENTRY_MODULE);
287
288
  return c.default || c;
288
289
  }
289
- var s = "[\\.|\\-|\\+|\\w|/|@]+", w = "\\((/\\*.*?\\*/)?s?.*?(" + s + ").*?\\)";
290
+ var s = "[\\.|\\-|\\+|\\w|/|@]+", g = "\\((/\\*.*?\\*/)?s?.*?(" + s + ").*?\\)";
290
291
  function u(i) {
291
292
  return (i + "").replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&");
292
293
  }
@@ -295,9 +296,9 @@ function z() {
295
296
  c[t] = [];
296
297
  var e = r.toString(), o = e.match(/^function\s?\(\w+,\s*\w+,\s*(\w+)\)/);
297
298
  if (!o) return c;
298
- for (var n = o[1], p = new RegExp("(\\\\n|\\W)" + u(n) + w, "g"), a; a = p.exec(e); )
299
+ for (var n = o[1], p = new RegExp("(\\\\n|\\W)" + u(n) + g, "g"), a; a = p.exec(e); )
299
300
  a[3] !== "dll-reference" && c[t].push(a[3]);
300
- for (p = new RegExp("\\(" + u(n) + '\\("(dll-reference\\s(' + s + '))"\\)\\)' + w, "g"); a = p.exec(e); )
301
+ for (p = new RegExp("\\(" + u(n) + '\\("(dll-reference\\s(' + s + '))"\\)\\)' + g, "g"); a = p.exec(e); )
301
302
  i[a[2]] || (c[t].push(a[1]), i[a[2]] = h(a[1]).m), c[a[2]] = c[a[2]] || [], c[a[2]].push(a[4]);
302
303
  return c;
303
304
  }
@@ -325,7 +326,7 @@ function z() {
325
326
  }
326
327
  return c;
327
328
  }
328
- y.exports = function(i, r) {
329
+ w.exports = function(i, r) {
329
330
  r = r || {};
330
331
  var t = {
331
332
  main: h.m
@@ -351,9 +352,9 @@ function z() {
351
352
  },
352
353
  /* 5 */
353
354
  /***/
354
- function(y, m) {
355
- y.exports = function(v, s, w) {
356
- var u = new v.Int32Array(w);
355
+ function(w, _) {
356
+ w.exports = function(v, s, g) {
357
+ var u = new v.Int32Array(g);
357
358
  function f(l, d) {
358
359
  l = l | 0, d = d | 0;
359
360
  var i = 0, r = 0, t = 0, c = 0, e = 0, o = 0, n = 0, p = 0, a = 0, x = 0, b = 0, C = 0, S = 0, A = 0;
@@ -377,7 +378,7 @@ function z() {
377
378
  },
378
379
  /* 6 */
379
380
  /***/
380
- function(y, m) {
381
+ function(w, _) {
381
382
  var h = this, v = void 0;
382
383
  typeof self < "u" && typeof self.FileReaderSync < "u" && (v = new self.FileReaderSync());
383
384
  var s = function(f, l, d, i, r, t) {
@@ -404,7 +405,7 @@ function z() {
404
405
  l[t + n + 3 | 0] = f.charCodeAt(i + n);
405
406
  }
406
407
  }
407
- }, w = function(f, l, d, i, r, t) {
408
+ }, g = function(f, l, d, i, r, t) {
408
409
  var c = void 0, e = t % 4, o = (r + e) % 4, n = r - o;
409
410
  switch (e) {
410
411
  case 0:
@@ -453,15 +454,15 @@ function z() {
453
454
  }
454
455
  }
455
456
  };
456
- y.exports = function(f, l, d, i, r, t) {
457
+ w.exports = function(f, l, d, i, r, t) {
457
458
  if (typeof f == "string")
458
459
  return s(f, l, d, i, r, t);
459
460
  if (f instanceof Array || h && h.Buffer && h.Buffer.isBuffer(f))
460
- return w(f, l, d, i, r, t);
461
+ return g(f, l, d, i, r, t);
461
462
  if (f instanceof ArrayBuffer)
462
- return w(new Uint8Array(f), l, d, i, r, t);
463
+ return g(new Uint8Array(f), l, d, i, r, t);
463
464
  if (f.buffer instanceof ArrayBuffer)
464
- return w(new Uint8Array(f.buffer, f.byteOffset, f.byteLength), l, d, i, r, t);
465
+ return g(new Uint8Array(f.buffer, f.byteOffset, f.byteLength), l, d, i, r, t);
465
466
  if (f instanceof Blob)
466
467
  return u(f, l, d, i, r, t);
467
468
  throw new Error("Unsupported data type.");
@@ -469,7 +470,7 @@ function z() {
469
470
  },
470
471
  /* 7 */
471
472
  /***/
472
- function(y, m, h) {
473
+ function(w, _, h) {
473
474
  var v = /* @__PURE__ */ function() {
474
475
  function d(i, r) {
475
476
  for (var t = 0; t < r.length; t++) {
@@ -485,9 +486,9 @@ function z() {
485
486
  if (!(d instanceof i))
486
487
  throw new TypeError("Cannot call a class as a function");
487
488
  }
488
- var w = h(0), u = h(1), f = u.toHex, l = function() {
489
+ var g = h(0), u = h(1), f = u.toHex, l = function() {
489
490
  function d() {
490
- s(this, d), this._rusha = new w(), this._rusha.resetState();
491
+ s(this, d), this._rusha = new g(), this._rusha.resetState();
491
492
  }
492
493
  return d.prototype.update = function(r) {
493
494
  return this._rusha.append(r), this;
@@ -508,7 +509,7 @@ function z() {
508
509
  }
509
510
  }]), d;
510
511
  }();
511
- y.exports = function() {
512
+ w.exports = function() {
512
513
  return new l();
513
514
  };
514
515
  }
@@ -518,110 +519,114 @@ function z() {
518
519
  });
519
520
  }(L)), L.exports;
520
521
  }
521
- var P = z();
522
- const G = /* @__PURE__ */ I(P), J = new G(), R = (g) => g.startsWith(".") || g === "__MACOSX" || g === "node_modules", $ = (g) => g.split("/").some((_) => _ && R(_)), k = (g) => g.startsWith("/") ? g : `/${g}`;
523
- function O(g, _, y) {
522
+ var $ = J();
523
+ const N = /* @__PURE__ */ P($), Y = new N(), R = (y) => /\.html?$/i.test(y), B = (y) => /\.zip$/i.test(y), O = (y) => y.startsWith(".") || y === "__MACOSX" || y === "node_modules", Z = (y) => y.split("/").some((m) => m && O(m)), k = (y) => y.startsWith("/") ? y : `/${y}`;
524
+ function E(y, m, w) {
524
525
  return {
525
- path: k(g),
526
- size: _.byteLength,
527
- sha: J.digest(_),
528
- content: _,
529
- name: y
526
+ path: k(y),
527
+ size: m.byteLength,
528
+ sha: Y.digest(m),
529
+ content: m,
530
+ name: w
530
531
  };
531
532
  }
532
- async function D(g) {
533
- const _ = await new Promise((m, h) => g.file(m, h)), y = await _.arrayBuffer();
534
- return O(g.fullPath, y, _.name);
533
+ async function W(y) {
534
+ const m = await new Promise((_, h) => y.file(_, h)), w = await m.arrayBuffer();
535
+ return E(y.fullPath, w, m.name);
535
536
  }
536
- async function B(g) {
537
- const _ = g.createReader(), y = [];
537
+ async function j(y) {
538
+ const m = y.createReader(), w = [];
538
539
  for (; ; ) {
539
- const m = await new Promise((h, v) => _.readEntries(h, v));
540
- if (!m.length) break;
541
- for (const h of m)
542
- R(h.name) || y.push(...h.isDirectory ? await B(h) : [await D(h)]);
540
+ const _ = await new Promise((h, v) => m.readEntries(h, v));
541
+ if (!_.length) break;
542
+ for (const h of _)
543
+ O(h.name) || w.push(...h.isDirectory ? await j(h) : [await W(h)]);
543
544
  }
544
- return y;
545
+ return w;
545
546
  }
546
- async function Z(g) {
547
+ async function K(y) {
547
548
  var h;
548
- const _ = (h = g.files) == null ? void 0 : h[0];
549
- if (g.files.length === 1 && _ && /\.zip$/i.test(_.name))
550
- return U(_);
551
- const m = Array.from(g.items ?? []).map((v) => {
549
+ const m = (h = y.files) == null ? void 0 : h[0];
550
+ if (y.files.length === 1 && m && B(m.name))
551
+ return I(m);
552
+ const _ = Array.from(y.items ?? []).map((v) => {
552
553
  var s;
553
554
  return (s = v.webkitGetAsEntry) == null ? void 0 : s.call(v);
554
555
  }).filter(Boolean);
555
- if (m.length) {
556
+ if (_.length) {
557
+ if (_.length === 1 && !_[0].isDirectory && !R(_[0].name))
558
+ throw new U();
556
559
  const v = await Promise.all(
557
- m.map(
558
- (s) => R(s.name) ? [] : s.isDirectory ? B(s) : D(s).then((w) => [w])
560
+ _.map(
561
+ (s) => O(s.name) ? [] : s.isDirectory ? j(s) : W(s).then((g) => [g])
559
562
  )
560
563
  );
561
- return E(v.flat());
564
+ return M(v.flat());
562
565
  }
563
- return Y(g.files);
566
+ return Q(y.files);
564
567
  }
565
- async function Y(g) {
566
- const _ = Array.from(g);
567
- if (_.length === 1 && /\.zip$/i.test(_[0].name))
568
- return U(_[0]);
569
- const y = [];
570
- for (const m of _) {
571
- const h = m.webkitRelativePath || m.name;
572
- y.push(O(h, await m.arrayBuffer(), m.name));
568
+ async function Q(y) {
569
+ const m = Array.from(y);
570
+ if (m.length === 1 && B(m[0].name))
571
+ return I(m[0]);
572
+ if (m.length === 1 && !R(m[0].name))
573
+ throw new U();
574
+ const w = [];
575
+ for (const _ of m) {
576
+ const h = _.webkitRelativePath || _.name;
577
+ w.push(E(h, await _.arrayBuffer(), _.name));
573
578
  }
574
- return E(y);
579
+ return M(w);
575
580
  }
576
- async function U(g) {
577
- const { unzip: _ } = await import("../../../../chunks/browser.D58rdxmc.js"), y = new Uint8Array(await g.arrayBuffer()), m = await new Promise(
578
- (v, s) => _(y, (w, u) => w ? s(w) : v(u))
581
+ async function I(y) {
582
+ const { unzip: m } = await import("../../../../chunks/browser.D58rdxmc.js"), w = new Uint8Array(await y.arrayBuffer()), _ = await new Promise(
583
+ (v, s) => m(w, (g, u) => g ? s(g) : v(u))
579
584
  ), h = [];
580
- for (const [v, s] of Object.entries(m)) {
581
- if (v.endsWith("/") || $(v)) continue;
582
- const w = s.buffer.slice(
585
+ for (const [v, s] of Object.entries(_)) {
586
+ if (v.endsWith("/") || Z(v)) continue;
587
+ const g = s.buffer.slice(
583
588
  s.byteOffset,
584
589
  s.byteOffset + s.byteLength
585
590
  );
586
- h.push(O(v, w, v.split("/").pop()));
591
+ h.push(E(v, g, v.split("/").pop()));
587
592
  }
588
- return E(h);
593
+ return M(h);
589
594
  }
590
- function E(g) {
591
- if (!g.length) return g;
592
- const _ = (h) => k(h).split("/").slice(1);
593
- let y = _(g[0].path).slice(0, -1);
594
- for (const h of g) {
595
- const v = _(h.path);
595
+ function M(y) {
596
+ if (!y.length) return y;
597
+ const m = (h) => k(h).split("/").slice(1);
598
+ let w = m(y[0].path).slice(0, -1);
599
+ for (const h of y) {
600
+ const v = m(h.path);
596
601
  let s = 0;
597
- for (; s < y.length && s < v.length - 1 && y[s] === v[s]; ) s++;
598
- y = y.slice(0, s);
602
+ for (; s < w.length && s < v.length - 1 && w[s] === v[s]; ) s++;
603
+ w = w.slice(0, s);
599
604
  }
600
- if (!y.length) return g;
601
- const m = "/" + y.join("/");
602
- return g.map((h) => ({ ...h, path: k(h.path.slice(m.length)) }));
605
+ if (!w.length) return y;
606
+ const _ = "/" + w.join("/");
607
+ return y.map((h) => ({ ...h, path: k(h.path.slice(_.length)) }));
603
608
  }
604
- const W = (g) => g.filter((_) => /\.html?$/i.test(_.name)), j = (g) => g.some((_) => /^index\.html?$/i.test(_.name));
605
- function Q(g) {
606
- const _ = W(g);
607
- return j(_) ? null : _.length === 1 ? _[0].name : null;
609
+ const F = (y) => y.filter((m) => R(m.name)), z = (y) => y.some((m) => /^index\.html?$/i.test(m.name));
610
+ function V(y) {
611
+ const m = F(y);
612
+ return z(m) ? null : m.length === 1 ? m[0].name : null;
608
613
  }
609
- function N(g) {
610
- const _ = W(g);
611
- return _.length > 1 && !j(_);
614
+ function q(y) {
615
+ const m = F(y);
616
+ return m.length > 1 && !z(m);
612
617
  }
613
- function X(g) {
614
- const _ = Q(g);
615
- return _ ? g.map((y) => {
616
- if (y.name !== _) return y;
617
- const m = y.path.lastIndexOf("/"), h = m === -1 ? "" : y.path.slice(0, m);
618
- return { ...y, name: "index.html", path: k(`${h}/index.html`) };
619
- }) : g;
618
+ function H(y) {
619
+ const m = V(y);
620
+ return m ? y.map((w) => {
621
+ if (w.name !== m) return w;
622
+ const _ = w.path.lastIndexOf("/"), h = _ === -1 ? "" : w.path.slice(0, _);
623
+ return { ...w, name: "index.html", path: k(`${h}/index.html`) };
624
+ }) : y;
620
625
  }
621
626
  export {
622
- Q as getSingleNonIndexHtmlFileName,
623
- N as hasMultipleHtmlFilesWithoutIndex,
624
- Z as readDataTransfer,
625
- Y as readFileList,
626
- X as renameSingleNonIndexHtmlToIndex
627
+ V as getSingleNonIndexHtmlFileName,
628
+ q as hasMultipleHtmlFilesWithoutIndex,
629
+ K as readDataTransfer,
630
+ Q as readFileList,
631
+ H as renameSingleNonIndexHtmlToIndex
627
632
  };
@@ -8,6 +8,13 @@ export interface DeployedSite {
8
8
  siteName: string;
9
9
  siteId: string;
10
10
  }
11
+ /** A deploy failure, handed to `onError` so consumers can render their own error UI. */
12
+ export interface DeployError {
13
+ /** Human-readable text ready to show a user — the same copy the built-in overlay displays. */
14
+ message: string;
15
+ /** The raw underlying reason (e.g. "drop failed: 429"), useful for logging. */
16
+ reason: string;
17
+ }
11
18
  /** Finite states of a single deploy attempt. */
12
19
  export type DeployStatus = {
13
20
  kind: 'idle';
@@ -34,13 +41,14 @@ interface UseDropDeployOptions {
34
41
  client?: DropClient;
35
42
  claimUrl: (siteName: string) => string;
36
43
  onDeploy?: (site: DeployedSite) => void;
44
+ onError?: (error: DeployError) => void;
37
45
  track: TrackFn;
38
46
  }
39
47
  /**
40
48
  * Owns the deploy state machine and its side effects (upload, local persistence,
41
49
  * analytics, redirect), keeping DropZone a thin view over the returned `status`.
42
50
  */
43
- export declare function useDropDeploy({ apiBase, client, claimUrl, onDeploy, track, }: UseDropDeployOptions): {
51
+ export declare function useDropDeploy({ apiBase, client, claimUrl, onDeploy, onError, track, }: UseDropDeployOptions): {
44
52
  status: DeployStatus;
45
53
  warning: string | null;
46
54
  busy: boolean;
@@ -1,71 +1,72 @@
1
- import { d as p, A as I } from "../../../chunks/hooks.module.DRcxknIz.js";
2
- import { renameSingleNonIndexHtmlToIndex as S, getSingleNonIndexHtmlFileName as b, hasMultipleHtmlFilesWithoutIndex as z } from "./drop-core/readFiles.js";
3
- import { AnonymousDropClient as F } from "./drop-core/client.js";
4
- import { deployFiles as H } from "./drop-core/deploy.js";
5
- import { humanizeDropError as E } from "./drop-core/errors.js";
6
- function W(e) {
7
- switch (e.phase) {
1
+ import { d as f, A as S } from "../../../chunks/hooks.module.DRcxknIz.js";
2
+ import { renameSingleNonIndexHtmlToIndex as b, getSingleNonIndexHtmlFileName as z, hasMultipleHtmlFilesWithoutIndex as F } from "./drop-core/readFiles.js";
3
+ import { AnonymousDropClient as H } from "./drop-core/client.js";
4
+ import { deployFiles as W } from "./drop-core/deploy.js";
5
+ import { humanizeDropError as $ } from "./drop-core/errors.js";
6
+ function v(n) {
7
+ switch (n.phase) {
8
8
  case "creating":
9
9
  return { kind: "creating" };
10
10
  case "uploading":
11
- return { kind: "uploading", uploaded: e.uploaded ?? 0, total: e.total ?? 0 };
11
+ return { kind: "uploading", uploaded: n.uploaded ?? 0, total: n.total ?? 0 };
12
12
  case "processing":
13
13
  case "ready":
14
14
  return { kind: "processing" };
15
15
  }
16
16
  }
17
- function $(e) {
18
- const d = b(e);
19
- return d ? `No index.html found — "${d}" won’t be served at the site root.` : z(e) ? "No index.html found — your site may not have a landing page." : null;
17
+ function A(n) {
18
+ const r = z(n);
19
+ return r ? `No index.html found — "${r}" won’t be served at the site root.` : F(n) ? "No index.html found — your site may not have a landing page." : null;
20
20
  }
21
- function P({
22
- apiBase: e,
23
- client: d,
24
- claimUrl: f,
25
- onDeploy: l,
26
- track: a
21
+ function q({
22
+ apiBase: n,
23
+ client: r,
24
+ claimUrl: g,
25
+ onDeploy: s,
26
+ onError: a,
27
+ track: u
27
28
  }) {
28
- const [u, t] = p({ kind: "idle" }), [g, c] = p(null), h = I(d ?? new F(e)).current, y = u.kind !== "idle" && u.kind !== "error";
29
- async function k(N, s) {
30
- c(null), t({ kind: "reading" });
31
- const m = (n, i, o) => {
32
- t({ kind: "error", message: n }), a("dropzone_deploy_failed", { method: s, reason: i, ...o });
33
- }, x = (n, i) => {
34
- const o = n instanceof Error ? n.message : String(n);
35
- m(E(n), o, i);
29
+ const [c, i] = f({ kind: "idle" }), [h, m] = f(null), y = S(r ?? new H(n)).current, k = c.kind !== "idle" && c.kind !== "error";
30
+ async function _(x, l) {
31
+ m(null), i({ kind: "reading" });
32
+ const p = (e, o, t) => {
33
+ i({ kind: "error", message: e }), u("dropzone_deploy_failed", { method: l, reason: o, ...t }), a == null || a({ message: e, reason: o });
34
+ }, w = (e, o) => {
35
+ const t = e instanceof Error ? e.message : String(e);
36
+ p($(e), t, o);
36
37
  };
37
38
  try {
38
- const n = S(await N());
39
- if (!n.length) {
40
- m("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
39
+ const e = b(await x());
40
+ if (!e.length) {
41
+ p("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
41
42
  return;
42
43
  }
43
- s === "drag" && a("dropzone_files_dropped", { method: s, file_count: n.length }), c($(n));
44
+ l === "drag" && u("dropzone_files_dropped", { method: l, file_count: e.length }), m(A(e));
44
45
  const {
45
- url: i,
46
- token: o,
47
- deploy: r
48
- } = await H(h, n, {
49
- onProgress: (w) => t(W(w))
46
+ url: o,
47
+ token: t,
48
+ deploy: d
49
+ } = await W(y, e, {
50
+ onProgress: (I) => i(v(I))
50
51
  });
51
52
  try {
52
- localStorage.setItem("drop-token", o), localStorage.setItem("dropSiteName", r.subdomain);
53
+ localStorage.setItem("drop-token", t), localStorage.setItem("dropSiteName", d.subdomain);
53
54
  } catch {
54
55
  }
55
- t({ kind: "redirecting" }), a("dropzone_deploy_succeeded", {
56
- method: s,
57
- site_name: r.subdomain,
58
- file_count: n.length
59
- }), l == null || l({ url: i, token: o, siteName: r.subdomain, siteId: r.id }), window.location.assign(`${f(r.subdomain)}#drop_token=${o}`);
60
- } catch (n) {
61
- x(n);
56
+ i({ kind: "redirecting" }), u("dropzone_deploy_succeeded", {
57
+ method: l,
58
+ site_name: d.subdomain,
59
+ file_count: e.length
60
+ }), s == null || s({ url: o, token: t, siteName: d.subdomain, siteId: d.id }), window.location.assign(`${g(d.subdomain)}#drop_token=${t}`);
61
+ } catch (e) {
62
+ w(e);
62
63
  }
63
64
  }
64
- function _() {
65
- c(null), t({ kind: "idle" });
65
+ function N() {
66
+ m(null), i({ kind: "idle" });
66
67
  }
67
- return { status: u, warning: g, busy: y, deploy: k, reset: _ };
68
+ return { status: c, warning: h, busy: k, deploy: _, reset: N };
68
69
  }
69
70
  export {
70
- P as useDropDeploy
71
+ q as useDropDeploy
71
72
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/spark-ui",
3
3
  "description": "Assets, design tokens, components, and utilities",
4
- "version": "1.27.5",
4
+ "version": "1.27.7",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -1,7 +1,13 @@
1
1
  import { useEffect, useRef, useState } from 'preact/hooks';
2
2
  import type { ComponentChildren } from 'preact';
3
3
  import { readDataTransfer, readFileList, type DropClient } from './drop-core';
4
- import { useDropDeploy, type DeployedSite, type DeployStatus, type TrackFn } from './useDropDeploy';
4
+ import {
5
+ useDropDeploy,
6
+ type DeployedSite,
7
+ type DeployError,
8
+ type DeployStatus,
9
+ type TrackFn,
10
+ } from './useDropDeploy';
5
11
 
6
12
  import './DropZone.css';
7
13
 
@@ -21,11 +27,18 @@ interface Props {
21
27
  claimUrl?: (siteName: string) => string;
22
28
  /** Called once the deploy is live. */
23
29
  onDeploy?: (result: DeployedSite) => void;
30
+ /**
31
+ * Called when a deploy fails, with a humanized `message` (ready to show a
32
+ * user — the same copy the built-in overlay uses) and the raw `reason`. Pair
33
+ * this with `showStatus={false}` to render your own error UI.
34
+ */
35
+ onError?: (error: DeployError) => void;
24
36
  /**
25
37
  * Render the built-in status overlay (drag tint, spinner, progress bar, error).
26
38
  * Set to `false` to suppress it entirely and keep your children visible, then
27
39
  * drive your own feedback from the `data-state` attribute on the root element.
28
- * Note: in this mode you're responsible for surfacing errors yourself.
40
+ * Note: in this mode you're responsible for surfacing errors yourself — use
41
+ * `onError` to get the message.
29
42
  */
30
43
  showStatus?: boolean;
31
44
  /**
@@ -54,6 +67,7 @@ export default function DropZone({
54
67
  client,
55
68
  claimUrl = defaultClaimUrl,
56
69
  onDeploy,
70
+ onError,
57
71
  showStatus = true,
58
72
  onTrack,
59
73
  analyticsId,
@@ -69,6 +83,7 @@ export default function DropZone({
69
83
  client,
70
84
  claimUrl,
71
85
  onDeploy,
86
+ onError,
72
87
  track,
73
88
  });
74
89
 
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Thrown when the user drops or picks a single loose file that Drop can't turn
3
+ * into a site on its own — anything that isn't a `.zip` (a site archive) or an
4
+ * HTML page (served as index.html). A folder, multiple files, a zip, or a lone
5
+ * HTML file are all fine; any other single file is rejected rather than
6
+ * deployed as a one-asset site (matches react-ui).
7
+ */
8
+ export class UnsupportedDropError extends Error {
9
+ constructor() {
10
+ super('unsupported single file');
11
+ this.name = 'UnsupportedDropError';
12
+ }
13
+ }
14
+
1
15
  /**
2
16
  * Turn a raw deploy failure into copy a non-technical user can act on.
3
17
  * The original message is still tracked via analytics — this is only for display.
@@ -5,6 +19,10 @@
5
19
  export function humanizeDropError(err: unknown): string {
6
20
  if (!(err instanceof Error)) return 'Something went wrong. Please try again.';
7
21
 
22
+ if (err instanceof UnsupportedDropError) {
23
+ return 'To deploy, drop a folder or a .zip file — or a single HTML page.';
24
+ }
25
+
8
26
  if (err.message === 'Deploy did not become ready in time') {
9
27
  return 'Your site is taking longer than expected to go live. Please try again in a moment.';
10
28
  }
@@ -1,10 +1,15 @@
1
1
  import Rusha from 'rusha';
2
2
  import type { ProcessedFile } from './types';
3
+ import { UnsupportedDropError } from './errors';
3
4
 
4
5
  /* eslint-disable @typescript-eslint/no-explicit-any -- webkit FileSystem entry APIs are untyped */
5
6
 
6
7
  const rusha = new Rusha();
7
8
 
9
+ /** A file we can serve as a page: `.html` / `.htm` (a lone one becomes index.html). */
10
+ const isHtmlName = (name: string) => /\.html?$/i.test(name);
11
+ const isZipName = (name: string) => /\.zip$/i.test(name);
12
+
8
13
  /** A single path segment we never deploy: dotfiles, macOS metadata, deps. */
9
14
  const isJunkName = (name: string) =>
10
15
  name.startsWith('.') || name === '__MACOSX' || name === 'node_modules';
@@ -58,12 +63,18 @@ async function readDirEntry(entry: any): Promise<ProcessedFile[]> {
58
63
  export async function readDataTransfer(dt: DataTransfer): Promise<ProcessedFile[]> {
59
64
  // single zip?
60
65
  const first = dt.files?.[0];
61
- if (dt.files.length === 1 && first && /\.zip$/i.test(first.name)) {
66
+ if (dt.files.length === 1 && first && isZipName(first.name)) {
62
67
  return readZip(first);
63
68
  }
64
69
  const items = Array.from(dt.items ?? []);
65
70
  const entries = items.map(i => i.webkitGetAsEntry?.()).filter(Boolean) as any[];
66
71
  if (entries.length) {
72
+ // A single loose file that isn't a zip (handled above) or an HTML page
73
+ // can't stand up a site on its own — reject it rather than deploy one
74
+ // stray asset. A lone folder (isDirectory) or multiple entries are fine.
75
+ if (entries.length === 1 && !entries[0].isDirectory && !isHtmlName(entries[0].name)) {
76
+ throw new UnsupportedDropError();
77
+ }
67
78
  const nested = await Promise.all(
68
79
  entries.map(e =>
69
80
  isJunkName(e.name) ? [] : e.isDirectory ? readDirEntry(e) : readFileEntry(e).then(f => [f])
@@ -79,9 +90,14 @@ export async function readDataTransfer(dt: DataTransfer): Promise<ProcessedFile[
79
90
 
80
91
  export async function readFileList(files: FileList): Promise<ProcessedFile[]> {
81
92
  const list = Array.from(files);
82
- if (list.length === 1 && /\.zip$/i.test(list[0].name)) {
93
+ if (list.length === 1 && isZipName(list[0].name)) {
83
94
  return readZip(list[0]);
84
95
  }
96
+ // Same single-loose-file guard as the drag path (also the drag fallback when
97
+ // the entries API is unavailable): only a zip or an HTML page may stand alone.
98
+ if (list.length === 1 && !isHtmlName(list[0].name)) {
99
+ throw new UnsupportedDropError();
100
+ }
85
101
  const out: ProcessedFile[] = [];
86
102
  for (const file of list) {
87
103
  const rel = (file as any).webkitRelativePath || file.name;
@@ -133,7 +149,7 @@ function normalizeFiles(files: ProcessedFile[]): ProcessedFile[] {
133
149
 
134
150
  // --- index.html validation (UI can surface these as warnings) ---------------
135
151
 
136
- const htmlFiles = (files: ProcessedFile[]) => files.filter(f => /\.html?$/i.test(f.name));
152
+ const htmlFiles = (files: ProcessedFile[]) => files.filter(f => isHtmlName(f.name));
137
153
  const hasIndex = (html: ProcessedFile[]) => html.some(f => /^index\.html?$/i.test(f.name));
138
154
 
139
155
  export function getSingleNonIndexHtmlFileName(files: ProcessedFile[]): string | null {
@@ -22,6 +22,14 @@ export interface DeployedSite {
22
22
  siteId: string;
23
23
  }
24
24
 
25
+ /** A deploy failure, handed to `onError` so consumers can render their own error UI. */
26
+ export interface DeployError {
27
+ /** Human-readable text ready to show a user — the same copy the built-in overlay displays. */
28
+ message: string;
29
+ /** The raw underlying reason (e.g. "drop failed: 429"), useful for logging. */
30
+ reason: string;
31
+ }
32
+
25
33
  /** Finite states of a single deploy attempt. */
26
34
  export type DeployStatus =
27
35
  | { kind: 'idle' }
@@ -40,6 +48,7 @@ interface UseDropDeployOptions {
40
48
  client?: DropClient;
41
49
  claimUrl: (siteName: string) => string;
42
50
  onDeploy?: (site: DeployedSite) => void;
51
+ onError?: (error: DeployError) => void;
43
52
  track: TrackFn;
44
53
  }
45
54
 
@@ -77,6 +86,7 @@ export function useDropDeploy({
77
86
  client,
78
87
  claimUrl,
79
88
  onDeploy,
89
+ onError,
80
90
  track,
81
91
  }: UseDropDeployOptions) {
82
92
  const [status, setStatus] = useState<DeployStatus>({ kind: 'idle' });
@@ -96,9 +106,12 @@ export function useDropDeploy({
96
106
  // included where the file set is known (it isn't if `read()` itself threw).
97
107
  // `reason` goes to analytics as-is (raw, e.g. "drop failed: 429"); `message`
98
108
  // is what the user sees, which for a caught error is humanized separately.
109
+ // `onError` gets both so a consumer (e.g. with `showStatus={false}`) can show
110
+ // the humanized message while keeping the raw reason for their own logging.
99
111
  const fail = (message: string, reason: string, extra?: Record<string, unknown>) => {
100
112
  setStatus({ kind: 'error', message });
101
113
  track('dropzone_deploy_failed', { method: source, reason, ...extra });
114
+ onError?.({ message, reason });
102
115
  };
103
116
 
104
117
  const failFromError = (err: unknown, extra?: Record<string, unknown>) => {