@netlify/spark-ui 1.31.0-alpha.5 → 1.31.0-alpha.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.
- package/README.md +3 -4
- package/dist/components/preact/DropZone/DropZone.d.ts +23 -97
- package/dist/components/preact/DropZone/useDropDeploy.js +110 -105
- package/dist/drop-core/analytics.d.ts +4 -15
- package/dist/drop-core/authedApi.d.ts +9 -22
- package/dist/drop-core/authedDropClient.d.ts +13 -55
- package/dist/drop-core/authedDropClient.js +5 -18
- package/dist/drop-core/buildClient.d.ts +19 -53
- package/dist/drop-core/buildClient.js +27 -40
- package/dist/drop-core/buildStash.d.ts +6 -20
- package/dist/drop-core/buildStash.js +36 -24
- package/dist/drop-core/client.d.ts +1 -1
- package/dist/drop-core/constants.d.ts +1 -21
- package/dist/drop-core/deploy.d.ts +5 -17
- package/dist/drop-core/detectBuild.d.ts +8 -26
- package/dist/drop-core/errors.d.ts +8 -27
- package/dist/drop-core/fileTypes.d.ts +4 -14
- package/dist/drop-core/readFiles.d.ts +5 -11
- package/dist/drop-core/types.d.ts +2 -4
- package/dist/drop-core/zip.d.ts +7 -30
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.tsx +27 -121
- package/packages/components/preact/DropZone/useDropDeploy.test.ts +12 -0
- package/packages/components/preact/DropZone/useDropDeploy.ts +39 -60
- package/packages/drop-core/README.md +311 -39
- package/packages/drop-core/analytics.ts +4 -15
- package/packages/drop-core/authedApi.ts +9 -22
- package/packages/drop-core/authedDropClient.ts +13 -55
- package/packages/drop-core/buildClient.test.ts +19 -4
- package/packages/drop-core/buildClient.ts +21 -63
- package/packages/drop-core/buildStash.test.ts +19 -0
- package/packages/drop-core/buildStash.ts +38 -29
- package/packages/drop-core/client.ts +6 -10
- package/packages/drop-core/constants.ts +8 -22
- package/packages/drop-core/deploy.ts +6 -22
- package/packages/drop-core/detectBuild.ts +15 -50
- package/packages/drop-core/errors.test.ts +3 -1
- package/packages/drop-core/errors.ts +9 -30
- package/packages/drop-core/fileTypes.ts +5 -19
- package/packages/drop-core/readFiles.ts +5 -12
- package/packages/drop-core/types.ts +2 -4
- package/packages/drop-core/zip.ts +7 -30
|
@@ -2,23 +2,15 @@ import { ProcessedFile } from './types';
|
|
|
2
2
|
/** Why a drop was judged to need a build — surfaced to analytics and `onBuildRequired`. */
|
|
3
3
|
export type BuildReason = 'netlify-toml-build' | 'package-json-build-script' | 'framework-dependency' | 'functions-directory';
|
|
4
4
|
export interface BuildDetection {
|
|
5
|
-
/**
|
|
5
|
+
/** The drop can't be served as static files. */
|
|
6
6
|
buildRequired: boolean;
|
|
7
7
|
/** The signal that decided it (first match wins), or null for a plain static drop. */
|
|
8
8
|
reason: BuildReason | null;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* root
|
|
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 }`.
|
|
11
|
+
* Does this drop need a build? Deliberately lightweight (two config files +
|
|
12
|
+
* the functions dirs; no framework-detection dependency). Input is
|
|
13
|
+
* root-normalized by `readFiles`, so config sits at `/netlify.toml` etc.
|
|
22
14
|
*/
|
|
23
15
|
export declare function detectBuild(files: ProcessedFile[]): BuildDetection;
|
|
24
16
|
/** Build settings confident enough to write into a `netlify.toml` for buildbot. */
|
|
@@ -31,19 +23,9 @@ export interface InferredBuildSettings {
|
|
|
31
23
|
framework: string;
|
|
32
24
|
}
|
|
33
25
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* known — a `build` script (which gives the command) and a recognised framework
|
|
39
|
-
* with a fixed default publish directory (which gives the output). Anything else
|
|
40
|
-
* returns `null`, leaving the zip without a synthesised `[build]` table so
|
|
41
|
-
* buildbot's own framework detection resolves the settings instead. That matters
|
|
42
|
-
* because a `[build]` table with a command but no `publish` makes buildbot
|
|
43
|
-
* publish the deploy root — shipping the project's *source* rather than its
|
|
44
|
-
* built output, which is worse than not guessing at all.
|
|
45
|
-
*
|
|
46
|
-
* Returns `null` too when the drop already declares a `[build]` table: the
|
|
47
|
-
* author's own config always wins.
|
|
26
|
+
* Build settings for the synthesised netlify.toml — only when BOTH halves are
|
|
27
|
+
* known (build script → command, known framework → publish). Guessing publish
|
|
28
|
+
* wrong ships source instead of output, so `null` beats a bad guess. An
|
|
29
|
+
* author-declared [build] table always wins (also `null`).
|
|
48
30
|
*/
|
|
49
31
|
export declare function inferBuildSettings(files: ProcessedFile[]): InferredBuildSettings | null;
|
|
@@ -1,33 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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).
|
|
2
|
+
* A single loose file that can't stand up a site — only a zip, folder,
|
|
3
|
+
* multiple files, or a lone HTML page may deploy (matches react-ui).
|
|
7
4
|
*/
|
|
8
5
|
export declare class UnsupportedDropError extends Error {
|
|
9
6
|
readonly code = "unsupported-drop";
|
|
10
7
|
constructor();
|
|
11
8
|
}
|
|
12
9
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* session-cookie site create was refused, so nothing was created. For static
|
|
16
|
-
* drops the flow falls back to the anonymous drop + claim; builds have no
|
|
17
|
-
* anonymous equivalent, so the consumer's only recourse is to send the visitor
|
|
18
|
-
* through login/signup and have them drop again.
|
|
10
|
+
* The pre-mutation "no session" signal: nothing was created yet, so static
|
|
11
|
+
* drops may fall back to the anonymous flow and build drops stash + hand off.
|
|
19
12
|
*/
|
|
20
13
|
export declare class NotAuthenticatedError extends Error {
|
|
21
14
|
readonly code = "not-authenticated";
|
|
22
15
|
constructor(message?: string);
|
|
23
16
|
}
|
|
24
17
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* compiled dist (`./preact`, `./drop-core`), so two copies of the class can
|
|
28
|
-
* coexist in one app and defeat `instanceof` — the `code` brand is the
|
|
29
|
-
* copy-proof fallback. Control flow that decides whether an anonymous retry
|
|
30
|
-
* is safe must use this, never a bare `instanceof`.
|
|
18
|
+
* Copy-proof check for the "no session" error — `instanceof` misses class
|
|
19
|
+
* copies from the package's other entrypoint. Fallback gates must use this.
|
|
31
20
|
*/
|
|
32
21
|
export declare function isNotAuthenticatedError(err: unknown): err is NotAuthenticatedError;
|
|
33
22
|
/** `POST …/sites` failed, so there is nothing to build into. Carries the HTTP status. */
|
|
@@ -45,12 +34,7 @@ export declare class BuildCreateError extends Error {
|
|
|
45
34
|
status?: number;
|
|
46
35
|
constructor(status?: number);
|
|
47
36
|
}
|
|
48
|
-
/**
|
|
49
|
-
* The build ran and ended in `error`/`rejected`. `reason` is the API's
|
|
50
|
-
* `error_message` when there is one — it's the only part of a build failure that
|
|
51
|
-
* says anything actionable (a failing build command, a missing dependency), so
|
|
52
|
-
* it is surfaced to the user rather than swallowed.
|
|
53
|
-
*/
|
|
37
|
+
/** The build ran and broke. `reason` carries the API's `error_message` — the only actionable part. */
|
|
54
38
|
export declare class BuildFailedError extends Error {
|
|
55
39
|
reason?: string | undefined;
|
|
56
40
|
readonly code = "build-failed";
|
|
@@ -61,10 +45,7 @@ export declare class BuildTimeoutError extends Error {
|
|
|
61
45
|
readonly code = "build-timeout";
|
|
62
46
|
constructor();
|
|
63
47
|
}
|
|
64
|
-
/**
|
|
65
|
-
* A static deploy ran and ended in `error`/`rejected`. Like a build failure,
|
|
66
|
-
* the API's `error_message` is the actionable part, so it travels on the error.
|
|
67
|
-
*/
|
|
48
|
+
/** A static deploy ran and ended in `error`/`rejected`; `reason` carries the API's `error_message`. */
|
|
68
49
|
export declare class DeployFailedError extends Error {
|
|
69
50
|
reason?: string | undefined;
|
|
70
51
|
readonly code = "deploy-failed";
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Distinct MIME types
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `ProcessedFile`s: the MIME type only exists on the original `File`, and reading
|
|
7
|
-
* can throw before any `ProcessedFile` does (an unsupported single file) — the
|
|
8
|
-
* very case the file type is most worth knowing about.
|
|
9
|
-
*
|
|
10
|
-
* Browsers leave `File.type` empty for anything they don't recognise — a dragged
|
|
11
|
-
* folder, an `.exe`, an extensionless file — so those collapse to
|
|
12
|
-
* `application/octet-stream` and the property never carries an empty string.
|
|
13
|
-
*
|
|
14
|
-
* Call this synchronously inside the drop handler: a `DataTransfer` detaches once
|
|
15
|
-
* the event turn ends.
|
|
2
|
+
* Distinct, sorted MIME types of a selection. Read at the drop boundary — the
|
|
3
|
+
* type only exists on the original File, and reading can throw before any
|
|
4
|
+
* ProcessedFile exists. Call synchronously in the drop handler: a DataTransfer
|
|
5
|
+
* detaches after the event turn.
|
|
16
6
|
*/
|
|
17
7
|
export declare function getFileTypes(files: FileList | null | undefined): string[];
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { ProcessedFile } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* Read a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* handler — the DataTransfer is detached once the event turn ends. The
|
|
7
|
-
* component captures entries first (see `readDroppedEntries`) then calls the
|
|
8
|
-
* async readers, so this function is safe to `await`.
|
|
3
|
+
* Read a drop's DataTransfer into ProcessedFiles. `webkitGetAsEntry()` runs
|
|
4
|
+
* synchronously before the first await — the DataTransfer detaches once the
|
|
5
|
+
* event turn ends.
|
|
9
6
|
*/
|
|
10
7
|
export declare function readDataTransfer(dt: DataTransfer): Promise<ProcessedFile[]>;
|
|
11
8
|
export declare function readFileList(files: FileList): Promise<ProcessedFile[]>;
|
|
12
9
|
export declare function getSingleNonIndexHtmlFileName(files: ProcessedFile[]): string | null;
|
|
13
10
|
export declare function hasMultipleHtmlFilesWithoutIndex(files: ProcessedFile[]): boolean;
|
|
14
11
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* index.html — keeping its directory — so it is served at the site root
|
|
18
|
-
* instead of 404ing. Content and sha are untouched. Any other set (already has
|
|
19
|
-
* an index, multiple HTML files, or no HTML) is returned unchanged.
|
|
12
|
+
* A lone non-index HTML page is renamed to index.html (same directory,
|
|
13
|
+
* content/sha untouched) so it serves instead of 404ing. Other sets unchanged.
|
|
20
14
|
*/
|
|
21
15
|
export declare function renameSingleNonIndexHtmlToIndex(files: ProcessedFile[]): ProcessedFile[];
|
|
@@ -9,10 +9,8 @@ export interface ProcessedFile {
|
|
|
9
9
|
}
|
|
10
10
|
export type Digest = Record<string, string>;
|
|
11
11
|
/**
|
|
12
|
-
* Response
|
|
13
|
-
*
|
|
14
|
-
* so the client has everything it needs to upload, poll, and hand off to the
|
|
15
|
-
* user in one round trip.
|
|
12
|
+
* Response of `POST /api/v1/drop` — a mixed site+deploy shape, everything the
|
|
13
|
+
* client needs to upload, poll, and hand off in one round trip.
|
|
16
14
|
*/
|
|
17
15
|
export interface DropResponse {
|
|
18
16
|
/** site id (UUID) — surfaced to callers so they can build claim links */
|
package/dist/drop-core/zip.d.ts
CHANGED
|
@@ -1,46 +1,23 @@
|
|
|
1
1
|
import { InferredBuildSettings } from './detectBuild';
|
|
2
2
|
import { ProcessedFile } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Render inferred settings as a `[build]` table. Only `command` and `publish`
|
|
5
|
-
* are written: everything else (functions directory, plugins, node version)
|
|
6
|
-
* is zero-config on Netlify's side and guessing at it would override detection
|
|
7
|
-
* that is better informed than we are.
|
|
8
|
-
*/
|
|
3
|
+
/** Renders a [build] table — command and publish only; everything else is zero-config server-side. */
|
|
9
4
|
export declare function generateNetlifyToml({ command, publish }: InferredBuildSettings): string;
|
|
10
5
|
export interface ZipOptions {
|
|
11
6
|
/** Filename given to the produced `File`. Shows up as the build's source archive. */
|
|
12
7
|
name?: string;
|
|
13
|
-
/**
|
|
14
|
-
* Synthesise a `netlify.toml` `[build]` table from `inferBuildSettings` when the
|
|
15
|
-
* drop doesn't declare one (default `true`). Set `false` to upload the files
|
|
16
|
-
* exactly as dropped and let buildbot resolve every setting itself.
|
|
17
|
-
*/
|
|
8
|
+
/** Synthesise a [build] table when the drop lacks one (default true); false uploads as-dropped. */
|
|
18
9
|
synthesizeNetlifyToml?: boolean;
|
|
19
10
|
}
|
|
20
11
|
export interface ZipResult {
|
|
21
12
|
/** The archive, ready to hand to `createBuildFromZip`. */
|
|
22
13
|
file: File;
|
|
23
|
-
/**
|
|
24
|
-
* The settings written into `netlify.toml`, or `null` when the archive is a
|
|
25
|
-
* faithful copy of the drop — either because it already declared a `[build]`
|
|
26
|
-
* table, or because nothing could be inferred with confidence.
|
|
27
|
-
*/
|
|
14
|
+
/** Settings written into netlify.toml, or null when the archive is a faithful copy. */
|
|
28
15
|
buildSettings: InferredBuildSettings | null;
|
|
29
16
|
}
|
|
30
17
|
/**
|
|
31
|
-
* Zip
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* uses (see `readFiles`), so junk — dotfiles, `node_modules`, macOS metadata — is
|
|
36
|
-
* already gone and paths sit at the deploy root. Output directories (`dist/`,
|
|
37
|
-
* `build/`) are deliberately *not* stripped: buildbot regenerates them when
|
|
38
|
-
* there's a build command, and a drop whose `netlify.toml` publishes a committed
|
|
39
|
-
* directory without a command would break if we removed it.
|
|
40
|
-
*
|
|
41
|
-
* When the drop needs a build but declares no `[build]` table, an inferred one is
|
|
42
|
-
* written in (appended to an existing `netlify.toml`, or created if there is
|
|
43
|
-
* none) so buildbot doesn't publish the project's source. See
|
|
44
|
-
* `inferBuildSettings` for how conservative that inference is.
|
|
18
|
+
* Zip a read file set into the archive `POST /sites/{id}/builds` expects.
|
|
19
|
+
* Junk is already gone (readFiles); dist/build are deliberately kept — a drop
|
|
20
|
+
* publishing a committed directory without a command would break without them.
|
|
21
|
+
* A [build] table is synthesised when missing (appended, so redirects survive).
|
|
45
22
|
*/
|
|
46
23
|
export declare function zipFiles(files: ProcessedFile[], { name, synthesizeNetlifyToml }?: ZipOptions): Promise<ZipResult>;
|
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.31.0-alpha.
|
|
4
|
+
"version": "1.31.0-alpha.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/components/preact/index.js",
|
|
7
7
|
"types": "dist/components/preact/index.d.ts",
|
|
@@ -25,26 +25,13 @@ const DRAG_INACTIVE_MS = 120;
|
|
|
25
25
|
|
|
26
26
|
const defaultClaimUrl = (siteName: string) => `https://app.netlify.com/drop/${siteName}`;
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Where an authenticated static deploy lands: the new site's project overview,
|
|
30
|
-
* the same place the app's own logged-in drop sends you.
|
|
31
|
-
*/
|
|
28
|
+
/** The project overview — where the app's own logged-in drop lands too. */
|
|
32
29
|
const defaultSiteDashboardUrl = (siteName: string) =>
|
|
33
30
|
`https://app.netlify.com/projects/${siteName}`;
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
* Where a build-required drop is sent by default. Mirrors the app's own Drop
|
|
37
|
-
* page: signup with `next=/drop`, so after signup + email verification the user
|
|
38
|
-
* lands back on the authenticated Drop page and can build there.
|
|
39
|
-
*/
|
|
32
|
+
// Mirrors the app's own Drop page hand-off.
|
|
40
33
|
const defaultBuildSignupUrl = 'https://app.netlify.com/signup?next=/drop&utm_campaign=drop';
|
|
41
34
|
|
|
42
|
-
/**
|
|
43
|
-
* Where an authenticated build drop lands: the new site's project overview —
|
|
44
|
-
* the same destination as a static deploy, and where the app's own Drop page
|
|
45
|
-
* sends build drops. The overview surfaces the in-progress deploy; consumers
|
|
46
|
-
* who want the deploy-log page instead can override with `build.deployId`.
|
|
47
|
-
*/
|
|
48
35
|
const defaultBuildDeployUrl = ({ siteName }: DeployedBuild) =>
|
|
49
36
|
`https://app.netlify.com/projects/${siteName}`;
|
|
50
37
|
|
|
@@ -57,29 +44,17 @@ interface Props {
|
|
|
57
44
|
client?: DropClient;
|
|
58
45
|
/** Build the "claim this site" URL from the assigned site name (subdomain). */
|
|
59
46
|
claimUrl?: (siteName: string) => string;
|
|
60
|
-
/**
|
|
61
|
-
* Where an authenticated static deploy sends the visitor — the site already
|
|
62
|
-
* lives in their account, so there is no claim step. Defaults to the new
|
|
63
|
-
* site's project overview on app.netlify.com.
|
|
64
|
-
*/
|
|
47
|
+
/** Destination after an authenticated static deploy (no claim step). Default: the project overview. */
|
|
65
48
|
siteDashboardUrl?: (siteName: string) => string;
|
|
66
49
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* a flow that returns the visitor to this page deploys it automatically —
|
|
70
|
-
* see `resumeStashedBuilds`. Defaults to the app signup page with
|
|
71
|
-
* `next=/drop`.
|
|
50
|
+
* Signup hand-off for a build drop with no session. The drop is stashed on
|
|
51
|
+
* this origin first — return the visitor here and it deploys automatically.
|
|
72
52
|
*/
|
|
73
53
|
buildSignupUrl?: string;
|
|
74
54
|
/**
|
|
75
|
-
* Mint
|
|
76
|
-
*
|
|
77
|
-
* drops
|
|
78
|
-
* build-required drops build in place instead of being handed off to the app.
|
|
79
|
-
* On netlify.com this reads `GET /access-control/generate-access-control-token`.
|
|
80
|
-
* Resolving `null` means "no session": static drops fall back to the anonymous
|
|
81
|
-
* claim flow; build drops are stashed and handed to `buildSignupUrl`.
|
|
82
|
-
* See [Logged-in visitors](#logged-in-visitors).
|
|
55
|
+
* Mint the upload bearer — passing this switches on the whole authenticated
|
|
56
|
+
* path. `null` means "no session": static drops fall back to the anonymous
|
|
57
|
+
* claim flow; build drops stash and hand off. See the docs' Logged-in visitors.
|
|
83
58
|
*/
|
|
84
59
|
getUploadToken?: () => Promise<string | null>;
|
|
85
60
|
/**
|
|
@@ -87,108 +62,46 @@ interface Props {
|
|
|
87
62
|
* Takes precedence over `getUploadToken` for the build path.
|
|
88
63
|
*/
|
|
89
64
|
buildClient?: BuildClient;
|
|
90
|
-
/**
|
|
91
|
-
* Inject a custom authenticated `DropClient` for static drops (tests, staging,
|
|
92
|
-
* a different account flow). Takes precedence over `getUploadToken` for the
|
|
93
|
-
* static path. Distinct from `client`, which is the anonymous fallback.
|
|
94
|
-
*/
|
|
65
|
+
/** Custom authenticated static-drop client; wins over `getUploadToken`. (`client` is the anonymous fallback.) */
|
|
95
66
|
authenticatedDropClient?: DropClient;
|
|
96
|
-
/**
|
|
97
|
-
* API root for the authenticated paths' JSON calls, which go through your
|
|
98
|
-
* access-control rewrite so the session cookie authorizes them. Defaults to
|
|
99
|
-
* `/access-control/bb-api/api/v1`. (Uploads always go direct to
|
|
100
|
-
* `api.netlify.com` — the proxy can't carry bodies that large.)
|
|
101
|
-
*/
|
|
67
|
+
/** Root for the session-cookie JSON calls, via your access-control rewrite. Uploads always go direct. */
|
|
102
68
|
proxyBase?: string;
|
|
103
69
|
/** Create the visitor's new project in this account. Omit for their default account. */
|
|
104
70
|
accountSlug?: string;
|
|
105
|
-
/**
|
|
106
|
-
* Where an enqueued build sends the visitor. Defaults to the new site's
|
|
107
|
-
* project overview, same as a static deploy; `build.deployId` is available for
|
|
108
|
-
* consumers who want the deploy-log page instead.
|
|
109
|
-
*/
|
|
71
|
+
/** Destination after a build is enqueued. Default: the project overview (`build.deployId` for the logs page). */
|
|
110
72
|
buildDeployUrl?: (build: DeployedBuild) => string;
|
|
111
73
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* its own error message here (via `onError` and the overlay) rather than the
|
|
115
|
-
* visitor discovering it on the deploy page — at the cost of holding them on a
|
|
116
|
-
* spinner for the length of the build, which is minutes. Off by default.
|
|
74
|
+
* Hold the visitor until the build finishes (off by default — builds take
|
|
75
|
+
* minutes). On is the only way a failing build's own message reaches `onError`.
|
|
117
76
|
*/
|
|
118
77
|
waitForBuild?: boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Called when a build-required drop is detected, instead of building it or
|
|
121
|
-
* redirecting. Use it to own the hand-off yourself (custom routing, persisting
|
|
122
|
-
* the drop, gating on auth). When set, the component neither builds nor
|
|
123
|
-
* redirects. The static drop path is unaffected either way.
|
|
124
|
-
*/
|
|
78
|
+
/** Take over build-required drops entirely — when set, the component neither builds nor redirects. */
|
|
125
79
|
onBuildRequired?: (info: BuildDetection) => void;
|
|
126
80
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* however fits your auth flow, e.g. open a signup popup that keeps the
|
|
131
|
-
* visitor on the page. Once they're back authenticated, a remount of the
|
|
132
|
-
* zone deploys the stash automatically.
|
|
81
|
+
* Own the signup hand-off instead of the `buildSignupUrl` redirect (e.g. an
|
|
82
|
+
* auth popup). The drop is already stashed; a remount while authenticated
|
|
83
|
+
* deploys it.
|
|
133
84
|
*/
|
|
134
85
|
onBuildSignupHandoff?: (info: { stashed: boolean; detection: BuildDetection }) => void;
|
|
135
|
-
/**
|
|
136
|
-
* Deploy a previously stashed build drop automatically when the zone mounts
|
|
137
|
-
* with an authenticated visitor (default `true`). The stash is written when a
|
|
138
|
-
* logged-out build drop is handed to signup; see
|
|
139
|
-
* [Signup hand-off and auto-resume](#signup-hand-off-and-auto-resume).
|
|
140
|
-
*/
|
|
86
|
+
/** Auto-deploy a stashed build drop on mount when authenticated (default true). */
|
|
141
87
|
resumeStashedBuilds?: boolean;
|
|
142
88
|
/** Called once the deploy is live. */
|
|
143
89
|
onDeploy?: (result: DeployedSite) => void;
|
|
144
|
-
/**
|
|
145
|
-
* Called once a build-required drop has been zipped, uploaded and enqueued —
|
|
146
|
-
* before the redirect to `buildDeployUrl`. The site exists at this point but
|
|
147
|
-
* won't serve content until the build finishes.
|
|
148
|
-
*/
|
|
90
|
+
/** Called once a build is enqueued, before the `buildDeployUrl` redirect. */
|
|
149
91
|
onBuildDeploy?: (build: DeployedBuild) => void;
|
|
150
|
-
/**
|
|
151
|
-
* Called when a deploy fails, with a humanized `message` (ready to show a
|
|
152
|
-
* user — the same copy the built-in overlay uses) and the raw `reason`. Pair
|
|
153
|
-
* this with `showStatus={false}` to render your own error UI.
|
|
154
|
-
*/
|
|
92
|
+
/** Deploy failures: humanized `message` (the overlay's copy) + raw `reason`. Pair with showStatus={false}. */
|
|
155
93
|
onError?: (error: DeployError) => void;
|
|
156
94
|
/**
|
|
157
|
-
* Render the built-in
|
|
158
|
-
*
|
|
159
|
-
* drive your own feedback from the `data-state` attribute on the root element.
|
|
160
|
-
* Note: in this mode you're responsible for surfacing errors yourself — use
|
|
161
|
-
* `onError` to get the message.
|
|
95
|
+
* Render the built-in overlay. `false` keeps children visible — drive your
|
|
96
|
+
* own feedback from `data-state`, and surface errors yourself via `onError`.
|
|
162
97
|
*/
|
|
163
98
|
showStatus?: boolean;
|
|
164
99
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* consuming app, e.g. `onTrack={(name, props) => new Analytics().track(name, props)}`.
|
|
168
|
-
* Emitted events (all include `dropzone_id` when `analyticsId` is set):
|
|
169
|
-
* - `dropzone_files_dropped` — files dropped via drag ({ method: 'drag', file_count, file_types })
|
|
170
|
-
* - `dropzone_browse_opened` — file picker opened ({ method: 'keyboard' | 'click' })
|
|
171
|
-
* - `dropzone_build_required` — drop needs a build (built here, or handed off) ({ method, reason, file_count, file_types })
|
|
172
|
-
* - `dropzone_deploy_succeeded` — static deploy went live ({ method, site_name, file_count, authenticated, auth_fallback?, file_types })
|
|
173
|
-
* - `dropzone_build_deploy_succeeded` — build enqueued for a logged-in visitor ({ method, reason, site_name, file_count, framework?, file_types })
|
|
174
|
-
* - `dropzone_deploy_failed` — deploy or build errored out ({ method, reason, file_count?, file_types })
|
|
175
|
-
*
|
|
176
|
-
* `authenticated` says whether the deploy landed in the visitor's account;
|
|
177
|
-
* `auth_fallback: true` is sent only when an authenticated attempt bounced
|
|
178
|
-
* (stale auth hint) and the drop completed anonymously — omitted otherwise.
|
|
179
|
-
* `file_types` is the sorted, de-duplicated list of MIME types in the
|
|
180
|
-
* selection, with `application/octet-stream` standing in wherever the browser
|
|
181
|
-
* reports none (a dragged folder, an `.exe`). It is omitted when the browser
|
|
182
|
-
* exposes no files at all. Note `dropzone_files_dropped` only fires for drag —
|
|
183
|
-
* a file-picker selection reports its types on the outcome events, as
|
|
184
|
-
* `dropzone_browse_opened` fires before any file exists.
|
|
100
|
+
* Provider-agnostic analytics hook. Event names and payload shapes live in
|
|
101
|
+
* DROPZONE_EVENTS / DropZoneEventProperties (drop-core/analytics.ts).
|
|
185
102
|
*/
|
|
186
103
|
onTrack?: TrackFn;
|
|
187
|
-
/**
|
|
188
|
-
* Stable identifier for this instance, attached to every tracked event as
|
|
189
|
-
* `dropzone_id`. Set it when a page renders more than one Drop Zone so the
|
|
190
|
-
* events can be told apart in Amplitude.
|
|
191
|
-
*/
|
|
104
|
+
/** Stamped on every event as `dropzone_id` — set when a page has more than one zone. */
|
|
192
105
|
analyticsId?: string;
|
|
193
106
|
className?: string;
|
|
194
107
|
}
|
|
@@ -218,8 +131,6 @@ export default function DropZone({
|
|
|
218
131
|
analyticsId,
|
|
219
132
|
className,
|
|
220
133
|
}: Props) {
|
|
221
|
-
// Tag every event with this instance's id so multiple zones on a page can be
|
|
222
|
-
// told apart, then hand it off to the consumer's analytics.
|
|
223
134
|
const track: TrackFn = (event, properties = {}) =>
|
|
224
135
|
onTrack?.(event, analyticsId ? { dropzone_id: analyticsId, ...properties } : properties);
|
|
225
136
|
|
|
@@ -253,8 +164,6 @@ export default function DropZone({
|
|
|
253
164
|
inputRef.current?.click();
|
|
254
165
|
};
|
|
255
166
|
|
|
256
|
-
// The overlay only paints when there's something to show — dragging, deploying,
|
|
257
|
-
// or an error. With `showStatus` off, the consumer drives their own feedback.
|
|
258
167
|
const showOverlay = showStatus && (active || busy || status.kind === 'error');
|
|
259
168
|
const rootClass = [
|
|
260
169
|
'n-dropzone',
|
|
@@ -343,11 +252,8 @@ export default function DropZone({
|
|
|
343
252
|
}
|
|
344
253
|
|
|
345
254
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* stream of `dragover` events — so we stay active only while they keep arriving
|
|
349
|
-
* and clear shortly after they stop. This also handles drags over children,
|
|
350
|
-
* where `dragleave` targets a child rather than the zone.
|
|
255
|
+
* Drag-over tracking by dragover decay: drags have no reliable cancel event,
|
|
256
|
+
* and dragleave targets children — so stay active while events keep arriving.
|
|
351
257
|
*/
|
|
352
258
|
function useDragActive() {
|
|
353
259
|
const [active, setActive] = useState(false);
|
|
@@ -287,6 +287,18 @@ describe('build-required drops', () => {
|
|
|
287
287
|
|
|
288
288
|
expect(saveDropStash).not.toHaveBeenCalled();
|
|
289
289
|
expect(harness.assigned).toEqual(['https://app.netlify.com/signup?next=/drop']);
|
|
290
|
+
// Nothing was stashed, so the drop is lost rather than deferred — reported
|
|
291
|
+
// with the same reason react-ui uses for its own build bounce.
|
|
292
|
+
const failed = harness.events.find(e => e.event === DROPZONE_EVENTS.DEPLOY_FAILED);
|
|
293
|
+
expect(failed?.props).toMatchObject({ reason: 'anonymous_build_not_supported' });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('does not report a loss when the archive was stashed', async () => {
|
|
297
|
+
const harness = mountHook({ buildClient: makeBuildClient('no-auth'), getUploadToken: async () => null });
|
|
298
|
+
await harness.api.deploy(async () => buildFiles(), 'drag', []);
|
|
299
|
+
await settle();
|
|
300
|
+
|
|
301
|
+
expect(harness.events.map(e => e.event)).not.toContain(DROPZONE_EVENTS.DEPLOY_FAILED);
|
|
290
302
|
});
|
|
291
303
|
|
|
292
304
|
it('cedes everything to onBuildRequired when set', async () => {
|