@netlify/spark-ui 1.27.7 → 1.29.0
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/components/preact/DropZone/DropZone.d.ts +18 -2
- package/dist/components/preact/DropZone/DropZone.js +52 -42
- package/dist/components/preact/DropZone/drop-core/detectBuild.d.ts +23 -0
- package/dist/components/preact/DropZone/drop-core/detectBuild.js +68 -0
- package/dist/components/preact/DropZone/drop-core/index.d.ts +1 -0
- package/dist/components/preact/DropZone/drop-core/index.js +9 -7
- package/dist/components/preact/DropZone/index.d.ts +2 -2
- package/dist/components/preact/DropZone/index.js +9 -7
- package/dist/components/preact/DropZone/useDropDeploy.d.ts +5 -2
- package/dist/components/preact/DropZone/useDropDeploy.js +56 -39
- package/dist/components/preact/index.js +17 -16
- package/dist/utils/index.d.ts +1 -0
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.tsx +37 -2
- package/packages/components/preact/DropZone/drop-core/detectBuild.ts +130 -0
- package/packages/components/preact/DropZone/drop-core/index.ts +1 -0
- package/packages/components/preact/DropZone/index.tsx +3 -0
- package/packages/components/preact/DropZone/useDropDeploy.ts +34 -2
- package/packages/utils/index.ts +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ComponentChildren } from 'preact';
|
|
2
|
-
import { DropClient } from './drop-core';
|
|
2
|
+
import { BuildDetection, DropClient } from './drop-core';
|
|
3
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. */
|
|
@@ -10,6 +10,21 @@ interface Props {
|
|
|
10
10
|
client?: DropClient;
|
|
11
11
|
/** Build the "claim this site" URL from the assigned site name (subdomain). */
|
|
12
12
|
claimUrl?: (siteName: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Where to send a build-required drop (framework source, build command,
|
|
15
|
+
* functions) when `onBuildRequired` isn't set. Such projects can't deploy
|
|
16
|
+
* through the anonymous static drop API, so by default the user is redirected
|
|
17
|
+
* here — the app signup page with `next=/drop` — to build on the
|
|
18
|
+
* authenticated Drop page. Override to point at your own destination.
|
|
19
|
+
*/
|
|
20
|
+
buildSignupUrl?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Called when a build-required drop is detected, instead of the default
|
|
23
|
+
* `buildSignupUrl` redirect. Use it to own the hand-off yourself (custom
|
|
24
|
+
* routing, persisting the drop, gating on auth). When set, the component does
|
|
25
|
+
* not redirect. The static drop path is unaffected either way.
|
|
26
|
+
*/
|
|
27
|
+
onBuildRequired?: (info: BuildDetection) => void;
|
|
13
28
|
/** Called once the deploy is live. */
|
|
14
29
|
onDeploy?: (result: DeployedSite) => void;
|
|
15
30
|
/**
|
|
@@ -33,6 +48,7 @@ interface Props {
|
|
|
33
48
|
* Emitted events (all include `dropzone_id` when `analyticsId` is set):
|
|
34
49
|
* - `dropzone_files_dropped` — files dropped via drag ({ method: 'drag', file_count })
|
|
35
50
|
* - `dropzone_browse_opened` — file picker opened ({ method: 'keyboard' | 'click' })
|
|
51
|
+
* - `dropzone_build_required` — drop needs a build; handed off to signup ({ method, reason, file_count })
|
|
36
52
|
* - `dropzone_deploy_succeeded` — deploy went live ({ method, site_name, file_count })
|
|
37
53
|
* - `dropzone_deploy_failed` — deploy errored out ({ method, reason, file_count? })
|
|
38
54
|
*/
|
|
@@ -45,5 +61,5 @@ interface Props {
|
|
|
45
61
|
analyticsId?: string;
|
|
46
62
|
className?: string;
|
|
47
63
|
}
|
|
48
|
-
export default function DropZone({ children, apiBase, client, claimUrl, onDeploy, onError, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
|
|
64
|
+
export default function DropZone({ children, apiBase, client, claimUrl, buildSignupUrl, onBuildRequired, onDeploy, onError, showStatus, onTrack, analyticsId, className, }: Props): import("preact").JSX.Element;
|
|
49
65
|
export {};
|
|
@@ -1,54 +1,59 @@
|
|
|
1
1
|
import { u as e } from "../../../chunks/jsxRuntime.module.Bzuv3cXw.js";
|
|
2
|
-
import { A as
|
|
3
|
-
import { readFileList as
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
2
|
+
import { A as D, d as E, y as I } from "../../../chunks/hooks.module.DRcxknIz.js";
|
|
3
|
+
import { readFileList as P, readDataTransfer as U } from "./drop-core/readFiles.js";
|
|
4
|
+
import "./drop-core/detectBuild.js";
|
|
5
|
+
import { useDropDeploy as j } from "./useDropDeploy.js";
|
|
6
|
+
import { Fragment as k } from "preact";
|
|
7
|
+
import '../../../assets/DropZone.css';const B = 120, F = (n) => `https://app.netlify.com/drop/${n}`, M = "https://app.netlify.com/signup?next=/drop&utm_campaign=drop";
|
|
8
|
+
function H({
|
|
8
9
|
children: n,
|
|
9
10
|
apiBase: l,
|
|
10
11
|
client: i,
|
|
11
|
-
claimUrl: s =
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
claimUrl: s = F,
|
|
13
|
+
buildSignupUrl: u = M,
|
|
14
|
+
onBuildRequired: w,
|
|
15
|
+
onDeploy: A,
|
|
16
|
+
onError: C,
|
|
14
17
|
showStatus: f = !0,
|
|
15
|
-
onTrack:
|
|
18
|
+
onTrack: c,
|
|
16
19
|
analyticsId: g,
|
|
17
|
-
className:
|
|
20
|
+
className: T
|
|
18
21
|
}) {
|
|
19
|
-
const
|
|
22
|
+
const h = (r, t = {}) => c == null ? void 0 : c(r, g ? { dropzone_id: g, ...t } : t), { status: a, warning: m, busy: o, deploy: v, reset: _ } = j({
|
|
20
23
|
apiBase: l,
|
|
21
24
|
client: i,
|
|
22
25
|
claimUrl: s,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
buildSignupUrl: u,
|
|
27
|
+
onBuildRequired: w,
|
|
28
|
+
onDeploy: A,
|
|
29
|
+
onError: C,
|
|
30
|
+
track: h
|
|
31
|
+
}), { active: p, keepActive: S, endDrag: R } = N(), b = D(null), y = (r) => {
|
|
27
32
|
var t;
|
|
28
|
-
|
|
29
|
-
},
|
|
33
|
+
h("dropzone_browse_opened", { method: r }), (t = b.current) == null || t.click();
|
|
34
|
+
}, $ = f && (p || o || a.kind === "error"), x = [
|
|
30
35
|
"n-dropzone",
|
|
31
36
|
p && "is-active",
|
|
32
37
|
o && "is-busy",
|
|
33
38
|
!f && "is-bare",
|
|
34
|
-
|
|
39
|
+
T
|
|
35
40
|
].filter(Boolean).join(" ");
|
|
36
41
|
return /* @__PURE__ */ e(
|
|
37
42
|
"div",
|
|
38
43
|
{
|
|
39
|
-
class:
|
|
44
|
+
class: x,
|
|
40
45
|
"data-state": a.kind,
|
|
41
46
|
role: "button",
|
|
42
47
|
tabIndex: o ? -1 : 0,
|
|
43
48
|
"aria-label": "Drop a site folder or zip here to deploy it, or press Enter to browse for files",
|
|
44
49
|
"aria-busy": o,
|
|
45
50
|
onDragOver: (r) => {
|
|
46
|
-
o || (r.preventDefault(),
|
|
51
|
+
o || (r.preventDefault(), S());
|
|
47
52
|
},
|
|
48
53
|
onDrop: (r) => {
|
|
49
|
-
if (r.preventDefault(),
|
|
54
|
+
if (r.preventDefault(), R(), o) return;
|
|
50
55
|
const t = r.dataTransfer;
|
|
51
|
-
t &&
|
|
56
|
+
t && v(() => U(t), "drag");
|
|
52
57
|
},
|
|
53
58
|
onClick: (r) => {
|
|
54
59
|
!o && r.target === r.currentTarget && y("click");
|
|
@@ -61,7 +66,7 @@ function L({
|
|
|
61
66
|
/* @__PURE__ */ e(
|
|
62
67
|
"input",
|
|
63
68
|
{
|
|
64
|
-
ref:
|
|
69
|
+
ref: b,
|
|
65
70
|
type: "file",
|
|
66
71
|
hidden: !0,
|
|
67
72
|
multiple: !0,
|
|
@@ -70,17 +75,17 @@ function L({
|
|
|
70
75
|
onChange: (r) => {
|
|
71
76
|
var z;
|
|
72
77
|
const t = r.currentTarget;
|
|
73
|
-
(z = t.files) != null && z.length &&
|
|
78
|
+
(z = t.files) != null && z.length && v(() => P(t.files), "browse"), t.value = "";
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
),
|
|
77
|
-
|
|
82
|
+
$ && /* @__PURE__ */ e(
|
|
78
83
|
"div",
|
|
79
84
|
{
|
|
80
85
|
class: "n-dropzone-overlay n-fadeIn",
|
|
81
86
|
children: [
|
|
82
|
-
/* @__PURE__ */ e(
|
|
83
|
-
|
|
87
|
+
/* @__PURE__ */ e(O, { status: a, active: p, onReset: _ }),
|
|
88
|
+
m && a.kind !== "error" && o && /* @__PURE__ */ e("p", { class: "n-dropzone-warning", role: "status", children: m })
|
|
84
89
|
]
|
|
85
90
|
},
|
|
86
91
|
a.kind === "idle" ? "active" : a.kind
|
|
@@ -89,15 +94,15 @@ function L({
|
|
|
89
94
|
}
|
|
90
95
|
);
|
|
91
96
|
}
|
|
92
|
-
function
|
|
93
|
-
const [n, l] =
|
|
94
|
-
return
|
|
95
|
-
l(!0), clearTimeout(i.current), i.current = setTimeout(() => l(!1),
|
|
97
|
+
function N() {
|
|
98
|
+
const [n, l] = E(!1), i = D();
|
|
99
|
+
return I(() => () => clearTimeout(i.current), []), { active: n, keepActive: () => {
|
|
100
|
+
l(!0), clearTimeout(i.current), i.current = setTimeout(() => l(!1), B);
|
|
96
101
|
}, endDrag: () => {
|
|
97
102
|
clearTimeout(i.current), l(!1);
|
|
98
103
|
} };
|
|
99
104
|
}
|
|
100
|
-
function
|
|
105
|
+
function O({
|
|
101
106
|
status: n,
|
|
102
107
|
active: l,
|
|
103
108
|
onReset: i
|
|
@@ -107,29 +112,34 @@ function U({
|
|
|
107
112
|
case "idle":
|
|
108
113
|
return l ? /* @__PURE__ */ e("p", { class: "n-dropzone-hint", children: "Drop to deploy" }) : null;
|
|
109
114
|
case "reading":
|
|
110
|
-
return /* @__PURE__ */ e(
|
|
115
|
+
return /* @__PURE__ */ e(d, { label: "Reading files…" });
|
|
111
116
|
case "creating":
|
|
112
|
-
return /* @__PURE__ */ e(
|
|
117
|
+
return /* @__PURE__ */ e(d, { label: "Creating deploy…" });
|
|
113
118
|
case "processing":
|
|
114
|
-
return /* @__PURE__ */ e(
|
|
119
|
+
return /* @__PURE__ */ e(d, { label: "Processing — almost live…" });
|
|
115
120
|
case "redirecting":
|
|
116
|
-
return /* @__PURE__ */ e(
|
|
121
|
+
return /* @__PURE__ */ e(
|
|
122
|
+
d,
|
|
123
|
+
{
|
|
124
|
+
label: n.target === "signup" ? "This project needs a build — taking you to Netlify…" : "Site’s live — taking you to claim it…"
|
|
125
|
+
}
|
|
126
|
+
);
|
|
117
127
|
case "uploading": {
|
|
118
128
|
const s = n.total ? Math.round(n.uploaded / n.total * 100) : 0;
|
|
119
|
-
return /* @__PURE__ */ e(
|
|
120
|
-
/* @__PURE__ */ e(
|
|
129
|
+
return /* @__PURE__ */ e(k, { children: [
|
|
130
|
+
/* @__PURE__ */ e(d, { label: `Uploading ${n.uploaded}/${n.total} files…` }),
|
|
121
131
|
/* @__PURE__ */ e("div", { class: "n-dropzone-progress", role: "progressbar", "aria-valuenow": s, children: /* @__PURE__ */ e("div", { class: "n-dropzone-progress-bar", style: { width: `${s}%` } }) })
|
|
122
132
|
] });
|
|
123
133
|
}
|
|
124
134
|
case "error":
|
|
125
|
-
return /* @__PURE__ */ e(
|
|
135
|
+
return /* @__PURE__ */ e(k, { children: [
|
|
126
136
|
/* @__PURE__ */ e("p", { class: "n-dropzone-title n-dropzone-title--error", children: "Something went wrong" }),
|
|
127
137
|
/* @__PURE__ */ e("p", { class: "n-dropzone-subtitle", role: "alert", children: n.message }),
|
|
128
138
|
/* @__PURE__ */ e("button", { type: "button", class: "n-dropzone-browse", onClick: i, children: "Try again" })
|
|
129
139
|
] });
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
|
-
function
|
|
142
|
+
function d({ label: n }) {
|
|
133
143
|
return /* @__PURE__ */ e("div", { class: "n-dropzone-loading", children: [
|
|
134
144
|
/* @__PURE__ */ e("span", { class: "n-dropzone-spinner", "aria-hidden": "true" }),
|
|
135
145
|
/* @__PURE__ */ e("p", { class: "n-dropzone-subtitle", "aria-live": "polite", children: [
|
|
@@ -140,5 +150,5 @@ function c({ label: n }) {
|
|
|
140
150
|
] });
|
|
141
151
|
}
|
|
142
152
|
export {
|
|
143
|
-
|
|
153
|
+
H as default
|
|
144
154
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ProcessedFile } from './types';
|
|
2
|
+
/** Why a drop was judged to need a build — surfaced to analytics and `onBuildRequired`. */
|
|
3
|
+
export type BuildReason = 'netlify-toml-build' | 'package-json-build-script' | 'framework-dependency' | 'functions-directory';
|
|
4
|
+
export interface BuildDetection {
|
|
5
|
+
/** True when the dropped project needs a build step and can't be served as static files. */
|
|
6
|
+
buildRequired: boolean;
|
|
7
|
+
/** The signal that decided it (first match wins), or null for a plain static drop. */
|
|
8
|
+
reason: BuildReason | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decide whether a dropped project needs a build. Runs on the already-read,
|
|
12
|
+
* root-normalized `ProcessedFile[]` (a dropped `.zip` is expanded and the common
|
|
13
|
+
* root folder stripped upstream in `readFiles`), so build config sits at the
|
|
14
|
+
* deploy root, e.g. `/netlify.toml`, `/package.json`.
|
|
15
|
+
*
|
|
16
|
+
* The anonymous drop API only performs static deploys, so a build-required
|
|
17
|
+
* project can't go through it — the consumer redirects such drops to the
|
|
18
|
+
* authenticated Drop page instead. This heuristic is deliberately lightweight
|
|
19
|
+
* (no framework-detection dependency): it reads the two config files and looks
|
|
20
|
+
* for the zero-config functions directories. A plain static site (HTML/CSS/JS,
|
|
21
|
+
* no build config) returns `{ buildRequired: false }`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectBuild(files: ProcessedFile[]): BuildDetection;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const u = /* @__PURE__ */ new Set([
|
|
2
|
+
"astro",
|
|
3
|
+
"next",
|
|
4
|
+
"nuxt",
|
|
5
|
+
"gatsby",
|
|
6
|
+
"vite",
|
|
7
|
+
"react-scripts",
|
|
8
|
+
"parcel",
|
|
9
|
+
"expo",
|
|
10
|
+
"gridsome",
|
|
11
|
+
"vuepress",
|
|
12
|
+
"hexo",
|
|
13
|
+
"@remix-run/dev",
|
|
14
|
+
"@sveltejs/kit",
|
|
15
|
+
"@angular/cli",
|
|
16
|
+
"@vue/cli-service",
|
|
17
|
+
"@11ty/eleventy",
|
|
18
|
+
"@docusaurus/core",
|
|
19
|
+
"@builder.io/qwik",
|
|
20
|
+
"@analogjs/platform",
|
|
21
|
+
"@solidjs/start"
|
|
22
|
+
]), c = /^\s*\[build(\.[\w-]+)?\]/m, d = ["/netlify/functions/", "/netlify/edge-functions/"], l = new TextDecoder();
|
|
23
|
+
function i(e, n) {
|
|
24
|
+
return e.find((t) => t.path === n);
|
|
25
|
+
}
|
|
26
|
+
function o(e) {
|
|
27
|
+
return l.decode(e.content);
|
|
28
|
+
}
|
|
29
|
+
function a(e) {
|
|
30
|
+
const n = e.scripts;
|
|
31
|
+
if (!n || typeof n != "object") return !1;
|
|
32
|
+
const t = n.build;
|
|
33
|
+
return typeof t == "string" && t.trim().length > 0;
|
|
34
|
+
}
|
|
35
|
+
function f(e) {
|
|
36
|
+
for (const n of ["dependencies", "devDependencies"]) {
|
|
37
|
+
const t = e[n];
|
|
38
|
+
if (!(!t || typeof t != "object")) {
|
|
39
|
+
for (const r of Object.keys(t))
|
|
40
|
+
if (u.has(r)) return !0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return !1;
|
|
44
|
+
}
|
|
45
|
+
function p(e) {
|
|
46
|
+
const n = i(e, "/netlify.toml");
|
|
47
|
+
if (n && c.test(o(n)))
|
|
48
|
+
return { buildRequired: !0, reason: "netlify-toml-build" };
|
|
49
|
+
const t = i(e, "/package.json");
|
|
50
|
+
if (t) {
|
|
51
|
+
let r = null;
|
|
52
|
+
try {
|
|
53
|
+
r = JSON.parse(o(t));
|
|
54
|
+
} catch {
|
|
55
|
+
r = null;
|
|
56
|
+
}
|
|
57
|
+
if (r) {
|
|
58
|
+
if (a(r))
|
|
59
|
+
return { buildRequired: !0, reason: "package-json-build-script" };
|
|
60
|
+
if (f(r))
|
|
61
|
+
return { buildRequired: !0, reason: "framework-dependency" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return e.some((r) => d.some((s) => r.path.startsWith(s))) ? { buildRequired: !0, reason: "functions-directory" } : { buildRequired: !1, reason: null };
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
p as detectBuild
|
|
68
|
+
};
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { getDigest as o } from "./digest.js";
|
|
2
2
|
import { getSingleNonIndexHtmlFileName as n, hasMultipleHtmlFilesWithoutIndex as l, readDataTransfer as i, readFileList as m, renameSingleNonIndexHtmlToIndex as p } from "./readFiles.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { detectBuild as x } from "./detectBuild.js";
|
|
4
|
+
import { AnonymousDropClient as s } from "./client.js";
|
|
5
|
+
import { deployFiles as u } from "./deploy.js";
|
|
6
|
+
import { UnsupportedDropError as D, humanizeDropError as F } from "./errors.js";
|
|
6
7
|
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
s as AnonymousDropClient,
|
|
9
|
+
D as UnsupportedDropError,
|
|
10
|
+
u as deployFiles,
|
|
11
|
+
x as detectBuild,
|
|
10
12
|
o as getDigest,
|
|
11
13
|
n as getSingleNonIndexHtmlFileName,
|
|
12
14
|
l as hasMultipleHtmlFilesWithoutIndex,
|
|
13
|
-
|
|
15
|
+
F as humanizeDropError,
|
|
14
16
|
i as readDataTransfer,
|
|
15
17
|
m as readFileList,
|
|
16
18
|
p as renameSingleNonIndexHtmlToIndex
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as DropZone } from './DropZone';
|
|
2
|
-
export { AnonymousDropClient, deployFiles, readDataTransfer, readFileList, } from './drop-core';
|
|
3
|
-
export type { DropClient, DropResponse, Digest, DeployResult, DeployProgress, DeployPhase, ProcessedFile, } from './drop-core';
|
|
2
|
+
export { AnonymousDropClient, deployFiles, detectBuild, readDataTransfer, readFileList, } from './drop-core';
|
|
3
|
+
export type { BuildDetection, BuildReason, DropClient, DropResponse, Digest, DeployResult, DeployProgress, DeployPhase, ProcessedFile, } from './drop-core';
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { default as
|
|
2
|
-
import { readDataTransfer as
|
|
3
|
-
import {
|
|
1
|
+
import { default as r } from "./DropZone.js";
|
|
2
|
+
import { readDataTransfer as p, readFileList as a } from "./drop-core/readFiles.js";
|
|
3
|
+
import { detectBuild as d } from "./drop-core/detectBuild.js";
|
|
4
|
+
import { AnonymousDropClient as m } from "./drop-core/client.js";
|
|
4
5
|
import { deployFiles as n } from "./drop-core/deploy.js";
|
|
5
6
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
m as AnonymousDropClient,
|
|
8
|
+
r as DropZone,
|
|
8
9
|
n as deployFiles,
|
|
9
|
-
|
|
10
|
-
p as
|
|
10
|
+
d as detectBuild,
|
|
11
|
+
p as readDataTransfer,
|
|
12
|
+
a as readFileList
|
|
11
13
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DropClient, ProcessedFile } from './drop-core';
|
|
1
|
+
import { BuildDetection, DropClient, ProcessedFile } from './drop-core';
|
|
2
2
|
/** How the files reached the zone — carried through to analytics. */
|
|
3
3
|
export type DeploySource = 'drag' | 'browse';
|
|
4
4
|
/** The live site handed back once a deploy succeeds. */
|
|
@@ -30,6 +30,7 @@ export type DeployStatus = {
|
|
|
30
30
|
kind: 'processing';
|
|
31
31
|
} | {
|
|
32
32
|
kind: 'redirecting';
|
|
33
|
+
target: 'claim' | 'signup';
|
|
33
34
|
} | {
|
|
34
35
|
kind: 'error';
|
|
35
36
|
message: string;
|
|
@@ -40,6 +41,8 @@ interface UseDropDeployOptions {
|
|
|
40
41
|
apiBase?: string;
|
|
41
42
|
client?: DropClient;
|
|
42
43
|
claimUrl: (siteName: string) => string;
|
|
44
|
+
buildSignupUrl: string;
|
|
45
|
+
onBuildRequired?: (info: BuildDetection) => void;
|
|
43
46
|
onDeploy?: (site: DeployedSite) => void;
|
|
44
47
|
onError?: (error: DeployError) => void;
|
|
45
48
|
track: TrackFn;
|
|
@@ -48,7 +51,7 @@ interface UseDropDeployOptions {
|
|
|
48
51
|
* Owns the deploy state machine and its side effects (upload, local persistence,
|
|
49
52
|
* analytics, redirect), keeping DropZone a thin view over the returned `status`.
|
|
50
53
|
*/
|
|
51
|
-
export declare function useDropDeploy({ apiBase, client, claimUrl, onDeploy, onError, track, }: UseDropDeployOptions): {
|
|
54
|
+
export declare function useDropDeploy({ apiBase, client, claimUrl, buildSignupUrl, onBuildRequired, onDeploy, onError, track, }: UseDropDeployOptions): {
|
|
52
55
|
status: DeployStatus;
|
|
53
56
|
warning: string | null;
|
|
54
57
|
busy: boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { d as
|
|
2
|
-
import { renameSingleNonIndexHtmlToIndex as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { d as h, A as F } from "../../../chunks/hooks.module.DRcxknIz.js";
|
|
2
|
+
import { renameSingleNonIndexHtmlToIndex as H, getSingleNonIndexHtmlFileName as W, hasMultipleHtmlFilesWithoutIndex as $ } from "./drop-core/readFiles.js";
|
|
3
|
+
import { detectBuild as v } from "./drop-core/detectBuild.js";
|
|
4
|
+
import { AnonymousDropClient as A } from "./drop-core/client.js";
|
|
5
|
+
import { deployFiles as C } from "./drop-core/deploy.js";
|
|
6
|
+
import { humanizeDropError as T } from "./drop-core/errors.js";
|
|
7
|
+
function q(n) {
|
|
7
8
|
switch (n.phase) {
|
|
8
9
|
case "creating":
|
|
9
10
|
return { kind: "creating" };
|
|
@@ -14,59 +15,75 @@ function v(n) {
|
|
|
14
15
|
return { kind: "processing" };
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
-
const
|
|
19
|
-
return
|
|
18
|
+
function M(n) {
|
|
19
|
+
const l = W(n);
|
|
20
|
+
return l ? `No index.html found — "${l}" won’t be served at the site root.` : $(n) ? "No index.html found — your site may not have a landing page." : null;
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
+
function K({
|
|
22
23
|
apiBase: n,
|
|
23
|
-
client:
|
|
24
|
-
claimUrl:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
client: l,
|
|
25
|
+
claimUrl: _,
|
|
26
|
+
buildSignupUrl: k,
|
|
27
|
+
onBuildRequired: g,
|
|
28
|
+
onDeploy: a,
|
|
29
|
+
onError: u,
|
|
30
|
+
track: s
|
|
28
31
|
}) {
|
|
29
|
-
const [c, i] =
|
|
30
|
-
async function
|
|
32
|
+
const [c, i] = h({ kind: "idle" }), [y, m] = h(null), w = F(l ?? new A(n)).current, N = c.kind !== "idle" && c.kind !== "error";
|
|
33
|
+
async function b(I, o) {
|
|
31
34
|
m(null), i({ kind: "reading" });
|
|
32
|
-
const
|
|
33
|
-
i({ kind: "error", message: e }),
|
|
34
|
-
},
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const f = (e, t, d) => {
|
|
36
|
+
i({ kind: "error", message: e }), s("dropzone_deploy_failed", { method: o, reason: t, ...d }), u == null || u({ message: e, reason: t });
|
|
37
|
+
}, S = (e, t) => {
|
|
38
|
+
const d = e instanceof Error ? e.message : String(e);
|
|
39
|
+
f(T(e), d, t);
|
|
37
40
|
};
|
|
38
41
|
try {
|
|
39
|
-
const e =
|
|
42
|
+
const e = H(await I());
|
|
40
43
|
if (!e.length) {
|
|
41
|
-
|
|
44
|
+
f("No files found to deploy.", "No files found to deploy.", { file_count: 0 });
|
|
42
45
|
return;
|
|
43
46
|
}
|
|
44
|
-
|
|
47
|
+
o === "drag" && s("dropzone_files_dropped", { method: o, file_count: e.length });
|
|
48
|
+
const t = v(e);
|
|
49
|
+
if (t.buildRequired) {
|
|
50
|
+
if (s("dropzone_build_required", {
|
|
51
|
+
method: o,
|
|
52
|
+
reason: t.reason,
|
|
53
|
+
file_count: e.length
|
|
54
|
+
}), g) {
|
|
55
|
+
i({ kind: "idle" }), g(t);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
i({ kind: "redirecting", target: "signup" }), window.location.assign(k);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
m(M(e));
|
|
45
62
|
const {
|
|
46
|
-
url:
|
|
47
|
-
token:
|
|
48
|
-
deploy:
|
|
49
|
-
} = await
|
|
50
|
-
onProgress: (
|
|
63
|
+
url: d,
|
|
64
|
+
token: p,
|
|
65
|
+
deploy: r
|
|
66
|
+
} = await C(w, e, {
|
|
67
|
+
onProgress: (z) => i(q(z))
|
|
51
68
|
});
|
|
52
69
|
try {
|
|
53
|
-
localStorage.setItem("drop-token",
|
|
70
|
+
localStorage.setItem("drop-token", p), localStorage.setItem("dropSiteName", r.subdomain);
|
|
54
71
|
} catch {
|
|
55
72
|
}
|
|
56
|
-
i({ kind: "redirecting" }),
|
|
57
|
-
method:
|
|
58
|
-
site_name:
|
|
73
|
+
i({ kind: "redirecting", target: "claim" }), s("dropzone_deploy_succeeded", {
|
|
74
|
+
method: o,
|
|
75
|
+
site_name: r.subdomain,
|
|
59
76
|
file_count: e.length
|
|
60
|
-
}),
|
|
77
|
+
}), a == null || a({ url: d, token: p, siteName: r.subdomain, siteId: r.id }), window.location.assign(`${_(r.subdomain)}#drop_token=${p}`);
|
|
61
78
|
} catch (e) {
|
|
62
|
-
|
|
79
|
+
S(e);
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
|
-
function
|
|
82
|
+
function x() {
|
|
66
83
|
m(null), i({ kind: "idle" });
|
|
67
84
|
}
|
|
68
|
-
return { status: c, warning:
|
|
85
|
+
return { status: c, warning: y, busy: N, deploy: b, reset: x };
|
|
69
86
|
}
|
|
70
87
|
export {
|
|
71
|
-
|
|
88
|
+
K as useDropDeploy
|
|
72
89
|
};
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { default as
|
|
1
|
+
import { default as p } from "./KeyboardKey/KeyboardKey.js";
|
|
2
2
|
import { default as d } from "./Modal/Modal.js";
|
|
3
|
-
import { default as
|
|
4
|
-
import { default as
|
|
5
|
-
import { default as
|
|
6
|
-
import { default as
|
|
7
|
-
import { A as
|
|
3
|
+
import { default as s } from "./Icon/index.js";
|
|
4
|
+
import { default as u } from "./SearchModal/components/SearchLink/SearchLink.js";
|
|
5
|
+
import { default as c } from "./SearchModal/components/SearchHeading/SearchHeading.js";
|
|
6
|
+
import { default as n } from "./SearchModal/components/SearchToggle/SearchToggle.js";
|
|
7
|
+
import { A as g } from "../../chunks/AskNetlify.bx0blyGV.js";
|
|
8
8
|
import "preact";
|
|
9
9
|
import "../../chunks/hooks.module.DRcxknIz.js";
|
|
10
|
-
import { default as
|
|
11
|
-
import { default as
|
|
10
|
+
import { default as k } from "./SearchModal/components/SearchModal/SearchModal.js";
|
|
11
|
+
import { default as K } from "./DropZone/DropZone.js";
|
|
12
12
|
import "./DropZone/drop-core/readFiles.js";
|
|
13
|
+
import "./DropZone/drop-core/detectBuild.js";
|
|
13
14
|
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
g as AskNetlify,
|
|
16
|
+
K as DropZone,
|
|
17
|
+
s as Icon,
|
|
18
|
+
p as KeyboardKey,
|
|
18
19
|
d as Modal,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
c as SearchHeading,
|
|
21
|
+
u as SearchLink,
|
|
22
|
+
k as SearchModal,
|
|
23
|
+
n as SearchToggle
|
|
23
24
|
};
|
package/dist/utils/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
2
|
import type { ComponentChildren } from 'preact';
|
|
3
|
-
import { readDataTransfer, readFileList, type DropClient } from './drop-core';
|
|
3
|
+
import { readDataTransfer, readFileList, type BuildDetection, type DropClient } from './drop-core';
|
|
4
4
|
import {
|
|
5
5
|
useDropDeploy,
|
|
6
6
|
type DeployedSite,
|
|
@@ -16,6 +16,13 @@ const DRAG_INACTIVE_MS = 120;
|
|
|
16
16
|
|
|
17
17
|
const defaultClaimUrl = (siteName: string) => `https://app.netlify.com/drop/${siteName}`;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Where a build-required drop is sent by default. Mirrors the app's own Drop
|
|
21
|
+
* page: signup with `next=/drop`, so after signup + email verification the user
|
|
22
|
+
* lands back on the authenticated Drop page and can build there.
|
|
23
|
+
*/
|
|
24
|
+
const defaultBuildSignupUrl = 'https://app.netlify.com/signup?next=/drop&utm_campaign=drop';
|
|
25
|
+
|
|
19
26
|
interface Props {
|
|
20
27
|
/** Content the drop layer sits on top of. The zone is invisible until you drag over it. */
|
|
21
28
|
children?: ComponentChildren;
|
|
@@ -25,6 +32,21 @@ interface Props {
|
|
|
25
32
|
client?: DropClient;
|
|
26
33
|
/** Build the "claim this site" URL from the assigned site name (subdomain). */
|
|
27
34
|
claimUrl?: (siteName: string) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Where to send a build-required drop (framework source, build command,
|
|
37
|
+
* functions) when `onBuildRequired` isn't set. Such projects can't deploy
|
|
38
|
+
* through the anonymous static drop API, so by default the user is redirected
|
|
39
|
+
* here — the app signup page with `next=/drop` — to build on the
|
|
40
|
+
* authenticated Drop page. Override to point at your own destination.
|
|
41
|
+
*/
|
|
42
|
+
buildSignupUrl?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Called when a build-required drop is detected, instead of the default
|
|
45
|
+
* `buildSignupUrl` redirect. Use it to own the hand-off yourself (custom
|
|
46
|
+
* routing, persisting the drop, gating on auth). When set, the component does
|
|
47
|
+
* not redirect. The static drop path is unaffected either way.
|
|
48
|
+
*/
|
|
49
|
+
onBuildRequired?: (info: BuildDetection) => void;
|
|
28
50
|
/** Called once the deploy is live. */
|
|
29
51
|
onDeploy?: (result: DeployedSite) => void;
|
|
30
52
|
/**
|
|
@@ -48,6 +70,7 @@ interface Props {
|
|
|
48
70
|
* Emitted events (all include `dropzone_id` when `analyticsId` is set):
|
|
49
71
|
* - `dropzone_files_dropped` — files dropped via drag ({ method: 'drag', file_count })
|
|
50
72
|
* - `dropzone_browse_opened` — file picker opened ({ method: 'keyboard' | 'click' })
|
|
73
|
+
* - `dropzone_build_required` — drop needs a build; handed off to signup ({ method, reason, file_count })
|
|
51
74
|
* - `dropzone_deploy_succeeded` — deploy went live ({ method, site_name, file_count })
|
|
52
75
|
* - `dropzone_deploy_failed` — deploy errored out ({ method, reason, file_count? })
|
|
53
76
|
*/
|
|
@@ -66,6 +89,8 @@ export default function DropZone({
|
|
|
66
89
|
apiBase,
|
|
67
90
|
client,
|
|
68
91
|
claimUrl = defaultClaimUrl,
|
|
92
|
+
buildSignupUrl = defaultBuildSignupUrl,
|
|
93
|
+
onBuildRequired,
|
|
69
94
|
onDeploy,
|
|
70
95
|
onError,
|
|
71
96
|
showStatus = true,
|
|
@@ -82,6 +107,8 @@ export default function DropZone({
|
|
|
82
107
|
apiBase,
|
|
83
108
|
client,
|
|
84
109
|
claimUrl,
|
|
110
|
+
buildSignupUrl,
|
|
111
|
+
onBuildRequired,
|
|
85
112
|
onDeploy,
|
|
86
113
|
onError,
|
|
87
114
|
track,
|
|
@@ -227,7 +254,15 @@ function StatusView({
|
|
|
227
254
|
case 'processing':
|
|
228
255
|
return <Spinner label="Processing — almost live…" />;
|
|
229
256
|
case 'redirecting':
|
|
230
|
-
return
|
|
257
|
+
return (
|
|
258
|
+
<Spinner
|
|
259
|
+
label={
|
|
260
|
+
status.target === 'signup'
|
|
261
|
+
? 'This project needs a build — taking you to Netlify…'
|
|
262
|
+
: 'Site’s live — taking you to claim it…'
|
|
263
|
+
}
|
|
264
|
+
/>
|
|
265
|
+
);
|
|
231
266
|
|
|
232
267
|
case 'uploading': {
|
|
233
268
|
const pct = status.total ? Math.round((status.uploaded / status.total) * 100) : 0;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ProcessedFile } from './types';
|
|
2
|
+
|
|
3
|
+
/** Why a drop was judged to need a build — surfaced to analytics and `onBuildRequired`. */
|
|
4
|
+
export type BuildReason =
|
|
5
|
+
| 'netlify-toml-build'
|
|
6
|
+
| 'package-json-build-script'
|
|
7
|
+
| 'framework-dependency'
|
|
8
|
+
| 'functions-directory';
|
|
9
|
+
|
|
10
|
+
export interface BuildDetection {
|
|
11
|
+
/** True when the dropped project needs a build step and can't be served as static files. */
|
|
12
|
+
buildRequired: boolean;
|
|
13
|
+
/** The signal that decided it (first match wins), or null for a plain static drop. */
|
|
14
|
+
reason: BuildReason | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Dependencies whose presence implies a build even without an explicit `build`
|
|
19
|
+
* script (e.g. a framework wired through its own CLI). Not exhaustive — the
|
|
20
|
+
* `build` script check below catches the common case; this is the backstop.
|
|
21
|
+
*/
|
|
22
|
+
const FRAMEWORK_DEPENDENCIES = new Set([
|
|
23
|
+
'astro',
|
|
24
|
+
'next',
|
|
25
|
+
'nuxt',
|
|
26
|
+
'gatsby',
|
|
27
|
+
'vite',
|
|
28
|
+
'react-scripts',
|
|
29
|
+
'parcel',
|
|
30
|
+
'expo',
|
|
31
|
+
'gridsome',
|
|
32
|
+
'vuepress',
|
|
33
|
+
'hexo',
|
|
34
|
+
'@remix-run/dev',
|
|
35
|
+
'@sveltejs/kit',
|
|
36
|
+
'@angular/cli',
|
|
37
|
+
'@vue/cli-service',
|
|
38
|
+
'@11ty/eleventy',
|
|
39
|
+
'@docusaurus/core',
|
|
40
|
+
'@builder.io/qwik',
|
|
41
|
+
'@analogjs/platform',
|
|
42
|
+
'@solidjs/start',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A `netlify.toml` build config: a bare `[build]` table or any of its
|
|
47
|
+
* sub-tables (`[build.environment]`, `[build.processing]`, …). Anchored to the
|
|
48
|
+
* line start so a `[build]` mention inside a string/comment doesn't count.
|
|
49
|
+
*/
|
|
50
|
+
const NETLIFY_TOML_BUILD_SECTION = /^\s*\[build(\.[\w-]+)?\]/m;
|
|
51
|
+
|
|
52
|
+
/** Zero-config functions directories — their presence alone requires bundling. */
|
|
53
|
+
const FUNCTIONS_DIR_PREFIXES = ['/netlify/functions/', '/netlify/edge-functions/'];
|
|
54
|
+
|
|
55
|
+
const decoder = new TextDecoder();
|
|
56
|
+
|
|
57
|
+
/** Find a file at the deploy root by its normalized path (leading slash). */
|
|
58
|
+
function rootFile(files: ProcessedFile[], path: string): ProcessedFile | undefined {
|
|
59
|
+
return files.find(f => f.path === path);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function text(file: ProcessedFile): string {
|
|
63
|
+
return decoder.decode(file.content);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** True if `package.json` declares a non-empty `build` script. */
|
|
67
|
+
function hasBuildScript(pkg: Record<string, unknown>): boolean {
|
|
68
|
+
const scripts = pkg.scripts;
|
|
69
|
+
if (!scripts || typeof scripts !== 'object') return false;
|
|
70
|
+
const build = (scripts as Record<string, unknown>).build;
|
|
71
|
+
return typeof build === 'string' && build.trim().length > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** True if any dependency (prod or dev) is a known build-requiring framework. */
|
|
75
|
+
function hasFrameworkDependency(pkg: Record<string, unknown>): boolean {
|
|
76
|
+
for (const key of ['dependencies', 'devDependencies'] as const) {
|
|
77
|
+
const deps = pkg[key];
|
|
78
|
+
if (!deps || typeof deps !== 'object') continue;
|
|
79
|
+
for (const name of Object.keys(deps)) {
|
|
80
|
+
if (FRAMEWORK_DEPENDENCIES.has(name)) return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Decide whether a dropped project needs a build. Runs on the already-read,
|
|
88
|
+
* root-normalized `ProcessedFile[]` (a dropped `.zip` is expanded and the common
|
|
89
|
+
* root folder stripped upstream in `readFiles`), so build config sits at the
|
|
90
|
+
* deploy root, e.g. `/netlify.toml`, `/package.json`.
|
|
91
|
+
*
|
|
92
|
+
* The anonymous drop API only performs static deploys, so a build-required
|
|
93
|
+
* project can't go through it — the consumer redirects such drops to the
|
|
94
|
+
* authenticated Drop page instead. This heuristic is deliberately lightweight
|
|
95
|
+
* (no framework-detection dependency): it reads the two config files and looks
|
|
96
|
+
* for the zero-config functions directories. A plain static site (HTML/CSS/JS,
|
|
97
|
+
* no build config) returns `{ buildRequired: false }`.
|
|
98
|
+
*/
|
|
99
|
+
export function detectBuild(files: ProcessedFile[]): BuildDetection {
|
|
100
|
+
const toml = rootFile(files, '/netlify.toml');
|
|
101
|
+
if (toml && NETLIFY_TOML_BUILD_SECTION.test(text(toml))) {
|
|
102
|
+
return { buildRequired: true, reason: 'netlify-toml-build' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pkg = rootFile(files, '/package.json');
|
|
106
|
+
if (pkg) {
|
|
107
|
+
let parsed: Record<string, unknown> | null = null;
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(text(pkg)) as Record<string, unknown>;
|
|
110
|
+
} catch {
|
|
111
|
+
// A malformed package.json isn't a reliable build signal on its own —
|
|
112
|
+
// fall through and let the other heuristics decide.
|
|
113
|
+
parsed = null;
|
|
114
|
+
}
|
|
115
|
+
if (parsed) {
|
|
116
|
+
if (hasBuildScript(parsed)) {
|
|
117
|
+
return { buildRequired: true, reason: 'package-json-build-script' };
|
|
118
|
+
}
|
|
119
|
+
if (hasFrameworkDependency(parsed)) {
|
|
120
|
+
return { buildRequired: true, reason: 'framework-dependency' };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (files.some(f => FUNCTIONS_DIR_PREFIXES.some(prefix => f.path.startsWith(prefix)))) {
|
|
126
|
+
return { buildRequired: true, reason: 'functions-directory' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { buildRequired: false, reason: null };
|
|
130
|
+
}
|
|
@@ -2,10 +2,13 @@ export { default as DropZone } from './DropZone';
|
|
|
2
2
|
export {
|
|
3
3
|
AnonymousDropClient,
|
|
4
4
|
deployFiles,
|
|
5
|
+
detectBuild,
|
|
5
6
|
readDataTransfer,
|
|
6
7
|
readFileList,
|
|
7
8
|
} from './drop-core';
|
|
8
9
|
export type {
|
|
10
|
+
BuildDetection,
|
|
11
|
+
BuildReason,
|
|
9
12
|
DropClient,
|
|
10
13
|
DropResponse,
|
|
11
14
|
Digest,
|
|
@@ -2,10 +2,12 @@ import { useRef, useState } from 'preact/hooks';
|
|
|
2
2
|
import {
|
|
3
3
|
AnonymousDropClient,
|
|
4
4
|
deployFiles,
|
|
5
|
+
detectBuild,
|
|
5
6
|
getSingleNonIndexHtmlFileName,
|
|
6
7
|
hasMultipleHtmlFilesWithoutIndex,
|
|
7
8
|
humanizeDropError,
|
|
8
9
|
renameSingleNonIndexHtmlToIndex,
|
|
10
|
+
type BuildDetection,
|
|
9
11
|
type DeployProgress,
|
|
10
12
|
type DropClient,
|
|
11
13
|
type ProcessedFile,
|
|
@@ -37,7 +39,9 @@ export type DeployStatus =
|
|
|
37
39
|
| { kind: 'creating' }
|
|
38
40
|
| { kind: 'uploading'; uploaded: number; total: number }
|
|
39
41
|
| { kind: 'processing' }
|
|
40
|
-
|
|
42
|
+
// `claim` follows a static deploy (hand off to the claim page); `signup`
|
|
43
|
+
// follows a build-required drop (hand off to the authenticated Drop page).
|
|
44
|
+
| { kind: 'redirecting'; target: 'claim' | 'signup' }
|
|
41
45
|
| { kind: 'error'; message: string };
|
|
42
46
|
|
|
43
47
|
/** Provider-agnostic analytics sink. */
|
|
@@ -47,6 +51,8 @@ interface UseDropDeployOptions {
|
|
|
47
51
|
apiBase?: string;
|
|
48
52
|
client?: DropClient;
|
|
49
53
|
claimUrl: (siteName: string) => string;
|
|
54
|
+
buildSignupUrl: string;
|
|
55
|
+
onBuildRequired?: (info: BuildDetection) => void;
|
|
50
56
|
onDeploy?: (site: DeployedSite) => void;
|
|
51
57
|
onError?: (error: DeployError) => void;
|
|
52
58
|
track: TrackFn;
|
|
@@ -85,6 +91,8 @@ export function useDropDeploy({
|
|
|
85
91
|
apiBase,
|
|
86
92
|
client,
|
|
87
93
|
claimUrl,
|
|
94
|
+
buildSignupUrl,
|
|
95
|
+
onBuildRequired,
|
|
88
96
|
onDeploy,
|
|
89
97
|
onError,
|
|
90
98
|
track,
|
|
@@ -136,6 +144,30 @@ export function useDropDeploy({
|
|
|
136
144
|
track('dropzone_files_dropped', { method: source, file_count: files.length });
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
// Build-required projects can't deploy through the anonymous drop API —
|
|
148
|
+
// it only performs static deploys, so building one this way yields a
|
|
149
|
+
// broken site. Detect them up front and hand off to the authenticated
|
|
150
|
+
// Drop page (via signup) instead. Consumers can override the hand-off
|
|
151
|
+
// with `onBuildRequired`.
|
|
152
|
+
const build = detectBuild(files);
|
|
153
|
+
if (build.buildRequired) {
|
|
154
|
+
track('dropzone_build_required', {
|
|
155
|
+
method: source,
|
|
156
|
+
reason: build.reason,
|
|
157
|
+
file_count: files.length,
|
|
158
|
+
});
|
|
159
|
+
if (onBuildRequired) {
|
|
160
|
+
// The consumer owns the hand-off now; drop back to idle so the zone
|
|
161
|
+
// stays usable rather than sitting on a spinner.
|
|
162
|
+
setStatus({ kind: 'idle' });
|
|
163
|
+
onBuildRequired(build);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
setStatus({ kind: 'redirecting', target: 'signup' });
|
|
167
|
+
window.location.assign(buildSignupUrl);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
139
171
|
setWarning(indexHtmlWarning(files));
|
|
140
172
|
|
|
141
173
|
const {
|
|
@@ -157,7 +189,7 @@ export function useDropDeploy({
|
|
|
157
189
|
/* private mode / storage disabled — non-fatal */
|
|
158
190
|
}
|
|
159
191
|
|
|
160
|
-
setStatus({ kind: 'redirecting' });
|
|
192
|
+
setStatus({ kind: 'redirecting', target: 'claim' });
|
|
161
193
|
// Fired before the redirect below. Note: the navigation can cut a
|
|
162
194
|
// fire-and-forget analytics request short — see the docs for delivery
|
|
163
195
|
// hardening (sendBeacon / deferring the redirect).
|
package/packages/utils/index.ts
CHANGED