@netlify/spark-ui 1.27.3 → 1.27.4

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,41 +1,44 @@
1
- const i = "https://api.netlify.com/api/v1";
2
- class n {
3
- constructor(e = i) {
4
- this.apiBase = e;
1
+ function r(s, t) {
2
+ return Object.assign(new Error(`${s} failed: ${t}`), { status: t });
3
+ }
4
+ const n = "https://api.netlify.com/api/v1";
5
+ class p {
6
+ constructor(t = n) {
7
+ this.apiBase = t;
5
8
  }
6
9
  async getToken() {
7
- const e = await fetch(`${this.apiBase}/drop/token`, { method: "POST" });
8
- if (!e.ok) throw new Error(`drop/token failed: ${e.status}`);
9
- return (await e.json()).token;
10
+ const t = await fetch(`${this.apiBase}/drop/token`, { method: "POST" });
11
+ if (!t.ok) throw r("drop/token", t.status);
12
+ return (await t.json()).token;
10
13
  }
11
- async createDeploy(e, o) {
12
- const t = await fetch(`${this.apiBase}/drop`, {
14
+ async createDeploy(t, o) {
15
+ const e = await fetch(`${this.apiBase}/drop`, {
13
16
  method: "POST",
14
17
  headers: { "Content-Type": "application/json" },
15
- body: JSON.stringify({ files: e, token: o })
18
+ body: JSON.stringify({ files: t, token: o })
16
19
  });
17
- if (!t.ok) throw new Error(`drop failed: ${t.status}`);
18
- return t.json();
20
+ if (!e.ok) throw r("drop", e.status);
21
+ return e.json();
19
22
  }
20
- async uploadFile(e, o, t, a) {
21
- const r = await fetch(`${this.apiBase}/deploys/${e}/files${o}`, {
23
+ async uploadFile(t, o, e, a) {
24
+ const i = await fetch(`${this.apiBase}/deploys/${t}/files${o}`, {
22
25
  method: "PUT",
23
26
  headers: { "Content-Type": "application/octet-stream", Authorization: `Bearer ${a}` },
24
- body: t
27
+ body: e
25
28
  });
26
- if (!r.ok) throw new Error(`upload ${o} failed: ${r.status}`);
29
+ if (!i.ok) throw r(`upload ${o}`, i.status);
27
30
  }
28
- async waitUntilReady(e, o) {
29
- for (let t = 0; t < 600; t++) {
30
- const a = await fetch(`${this.apiBase}/deploys/${e.deploy_id}`, {
31
+ async waitUntilReady(t, o) {
32
+ for (let e = 0; e < 600; e++) {
33
+ const a = await fetch(`${this.apiBase}/deploys/${t.deploy_id}`, {
31
34
  headers: { Authorization: `Bearer ${o}` }
32
35
  });
33
36
  if (a.ok && (await a.json()).state === "ready") return;
34
- await new Promise((r) => setTimeout(r, 1e3));
37
+ await new Promise((i) => setTimeout(i, 1e3));
35
38
  }
36
39
  throw new Error("Deploy did not become ready in time");
37
40
  }
38
41
  }
39
42
  export {
40
- n as AnonymousDropClient
43
+ p as AnonymousDropClient
41
44
  };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Turn a raw deploy failure into copy a non-technical user can act on.
3
+ * The original message is still tracked via analytics — this is only for display.
4
+ */
5
+ export declare function humanizeDropError(err: unknown): string;
@@ -0,0 +1,29 @@
1
+ function a(e) {
2
+ if (!(e instanceof Error)) return "Something went wrong. Please try again.";
3
+ if (e.message === "Deploy did not become ready in time")
4
+ return "Your site is taking longer than expected to go live. Please try again in a moment.";
5
+ if (e instanceof TypeError)
6
+ return "Couldn't connect to Netlify. Check your internet connection and try again.";
7
+ const t = "status" in e && typeof e.status == "number" ? e.status : void 0;
8
+ switch (t) {
9
+ case 429:
10
+ return "Too many attempts — please wait a moment and try again.";
11
+ case 413:
12
+ return "Your upload is too large to deploy this way.";
13
+ case 404:
14
+ return "We couldn't find that deploy. Please try again.";
15
+ case 401:
16
+ case 403:
17
+ return "That request wasn't allowed. Please try again.";
18
+ case 500:
19
+ case 502:
20
+ case 503:
21
+ case 504:
22
+ return "Netlify's servers are having trouble right now. Please try again shortly.";
23
+ default:
24
+ return t ? "Something went wrong talking to Netlify. Please try again." : "Something went wrong. Please try again.";
25
+ }
26
+ }
27
+ export {
28
+ a as humanizeDropError
29
+ };
@@ -3,3 +3,4 @@ export * from './digest';
3
3
  export * from './readFiles';
4
4
  export * from './client';
5
5
  export * from './deploy';
6
+ export * from './errors';
@@ -1,13 +1,15 @@
1
1
  import { getDigest as o } from "./digest.js";
2
- import { getSingleNonIndexHtmlFileName as l, hasMultipleHtmlFilesWithoutIndex as i, readDataTransfer as m, readFileList as n } from "./readFiles.js";
3
- import { AnonymousDropClient as p } from "./client.js";
2
+ import { getSingleNonIndexHtmlFileName as i, hasMultipleHtmlFilesWithoutIndex as l, readDataTransfer as m, readFileList as n } from "./readFiles.js";
3
+ import { AnonymousDropClient as a } from "./client.js";
4
4
  import { deployFiles as x } from "./deploy.js";
5
+ import { humanizeDropError as d } from "./errors.js";
5
6
  export {
6
- p as AnonymousDropClient,
7
+ a as AnonymousDropClient,
7
8
  x as deployFiles,
8
9
  o as getDigest,
9
- l as getSingleNonIndexHtmlFileName,
10
- i as hasMultipleHtmlFilesWithoutIndex,
10
+ i as getSingleNonIndexHtmlFileName,
11
+ l as hasMultipleHtmlFilesWithoutIndex,
12
+ d as humanizeDropError,
11
13
  m as readDataTransfer,
12
14
  n as readFileList
13
15
  };
@@ -1,67 +1,71 @@
1
- import { d as m, A as x } from "../../../chunks/hooks.module.DRcxknIz.js";
2
- import { getSingleNonIndexHtmlFileName as S, hasMultipleHtmlFilesWithoutIndex as b } from "./drop-core/readFiles.js";
3
- import { AnonymousDropClient as I } from "./drop-core/client.js";
4
- import { deployFiles as z } from "./drop-core/deploy.js";
5
- function F(n) {
6
- switch (n.phase) {
1
+ import { d as p, A as S } from "../../../chunks/hooks.module.DRcxknIz.js";
2
+ import { getSingleNonIndexHtmlFileName as b, hasMultipleHtmlFilesWithoutIndex as I } from "./drop-core/readFiles.js";
3
+ import { AnonymousDropClient as z } from "./drop-core/client.js";
4
+ import { deployFiles as F } from "./drop-core/deploy.js";
5
+ import { humanizeDropError as E } from "./drop-core/errors.js";
6
+ function H(e) {
7
+ switch (e.phase) {
7
8
  case "creating":
8
9
  return { kind: "creating" };
9
10
  case "uploading":
10
- return { kind: "uploading", uploaded: n.uploaded ?? 0, total: n.total ?? 0 };
11
+ return { kind: "uploading", uploaded: e.uploaded ?? 0, total: e.total ?? 0 };
11
12
  case "processing":
12
13
  case "ready":
13
14
  return { kind: "processing" };
14
15
  }
15
16
  }
16
- function H(n) {
17
- const i = S(n);
18
- return i ? `No index.html found — "${i}" won’t be served at the site root.` : b(n) ? "No index.html found — your site may not have a landing page." : null;
17
+ function W(e) {
18
+ const d = b(e);
19
+ return d ? `No index.html found — "${d}" won’t be served at the site root.` : I(e) ? "No index.html found — your site may not have a landing page." : null;
19
20
  }
20
- function C({
21
- apiBase: n,
22
- client: i,
23
- claimUrl: g,
24
- onDeploy: d,
25
- track: s
21
+ function P({
22
+ apiBase: e,
23
+ client: d,
24
+ claimUrl: f,
25
+ onDeploy: a,
26
+ track: l
26
27
  }) {
27
- const [a, t] = m({ kind: "idle" }), [f, l] = m(null), h = x(i ?? new I(n)).current, k = a.kind !== "idle" && a.kind !== "error";
28
- async function y(w, r) {
29
- l(null), t({ kind: "reading" });
30
- const p = (e, u) => {
31
- t({ kind: "error", message: e }), s("dropzone_deploy_failed", { method: r, reason: e, ...u });
28
+ const [u, t] = p({ kind: "idle" }), [g, c] = p(null), h = S(d ?? new z(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 }), l("dropzone_deploy_failed", { method: s, reason: i, ...o });
33
+ }, w = (n, i) => {
34
+ const o = n instanceof Error ? n.message : String(n);
35
+ m(E(n), o, i);
32
36
  };
33
37
  try {
34
- const e = await w();
35
- if (!e.length) {
36
- p("No files found to deploy.", { file_count: 0 });
38
+ const n = await N();
39
+ if (!n.length) {
40
+ m("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
37
41
  return;
38
42
  }
39
- r === "drag" && s("dropzone_files_dropped", { method: r, file_count: e.length }), l(H(e));
43
+ s === "drag" && l("dropzone_files_dropped", { method: s, file_count: n.length }), c(W(n));
40
44
  const {
41
- url: u,
42
- token: c,
43
- deploy: o
44
- } = await z(h, e, {
45
- onProgress: (N) => t(F(N))
45
+ url: i,
46
+ token: o,
47
+ deploy: r
48
+ } = await F(h, n, {
49
+ onProgress: (x) => t(H(x))
46
50
  });
47
51
  try {
48
- localStorage.setItem("drop-token", c), localStorage.setItem("dropSiteName", o.subdomain);
52
+ localStorage.setItem("drop-token", o), localStorage.setItem("dropSiteName", r.subdomain);
49
53
  } catch {
50
54
  }
51
- t({ kind: "redirecting" }), s("dropzone_deploy_succeeded", {
52
- method: r,
53
- site_name: o.subdomain,
54
- file_count: e.length
55
- }), d == null || d({ url: u, token: c, siteName: o.subdomain, siteId: o.id }), window.location.assign(`${g(o.subdomain)}#drop_token=${c}`);
56
- } catch (e) {
57
- p(e instanceof Error ? e.message : String(e));
55
+ t({ kind: "redirecting" }), l("dropzone_deploy_succeeded", {
56
+ method: s,
57
+ site_name: r.subdomain,
58
+ file_count: n.length
59
+ }), a == null || a({ url: i, token: o, siteName: r.subdomain, siteId: r.id }), window.location.assign(`${f(r.subdomain)}#drop_token=${o}`);
60
+ } catch (n) {
61
+ w(n);
58
62
  }
59
63
  }
60
64
  function _() {
61
- l(null), t({ kind: "idle" });
65
+ c(null), t({ kind: "idle" });
62
66
  }
63
- return { status: a, warning: f, busy: k, deploy: y, reset: _ };
67
+ return { status: u, warning: g, busy: y, deploy: k, reset: _ };
64
68
  }
65
69
  export {
66
- C as useDropDeploy
70
+ P as useDropDeploy
67
71
  };
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.3",
4
+ "version": "1.27.4",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -1,5 +1,10 @@
1
1
  import type { DropResponse, Digest } from './types';
2
2
 
3
+ /** A normal Error with the response's HTTP status attached, so callers can branch on it directly. */
4
+ function apiError(action: string, status: number): Error & { status: number } {
5
+ return Object.assign(new Error(`${action} failed: ${status}`), { status });
6
+ }
7
+
3
8
  export interface DropClient {
4
9
  getToken(): Promise<string>;
5
10
  createDeploy(digest: Digest, token: string): Promise<DropResponse>;
@@ -18,7 +23,7 @@ export class AnonymousDropClient implements DropClient {
18
23
 
19
24
  async getToken(): Promise<string> {
20
25
  const res = await fetch(`${this.apiBase}/drop/token`, { method: 'POST' });
21
- if (!res.ok) throw new Error(`drop/token failed: ${res.status}`);
26
+ if (!res.ok) throw apiError('drop/token', res.status);
22
27
  return (await res.json()).token;
23
28
  }
24
29
 
@@ -28,7 +33,7 @@ export class AnonymousDropClient implements DropClient {
28
33
  headers: { 'Content-Type': 'application/json' },
29
34
  body: JSON.stringify({ files, token }),
30
35
  });
31
- if (!res.ok) throw new Error(`drop failed: ${res.status}`);
36
+ if (!res.ok) throw apiError('drop', res.status);
32
37
  return res.json();
33
38
  }
34
39
 
@@ -44,7 +49,7 @@ export class AnonymousDropClient implements DropClient {
44
49
  headers: { 'Content-Type': 'application/octet-stream', Authorization: `Bearer ${token}` },
45
50
  body: content,
46
51
  });
47
- if (!res.ok) throw new Error(`upload ${path} failed: ${res.status}`);
52
+ if (!res.ok) throw apiError(`upload ${path}`, res.status);
48
53
  }
49
54
 
50
55
  async waitUntilReady(deploy: DropResponse, token: string): Promise<void> {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Turn a raw deploy failure into copy a non-technical user can act on.
3
+ * The original message is still tracked via analytics — this is only for display.
4
+ */
5
+ export function humanizeDropError(err: unknown): string {
6
+ if (!(err instanceof Error)) return 'Something went wrong. Please try again.';
7
+
8
+ if (err.message === 'Deploy did not become ready in time') {
9
+ return 'Your site is taking longer than expected to go live. Please try again in a moment.';
10
+ }
11
+ // fetch() rejects with a TypeError when the network is unreachable (offline, DNS, CORS, etc).
12
+ if (err instanceof TypeError) {
13
+ return "Couldn't connect to Netlify. Check your internet connection and try again.";
14
+ }
15
+
16
+ // client.ts attaches `.status` to API failures — see apiError().
17
+ const status = 'status' in err && typeof err.status === 'number' ? err.status : undefined;
18
+
19
+ switch (status) {
20
+ case 429:
21
+ return 'Too many attempts — please wait a moment and try again.';
22
+ case 413:
23
+ return 'Your upload is too large to deploy this way.';
24
+ case 404:
25
+ return "We couldn't find that deploy. Please try again.";
26
+ case 401:
27
+ case 403:
28
+ return "That request wasn't allowed. Please try again.";
29
+ case 500:
30
+ case 502:
31
+ case 503:
32
+ case 504:
33
+ return "Netlify's servers are having trouble right now. Please try again shortly.";
34
+ default:
35
+ return status
36
+ ? 'Something went wrong talking to Netlify. Please try again.'
37
+ : 'Something went wrong. Please try again.';
38
+ }
39
+ }
@@ -3,3 +3,4 @@ export * from './digest';
3
3
  export * from './readFiles';
4
4
  export * from './client';
5
5
  export * from './deploy';
6
+ export * from './errors';
@@ -4,6 +4,7 @@ import {
4
4
  deployFiles,
5
5
  getSingleNonIndexHtmlFileName,
6
6
  hasMultipleHtmlFilesWithoutIndex,
7
+ humanizeDropError,
7
8
  type DeployProgress,
8
9
  type DropClient,
9
10
  type ProcessedFile,
@@ -92,15 +93,22 @@ export function useDropDeploy({
92
93
  // read/deploy throw) reports uniformly — the mirror of the
93
94
  // `dropzone_deploy_succeeded` event on the happy path. `file_count` is only
94
95
  // included where the file set is known (it isn't if `read()` itself threw).
95
- const fail = (message: string, extra?: Record<string, unknown>) => {
96
+ // `reason` goes to analytics as-is (raw, e.g. "drop failed: 429"); `message`
97
+ // is what the user sees, which for a caught error is humanized separately.
98
+ const fail = (message: string, reason: string, extra?: Record<string, unknown>) => {
96
99
  setStatus({ kind: 'error', message });
97
- track('dropzone_deploy_failed', { method: source, reason: message, ...extra });
100
+ track('dropzone_deploy_failed', { method: source, reason, ...extra });
101
+ };
102
+
103
+ const failFromError = (err: unknown, extra?: Record<string, unknown>) => {
104
+ const reason = err instanceof Error ? err.message : String(err);
105
+ fail(humanizeDropError(err), reason, extra);
98
106
  };
99
107
 
100
108
  try {
101
109
  const files = await read();
102
110
  if (!files.length) {
103
- fail('No files found to deploy.', { file_count: 0 });
111
+ fail('No files found to deploy.', 'No files found to deploy.', { file_count: 0 });
104
112
  return;
105
113
  }
106
114
 
@@ -147,7 +155,7 @@ export function useDropDeploy({
147
155
  // `#drop_token=` and persists it for the claim + post-signup steps.
148
156
  window.location.assign(`${claimUrl(result.subdomain)}#drop_token=${token}`);
149
157
  } catch (err) {
150
- fail(err instanceof Error ? err.message : String(err));
158
+ failFromError(err);
151
159
  }
152
160
  }
153
161