@shipstatic/drop 2.0.0-beta.1 → 2.0.0-beta.11
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 +17 -7
- package/dist/index.cjs +121 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +121 -10
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +109 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +109 -4
- package/dist/testing.js.map +1 -1
- package/dist/{useDrop-Bmjfjp_o.d.cts → useDrop-C43AHhf8.d.cts} +45 -14
- package/dist/{useDrop-Bmjfjp_o.d.ts → useDrop-C43AHhf8.d.ts} +45 -14
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -94,10 +94,10 @@ interface DropReturn {
|
|
|
94
94
|
|
|
95
95
|
// Prop getters
|
|
96
96
|
getDropzoneProps: (options?: { clickable?: boolean }) => { ... };
|
|
97
|
-
getInputProps: () => { ... };
|
|
97
|
+
getInputProps: (mode?: PickerMode) => { ... }; // 'folder' (default) | 'files'
|
|
98
98
|
|
|
99
99
|
// Actions
|
|
100
|
-
open: () => void;
|
|
100
|
+
open: (mode?: PickerMode) => void; // trigger a picker (default: folder)
|
|
101
101
|
processFiles: (files: File[]) => Promise<void>; // advanced — see below
|
|
102
102
|
reset: () => void;
|
|
103
103
|
|
|
@@ -141,18 +141,28 @@ useEffect(() => {
|
|
|
141
141
|
</div>
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Drag-only, with your own
|
|
144
|
+
Drag-only, with your own triggers:
|
|
145
145
|
|
|
146
146
|
```tsx
|
|
147
147
|
<div {...drop.getDropzoneProps({ clickable: false })}>
|
|
148
|
-
<input {...drop.getInputProps()} />
|
|
149
|
-
<
|
|
148
|
+
<input {...drop.getInputProps('folder')} />
|
|
149
|
+
<input {...drop.getInputProps('files')} />
|
|
150
|
+
<button onClick={() => drop.open('folder')}>Select folder</button>
|
|
151
|
+
<button onClick={() => drop.open('files')}>Select files</button>
|
|
150
152
|
</div>
|
|
151
153
|
```
|
|
152
154
|
|
|
153
155
|
`getDropzoneProps()` handles `webkitGetAsEntry` internally, which is what preserves folder structure. Calling `processFiles()` yourself loses it — the browser invalidates `dataTransfer.items` at the first `await`, so entries must be captured synchronously.
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
### Two pickers
|
|
158
|
+
|
|
159
|
+
`PickerMode` is `'folder' | 'files'`, and **folder is the default** — a bare `getInputProps()` / `open()`, and the dropzone's own click, open the folder picker.
|
|
160
|
+
|
|
161
|
+
An `<input>` is either a folder picker or a file picker, so each mode owns its own element and its own ref: a UI offering both renders **both inputs**, and `open(mode)` clicks whichever is mounted. Exactly one attribute differs — `webkitdirectory` in folder mode, `accept` in files mode.
|
|
162
|
+
|
|
163
|
+
Wrap `open` in a handler rather than passing it by reference (`onClick={() => drop.open('files')}`): React hands a click handler a `MouseEvent`, which would otherwise arrive as the mode.
|
|
164
|
+
|
|
165
|
+
**Selecting is not a second code path.** A picked file set — loose files or a ZIP — runs the identical pipeline as a dropped one, with the same paths, the same source name and the same verdict. The `accept` list is a *hint* that biases what the file dialog shows first; it decides nothing, since every dialog offers an all-files escape and drag & drop ignores `accept` outright. What files may be deployed is one rule, applied downstream of both entry points.
|
|
156
166
|
|
|
157
167
|
## Validation
|
|
158
168
|
|
|
@@ -191,7 +201,7 @@ await ship.deployments.upload(drop.getFilesForUpload(), {
|
|
|
191
201
|
|
|
192
202
|
## ZIP handling
|
|
193
203
|
|
|
194
|
-
A **single** dropped ZIP is extracted and its contents deployed. ZIPs among several files are treated as ordinary files. Archive paths are sanitized against directory traversal (`../../
|
|
204
|
+
A **single** dropped ZIP is extracted and its contents deployed. ZIPs among several files are treated as ordinary files. Archive paths are sanitized against directory traversal (`../../config.json` → `config.json`).
|
|
195
205
|
|
|
196
206
|
## Without React
|
|
197
207
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,10 +4,16 @@ var ship = require('@shipstatic/ship');
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
|
|
6
6
|
// src/process.ts
|
|
7
|
-
|
|
8
|
-
// node_modules/.pnpm/@shipstatic+types@2.2.1-beta.0/node_modules/@shipstatic/types/dist/index.js
|
|
9
7
|
var ErrorType = {
|
|
10
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Validation failed. Input shape is wrong.
|
|
10
|
+
*
|
|
11
|
+
* Carries 400 when an API judged it — including a client-side pre-check of a
|
|
12
|
+
* rule the server enforces too, which keeps the error identical wherever it
|
|
13
|
+
* was caught. **Statusless** when a client rejects something no API judges,
|
|
14
|
+
* such as a CLI's own command grammar: `status` is documented "(API
|
|
15
|
+
* contexts)" on `ErrorResponse`, so there is none to report.
|
|
16
|
+
*/
|
|
11
17
|
Validation: "validation_failed",
|
|
12
18
|
/** Resource not found (404). */
|
|
13
19
|
NotFound: "not_found",
|
|
@@ -21,6 +27,17 @@ var ErrorType = {
|
|
|
21
27
|
Business: "business_logic_error",
|
|
22
28
|
/** API server error (500). Generic server-side fault. */
|
|
23
29
|
Api: "internal_server_error",
|
|
30
|
+
/**
|
|
31
|
+
* The platform is closed for maintenance (503). A deliberate operator
|
|
32
|
+
* state, not a fault — nothing errored; the API is refusing work on
|
|
33
|
+
* purpose, and deployed sites keep serving throughout.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from `Api` at 503, which the platform already uses for a
|
|
36
|
+
* dependency that failed (moderation unavailable). A consumer has to tell
|
|
37
|
+
* "we closed the door" from "something broke": the two get opposite words
|
|
38
|
+
* and opposite retry behaviour.
|
|
39
|
+
*/
|
|
40
|
+
Maintenance: "maintenance",
|
|
24
41
|
/** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
|
|
25
42
|
Network: "network_error",
|
|
26
43
|
/** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
|
|
@@ -40,6 +57,90 @@ new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)))
|
|
|
40
57
|
function isShipError(error) {
|
|
41
58
|
return error !== null && typeof error === "object" && "name" in error && error.name === "ShipError" && "status" in error;
|
|
42
59
|
}
|
|
60
|
+
var WEB_FILE_EXTENSIONS = [
|
|
61
|
+
// Markup & documents
|
|
62
|
+
"html",
|
|
63
|
+
"htm",
|
|
64
|
+
"xhtml",
|
|
65
|
+
"xml",
|
|
66
|
+
"txt",
|
|
67
|
+
"md",
|
|
68
|
+
"markdown",
|
|
69
|
+
"pdf",
|
|
70
|
+
"csv",
|
|
71
|
+
// Data & config
|
|
72
|
+
"json",
|
|
73
|
+
"jsonc",
|
|
74
|
+
"webmanifest",
|
|
75
|
+
"map",
|
|
76
|
+
"toml",
|
|
77
|
+
"yaml",
|
|
78
|
+
"yml",
|
|
79
|
+
"rss",
|
|
80
|
+
"atom",
|
|
81
|
+
// Styles
|
|
82
|
+
"css",
|
|
83
|
+
"scss",
|
|
84
|
+
"sass",
|
|
85
|
+
"less",
|
|
86
|
+
// Scripts & modules
|
|
87
|
+
"js",
|
|
88
|
+
"mjs",
|
|
89
|
+
"cjs",
|
|
90
|
+
"jsx",
|
|
91
|
+
"ts",
|
|
92
|
+
"tsx",
|
|
93
|
+
"wasm",
|
|
94
|
+
"vue",
|
|
95
|
+
"svelte",
|
|
96
|
+
// Images
|
|
97
|
+
"png",
|
|
98
|
+
"jpg",
|
|
99
|
+
"jpeg",
|
|
100
|
+
"gif",
|
|
101
|
+
"webp",
|
|
102
|
+
"avif",
|
|
103
|
+
"svg",
|
|
104
|
+
"ico",
|
|
105
|
+
"bmp",
|
|
106
|
+
"tif",
|
|
107
|
+
"tiff",
|
|
108
|
+
"heic",
|
|
109
|
+
"heif",
|
|
110
|
+
// Fonts
|
|
111
|
+
"woff",
|
|
112
|
+
"woff2",
|
|
113
|
+
"ttf",
|
|
114
|
+
"otf",
|
|
115
|
+
"eot",
|
|
116
|
+
// Audio
|
|
117
|
+
"mp3",
|
|
118
|
+
"wav",
|
|
119
|
+
"ogg",
|
|
120
|
+
"oga",
|
|
121
|
+
"opus",
|
|
122
|
+
"m4a",
|
|
123
|
+
"aac",
|
|
124
|
+
"flac",
|
|
125
|
+
"weba",
|
|
126
|
+
// Video
|
|
127
|
+
"mp4",
|
|
128
|
+
"webm",
|
|
129
|
+
"ogv",
|
|
130
|
+
"mov",
|
|
131
|
+
"m4v",
|
|
132
|
+
"avi",
|
|
133
|
+
// 3D models
|
|
134
|
+
"glb",
|
|
135
|
+
"gltf",
|
|
136
|
+
"usdz",
|
|
137
|
+
// Text tracks
|
|
138
|
+
"vtt",
|
|
139
|
+
"srt",
|
|
140
|
+
// Archive — a whole site in one file
|
|
141
|
+
"zip"
|
|
142
|
+
];
|
|
143
|
+
var WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(",");
|
|
43
144
|
var UNBUILT_PROJECT_MARKERS = /* @__PURE__ */ new Set([
|
|
44
145
|
"node_modules",
|
|
45
146
|
"package.json"
|
|
@@ -948,7 +1049,8 @@ var initialState = {
|
|
|
948
1049
|
function useDrop({ ship }) {
|
|
949
1050
|
const [state, setState] = react.useState(initialState);
|
|
950
1051
|
const isProcessingRef = react.useRef(false);
|
|
951
|
-
const
|
|
1052
|
+
const folderInputRef = react.useRef(null);
|
|
1053
|
+
const filesInputRef = react.useRef(null);
|
|
952
1054
|
const isProcessing = state.phase === "processing";
|
|
953
1055
|
const hasError = state.phase === "error";
|
|
954
1056
|
const isInteractive = state.phase === "idle" || state.phase === "ready";
|
|
@@ -1042,8 +1144,15 @@ function useDrop({ ship }) {
|
|
|
1042
1144
|
},
|
|
1043
1145
|
[processFiles2]
|
|
1044
1146
|
);
|
|
1045
|
-
const open = react.useCallback(() => {
|
|
1046
|
-
|
|
1147
|
+
const open = react.useCallback((mode) => {
|
|
1148
|
+
const input = mode === "files" ? filesInputRef.current : folderInputRef.current;
|
|
1149
|
+
if (!input) {
|
|
1150
|
+
console.warn(
|
|
1151
|
+
`No ${mode === "files" ? "files" : "folder"} input is mounted. Spread getInputProps('${mode === "files" ? "files" : "folder"}') onto an <input> to open this picker.`
|
|
1152
|
+
);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
input.click();
|
|
1047
1156
|
}, []);
|
|
1048
1157
|
const getDropzoneProps = react.useCallback(
|
|
1049
1158
|
(options) => {
|
|
@@ -1052,18 +1161,20 @@ function useDrop({ ship }) {
|
|
|
1052
1161
|
onDragOver: handleDragOver,
|
|
1053
1162
|
onDragLeave: handleDragLeave,
|
|
1054
1163
|
onDrop: handleDrop,
|
|
1055
|
-
|
|
1164
|
+
// Wrapped rather than passed by reference: `open` takes a mode, and a
|
|
1165
|
+
// click handler would hand it a MouseEvent.
|
|
1166
|
+
...clickable && { onClick: () => open() }
|
|
1056
1167
|
};
|
|
1057
1168
|
},
|
|
1058
1169
|
[handleDragOver, handleDragLeave, handleDrop, open]
|
|
1059
1170
|
);
|
|
1060
1171
|
const getInputProps = react.useCallback(
|
|
1061
|
-
() => ({
|
|
1062
|
-
ref:
|
|
1172
|
+
(mode) => ({
|
|
1173
|
+
ref: mode === "files" ? filesInputRef : folderInputRef,
|
|
1063
1174
|
type: "file",
|
|
1064
1175
|
style: { display: "none" },
|
|
1065
1176
|
multiple: true,
|
|
1066
|
-
webkitdirectory: "",
|
|
1177
|
+
...mode === "files" ? { accept: WEB_FILE_ACCEPT } : { webkitdirectory: "" },
|
|
1067
1178
|
onChange: handleInputChange
|
|
1068
1179
|
}),
|
|
1069
1180
|
[handleInputChange]
|