@netlify/spark-ui 1.27.2 → 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.
- package/dist/assets/DropZone.css +1 -1
- package/dist/components/preact/DropZone/drop-core/client.js +24 -21
- package/dist/components/preact/DropZone/drop-core/errors.d.ts +5 -0
- package/dist/components/preact/DropZone/drop-core/errors.js +29 -0
- package/dist/components/preact/DropZone/drop-core/index.d.ts +1 -0
- package/dist/components/preact/DropZone/drop-core/index.js +7 -5
- package/dist/components/preact/DropZone/useDropDeploy.js +45 -41
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.css +1 -0
- package/packages/components/preact/DropZone/drop-core/client.ts +8 -3
- package/packages/components/preact/DropZone/drop-core/errors.ts +39 -0
- package/packages/components/preact/DropZone/drop-core/index.ts +1 -0
- package/packages/components/preact/DropZone/useDropDeploy.ts +12 -4
package/dist/assets/DropZone.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.n-fadeIn{opacity:0;animation:fadeIn var(--animation-duration, .3s) ease-out forwards}.n-fadeInUp{opacity:0;transform:translateY(var(--animation-transformY, 5px));animation:fadeInUp var(--animation-duration, .3s) ease-out forwards}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeInUp{to{opacity:1;transform:translateY(0)}}.n-dropzone{--n-dropzone-accent: var(--color-brand-primary, #0e7fe1);--n-dropzone-radius: var(--radius-2xl, 1rem);box-sizing:border-box;position:relative;min-height:4rem;color:var(--color-text-default, #14161a);border:2px dashed var(--color-border-default, rgba(0, 0, 0, .15));border-radius:var(--n-dropzone-radius);outline:none;transition:border-color .2s ease-out,background-color .2s ease-out,box-shadow .2s ease-out}.n-dropzone:focus-visible{border-color:var(--n-dropzone-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--n-dropzone-accent) 40%,transparent)}.n-dropzone.is-active{border-color:var(--n-dropzone-accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--n-dropzone-accent) 18%,transparent)}.n-dropzone.is-busy{border-style:solid;border-color:var(--color-border-subtle, rgba(0, 0, 0, .08))}.n-dropzone-content{transition:opacity .15s ease-out;pointer-events:none}.n-dropzone.is-active:not(.is-bare) .n-dropzone-content,.n-dropzone.is-busy:not(.is-bare) .n-dropzone-content{opacity:0}.n-dropzone-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-s, 1rem);padding:var(--space-l, 1.5rem);text-align:center;border-radius:inherit;pointer-events:none;background:color-mix(in srgb,var(--n-dropzone-accent) 10%,transparent)}.n-dropzone.is-busy .n-dropzone-overlay,.n-dropzone[data-state=error] .n-dropzone-overlay{pointer-events:auto;background:color-mix(in srgb,var(--color-bg, #fff) 88%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.n-dropzone-hint{margin:0;font-size:1.0625rem;font-weight:var(--font-weight-semibold, 600);color:var(--n-dropzone-accent)}.n-dropzone-title{margin:0;font-size:1.125rem;font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-snug, 1.3)}.n-dropzone-title--error{color:var(--color-invalid-text, #d00)}.n-dropzone-subtitle{margin:0;max-width:30rem;font-size:.9375rem;color:var(--color-text-weak, #5c6570);line-height:var(--line-height-text, 1.5)}.n-dropzone-loading{display:flex;flex-direction:column;align-items:center;gap:var(--space-s, 1rem)}.n-dropzone-spinner{width:2.25rem;height:2.25rem;border-radius:var(--radius-pill, 999px);border:3px solid color-mix(in srgb,var(--n-dropzone-accent) 22%,transparent);border-top-color:var(--n-dropzone-accent);animation:dropSpin .72s linear infinite}@keyframes dropSpin{to{transform:rotate(360deg)}}.n-dropzone-nudge{display:block;font-size:.8125rem;color:var(--color-text-weaker, #8a929c)}.n-dropzone-progress{width:min(22rem,100%);height:.5rem;border-radius:var(--radius-pill, 999px);background:var(--color-bg-3, rgba(0, 0, 0, .08));overflow:hidden}.n-dropzone-progress-bar{height:100%;border-radius:inherit;background:var(--n-dropzone-accent);transition:width .3s cubic-bezier(.22,1,.36,1)}.n-dropzone-browse{display:inline-flex;align-items:center;justify-content:center;padding:var(--space-2xs, .5rem) var(--space-m, 1.25rem);border:none;border-radius:var(--radius-pill, 999px);background:var(--n-dropzone-accent);color:var(--neutral-000, #fff);font-size:.9375rem;font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-none, 1);text-decoration:none;cursor:pointer;transition:background-color .15s ease-out,transform .12s ease-out}.n-dropzone-browse:hover{background:var(--color-brand-primary-hover, #0b6bc0);transform:translateY(-1px)}.n-dropzone-browse:active{transform:translateY(0)}.n-dropzone-warning{margin:0;padding:var(--space-2xs, .5rem) var(--space-s, 1rem);max-width:32rem;border-radius:var(--radius-m, .5rem);background:var(--color-warning-bg, #fdf3d7);color:var(--color-warning-text, #7a5b00);font-size:.8125rem}@media (prefers-reduced-motion: reduce){.n-dropzone,.n-dropzone-overlay,.n-dropzone-spinner,.n-dropzone-progress-bar{animation:none!important;transition:none!important}.n-dropzone-spinner{border-top-color:var(--n-dropzone-accent)}}
|
|
1
|
+
.n-fadeIn{opacity:0;animation:fadeIn var(--animation-duration, .3s) ease-out forwards}.n-fadeInUp{opacity:0;transform:translateY(var(--animation-transformY, 5px));animation:fadeInUp var(--animation-duration, .3s) ease-out forwards}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeInUp{to{opacity:1;transform:translateY(0)}}.n-dropzone{--n-dropzone-accent: var(--color-brand-primary, #0e7fe1);--n-dropzone-radius: var(--radius-2xl, 1rem);box-sizing:border-box;position:relative;min-height:4rem;color:var(--color-text-default, #14161a);border:2px dashed var(--color-border-default, rgba(0, 0, 0, .15));border-radius:var(--n-dropzone-radius);outline:none;cursor:pointer;transition:border-color .2s ease-out,background-color .2s ease-out,box-shadow .2s ease-out}.n-dropzone:focus-visible{border-color:var(--n-dropzone-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--n-dropzone-accent) 40%,transparent)}.n-dropzone.is-active{border-color:var(--n-dropzone-accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--n-dropzone-accent) 18%,transparent)}.n-dropzone.is-busy{border-style:solid;border-color:var(--color-border-subtle, rgba(0, 0, 0, .08))}.n-dropzone-content{transition:opacity .15s ease-out;pointer-events:none}.n-dropzone.is-active:not(.is-bare) .n-dropzone-content,.n-dropzone.is-busy:not(.is-bare) .n-dropzone-content{opacity:0}.n-dropzone-overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-s, 1rem);padding:var(--space-l, 1.5rem);text-align:center;border-radius:inherit;pointer-events:none;background:color-mix(in srgb,var(--n-dropzone-accent) 10%,transparent)}.n-dropzone.is-busy .n-dropzone-overlay,.n-dropzone[data-state=error] .n-dropzone-overlay{pointer-events:auto;background:color-mix(in srgb,var(--color-bg, #fff) 88%,transparent);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.n-dropzone-hint{margin:0;font-size:1.0625rem;font-weight:var(--font-weight-semibold, 600);color:var(--n-dropzone-accent)}.n-dropzone-title{margin:0;font-size:1.125rem;font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-snug, 1.3)}.n-dropzone-title--error{color:var(--color-invalid-text, #d00)}.n-dropzone-subtitle{margin:0;max-width:30rem;font-size:.9375rem;color:var(--color-text-weak, #5c6570);line-height:var(--line-height-text, 1.5)}.n-dropzone-loading{display:flex;flex-direction:column;align-items:center;gap:var(--space-s, 1rem)}.n-dropzone-spinner{width:2.25rem;height:2.25rem;border-radius:var(--radius-pill, 999px);border:3px solid color-mix(in srgb,var(--n-dropzone-accent) 22%,transparent);border-top-color:var(--n-dropzone-accent);animation:dropSpin .72s linear infinite}@keyframes dropSpin{to{transform:rotate(360deg)}}.n-dropzone-nudge{display:block;font-size:.8125rem;color:var(--color-text-weaker, #8a929c)}.n-dropzone-progress{width:min(22rem,100%);height:.5rem;border-radius:var(--radius-pill, 999px);background:var(--color-bg-3, rgba(0, 0, 0, .08));overflow:hidden}.n-dropzone-progress-bar{height:100%;border-radius:inherit;background:var(--n-dropzone-accent);transition:width .3s cubic-bezier(.22,1,.36,1)}.n-dropzone-browse{display:inline-flex;align-items:center;justify-content:center;padding:var(--space-2xs, .5rem) var(--space-m, 1.25rem);border:none;border-radius:var(--radius-pill, 999px);background:var(--n-dropzone-accent);color:var(--neutral-000, #fff);font-size:.9375rem;font-weight:var(--font-weight-semibold, 600);line-height:var(--line-height-none, 1);text-decoration:none;cursor:pointer;transition:background-color .15s ease-out,transform .12s ease-out}.n-dropzone-browse:hover{background:var(--color-brand-primary-hover, #0b6bc0);transform:translateY(-1px)}.n-dropzone-browse:active{transform:translateY(0)}.n-dropzone-warning{margin:0;padding:var(--space-2xs, .5rem) var(--space-s, 1rem);max-width:32rem;border-radius:var(--radius-m, .5rem);background:var(--color-warning-bg, #fdf3d7);color:var(--color-warning-text, #7a5b00);font-size:.8125rem}@media (prefers-reduced-motion: reduce){.n-dropzone,.n-dropzone-overlay,.n-dropzone-spinner,.n-dropzone-progress-bar{animation:none!important;transition:none!important}.n-dropzone-spinner{border-top-color:var(--n-dropzone-accent)}}
|
|
@@ -1,41 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
8
|
-
if (!
|
|
9
|
-
return (await
|
|
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(
|
|
12
|
-
const
|
|
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:
|
|
18
|
+
body: JSON.stringify({ files: t, token: o })
|
|
16
19
|
});
|
|
17
|
-
if (!
|
|
18
|
-
return
|
|
20
|
+
if (!e.ok) throw r("drop", e.status);
|
|
21
|
+
return e.json();
|
|
19
22
|
}
|
|
20
|
-
async uploadFile(
|
|
21
|
-
const
|
|
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:
|
|
27
|
+
body: e
|
|
25
28
|
});
|
|
26
|
-
if (!
|
|
29
|
+
if (!i.ok) throw r(`upload ${o}`, i.status);
|
|
27
30
|
}
|
|
28
|
-
async waitUntilReady(
|
|
29
|
-
for (let
|
|
30
|
-
const a = await fetch(`${this.apiBase}/deploys/${
|
|
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((
|
|
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
|
-
|
|
43
|
+
p as AnonymousDropClient
|
|
41
44
|
};
|
|
@@ -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
|
+
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { getDigest as o } from "./digest.js";
|
|
2
|
-
import { getSingleNonIndexHtmlFileName as
|
|
3
|
-
import { AnonymousDropClient as
|
|
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
|
-
|
|
7
|
+
a as AnonymousDropClient,
|
|
7
8
|
x as deployFiles,
|
|
8
9
|
o as getDigest,
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
2
|
-
import { getSingleNonIndexHtmlFileName as
|
|
3
|
-
import { AnonymousDropClient as
|
|
4
|
-
import { deployFiles as
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
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
|
|
17
|
-
const
|
|
18
|
-
return
|
|
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
|
|
21
|
-
apiBase:
|
|
22
|
-
client:
|
|
23
|
-
claimUrl:
|
|
24
|
-
onDeploy:
|
|
25
|
-
track:
|
|
21
|
+
function P({
|
|
22
|
+
apiBase: e,
|
|
23
|
+
client: d,
|
|
24
|
+
claimUrl: f,
|
|
25
|
+
onDeploy: a,
|
|
26
|
+
track: l
|
|
26
27
|
}) {
|
|
27
|
-
const [
|
|
28
|
-
async function
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
t({ kind: "error", message:
|
|
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
|
|
35
|
-
if (!
|
|
36
|
-
|
|
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
|
-
|
|
43
|
+
s === "drag" && l("dropzone_files_dropped", { method: s, file_count: n.length }), c(W(n));
|
|
40
44
|
const {
|
|
41
|
-
url:
|
|
42
|
-
token:
|
|
43
|
-
deploy:
|
|
44
|
-
} = await
|
|
45
|
-
onProgress: (
|
|
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",
|
|
52
|
+
localStorage.setItem("drop-token", o), localStorage.setItem("dropSiteName", r.subdomain);
|
|
49
53
|
} catch {
|
|
50
54
|
}
|
|
51
|
-
t({ kind: "redirecting" }),
|
|
52
|
-
method:
|
|
53
|
-
site_name:
|
|
54
|
-
file_count:
|
|
55
|
-
}),
|
|
56
|
-
} catch (
|
|
57
|
-
|
|
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
|
-
|
|
65
|
+
c(null), t({ kind: "idle" });
|
|
62
66
|
}
|
|
63
|
-
return { status:
|
|
67
|
+
return { status: u, warning: g, busy: y, deploy: k, reset: _ };
|
|
64
68
|
}
|
|
65
69
|
export {
|
|
66
|
-
|
|
70
|
+
P as useDropDeploy
|
|
67
71
|
};
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
158
|
+
failFromError(err);
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
|