@shipstatic/drop 0.1.1 → 0.1.3
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/LICENSE +21 -0
- package/README.md +181 -42
- package/dist/index.cjs +24 -589
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -45
- package/dist/index.d.ts +20 -45
- package/dist/index.js +25 -586
- package/dist/index.js.map +1 -1
- package/package.json +7 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 shipstatic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Headless file processing toolkit for Ship SDK deployments**
|
|
4
4
|
|
|
5
|
-
A focused React hook for preparing files for deployment with [@shipstatic/ship](https://github.com/shipstatic/ship). Handles ZIP extraction,
|
|
5
|
+
A focused React hook for preparing files for deployment with [@shipstatic/ship](https://github.com/shipstatic/ship). Handles ZIP extraction, path normalization, and validation - everything needed before calling `ship.deploy()`.
|
|
6
|
+
|
|
7
|
+
**Note:** MD5 calculation is handled by Ship SDK during deployment. Drop focuses on file processing and UI state management.
|
|
6
8
|
|
|
7
9
|
## Why Headless?
|
|
8
10
|
|
|
@@ -13,18 +15,17 @@ This package provides **zero UI components**. You build the dropzone that fits y
|
|
|
13
15
|
3. **Smaller bundle** - No React components, no extra dependencies (~14KB saved vs generic libraries)
|
|
14
16
|
4. **Ship SDK integration** - Purpose-built for Ship deployments, not a generic file upload library
|
|
15
17
|
|
|
16
|
-
The package focuses on what's hard (ZIP extraction,
|
|
18
|
+
The package focuses on what's hard (ZIP extraction, folder structure preservation) and leaves what's easy (UI) to you.
|
|
17
19
|
|
|
18
20
|
## Features
|
|
19
21
|
|
|
20
22
|
- 🎯 **Headless Architecture** - Just the hook, no UI opinions
|
|
21
23
|
- 📦 **ZIP Support** - Automatic ZIP file extraction and processing
|
|
22
|
-
- ✅ **Validation** - Client-side file size, count, and total size validation
|
|
23
|
-
- 🗑️ **Junk Filtering** - Automatically filters `.DS_Store`, `Thumbs.db`, etc.
|
|
24
|
-
- 🔍 **MD5 Hashing** - Calculates MD5 checksums for all files
|
|
24
|
+
- ✅ **Validation** - Client-side file size, count, and total size validation (powered by Ship SDK)
|
|
25
|
+
- 🗑️ **Junk Filtering** - Automatically filters `.DS_Store`, `Thumbs.db`, etc. (powered by Ship SDK)
|
|
25
26
|
- 🔒 **Path Sanitization** - Defense-in-depth protection against directory traversal attacks
|
|
26
27
|
- 📁 **Folder Structure Preservation** - Respects `webkitRelativePath` for proper deployment paths
|
|
27
|
-
- 🚀 **
|
|
28
|
+
- 🚀 **Focused Scope** - File processing and UI state only. MD5 calculation and deployment handled by Ship SDK
|
|
28
29
|
|
|
29
30
|
## Installation
|
|
30
31
|
|
|
@@ -40,21 +41,18 @@ pnpm add @shipstatic/drop
|
|
|
40
41
|
import { useDrop } from '@shipstatic/drop';
|
|
41
42
|
import Ship from '@shipstatic/ship';
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
const ship = new Ship({ apiUrl: '...' });
|
|
45
|
-
|
|
46
|
-
// Get validation config from Ship SDK
|
|
47
|
-
const config = await ship.getConfig();
|
|
44
|
+
const ship = new Ship({ deployToken: 'token-xxxx' });
|
|
48
45
|
|
|
46
|
+
function MyUploader() {
|
|
49
47
|
const drop = useDrop({
|
|
50
|
-
|
|
48
|
+
ship // Pass Ship instance - Drop uses ship.getConfig() for validation
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
const handleUpload = async () => {
|
|
54
52
|
const validFiles = drop.getValidFiles();
|
|
55
53
|
|
|
56
54
|
// ProcessedFile extends StaticFile - no conversion needed!
|
|
57
|
-
await ship.deployments.create(
|
|
55
|
+
await ship.deployments.create(validFiles.map(f => f.file));
|
|
58
56
|
};
|
|
59
57
|
|
|
60
58
|
return (
|
|
@@ -87,6 +85,31 @@ function MyUploader() {
|
|
|
87
85
|
}
|
|
88
86
|
```
|
|
89
87
|
|
|
88
|
+
### ⚠️ Configuration Architecture
|
|
89
|
+
|
|
90
|
+
**Drop uses Ship's validation config automatically:**
|
|
91
|
+
|
|
92
|
+
Drop accepts a `Ship` instance and uses `ship.getConfig()` internally. This ensures:
|
|
93
|
+
- ✅ **Single source of truth** - Validation config comes from Ship SDK
|
|
94
|
+
- ✅ **Always in sync** - Client validation matches server limits
|
|
95
|
+
- ✅ **No manual config fetching** - Drop handles it internally
|
|
96
|
+
- ✅ **Simpler API** - Just pass `ship` instance
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
// Drop fetches config from Ship SDK automatically
|
|
100
|
+
const drop = useDrop({ ship });
|
|
101
|
+
|
|
102
|
+
// Behind the scenes:
|
|
103
|
+
// 1. Ship SDK fetches /config on initialization
|
|
104
|
+
// 2. Drop calls ship.getConfig() when validating
|
|
105
|
+
// 3. Validation always uses current server limits
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Why this architecture:**
|
|
109
|
+
- Drop has NO validation rules of its own - it's a pure proxy
|
|
110
|
+
- Ship SDK is the single source of truth for validation
|
|
111
|
+
- Drop only provides what Ship doesn't have (ZIP, React state, folder structure)
|
|
112
|
+
|
|
90
113
|
## Building Your Drop Zone with Folder Support
|
|
91
114
|
|
|
92
115
|
For production use, you'll want to support folder drag-and-drop using modern browser APIs. Here's a complete example:
|
|
@@ -243,8 +266,8 @@ Main hook for managing drop state.
|
|
|
243
266
|
|
|
244
267
|
```typescript
|
|
245
268
|
interface DropOptions {
|
|
246
|
-
/**
|
|
247
|
-
|
|
269
|
+
/** Ship SDK instance (required for validation) */
|
|
270
|
+
ship: Ship;
|
|
248
271
|
/** Callback when validation fails */
|
|
249
272
|
onValidationError?: (error: ClientError) => void;
|
|
250
273
|
/** Callback when files are ready for upload */
|
|
@@ -266,13 +289,9 @@ interface DropReturn {
|
|
|
266
289
|
isProcessing: boolean;
|
|
267
290
|
/** Last validation error if any */
|
|
268
291
|
validationError: ClientError | null;
|
|
269
|
-
/** Whether all valid files have MD5 checksums calculated */
|
|
270
|
-
hasChecksums: boolean;
|
|
271
292
|
|
|
272
293
|
/** Process files from drop (resets and replaces existing files) */
|
|
273
294
|
processFiles: (files: File[]) => Promise<void>;
|
|
274
|
-
/** Remove a specific file */
|
|
275
|
-
removeFile: (fileId: string) => void;
|
|
276
295
|
/** Clear all files and reset state */
|
|
277
296
|
clearAll: () => void;
|
|
278
297
|
/** Get only valid files ready for upload */
|
|
@@ -286,19 +305,121 @@ interface DropReturn {
|
|
|
286
305
|
}
|
|
287
306
|
```
|
|
288
307
|
|
|
308
|
+
## Error Handling
|
|
309
|
+
|
|
310
|
+
### Per-File Error Display
|
|
311
|
+
|
|
312
|
+
Each file in the `files` array contains its own `status` and `statusMessage`, allowing you to display granular errors for individual files:
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
function FileList({ drop }) {
|
|
316
|
+
return (
|
|
317
|
+
<div>
|
|
318
|
+
{drop.files.map(file => (
|
|
319
|
+
<div key={file.id}>
|
|
320
|
+
<span>{file.path}</span>
|
|
321
|
+
|
|
322
|
+
{/* Show status indicator */}
|
|
323
|
+
{file.status === 'ready' ? '✓' : '✗'}
|
|
324
|
+
|
|
325
|
+
{/* Show per-file error message */}
|
|
326
|
+
{file.status !== 'ready' && file.statusMessage && (
|
|
327
|
+
<span style={{ color: 'red' }}>
|
|
328
|
+
{file.statusMessage}
|
|
329
|
+
</span>
|
|
330
|
+
)}
|
|
331
|
+
</div>
|
|
332
|
+
))}
|
|
333
|
+
|
|
334
|
+
{/* If validation fails, allow user to clear all and try again */}
|
|
335
|
+
{drop.validationError && (
|
|
336
|
+
<button onClick={drop.clearAll}>
|
|
337
|
+
Clear All & Try Again
|
|
338
|
+
</button>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Common error statuses:**
|
|
346
|
+
- `validation_failed` - File failed validation (size, type, name, etc.)
|
|
347
|
+
- `processing_error` - MD5 calculation or processing failed
|
|
348
|
+
- `empty_file` - File is 0 bytes
|
|
349
|
+
- `ready` - File passed all validation and is ready for upload
|
|
350
|
+
|
|
351
|
+
### Validation Error Summary
|
|
352
|
+
|
|
353
|
+
The `validationError` provides a summary when any files fail validation:
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
{drop.validationError && (
|
|
357
|
+
<div>
|
|
358
|
+
<p>{drop.validationError.error}</p>
|
|
359
|
+
<p>{drop.validationError.details}</p>
|
|
360
|
+
</div>
|
|
361
|
+
)}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Atomic Validation**: If ANY file fails validation, ALL files are marked as `validation_failed`. This ensures deployments are all-or-nothing for data integrity. The Ship SDK follows this same pattern server-side.
|
|
365
|
+
|
|
366
|
+
### No Individual File Removal
|
|
367
|
+
|
|
368
|
+
The Drop package **intentionally does not support removing individual files**. Here's why:
|
|
369
|
+
|
|
370
|
+
**Reason:** Ship SDK uses **atomic validation** - if ANY file fails validation, ALL files are marked as `validation_failed`. This ensures deployments are all-or-nothing for data integrity.
|
|
371
|
+
|
|
372
|
+
**The Problem with Individual Removal:**
|
|
373
|
+
```tsx
|
|
374
|
+
// User drops 5 files, 1 is too large
|
|
375
|
+
// Atomic validation: ALL 5 files marked as validation_failed
|
|
376
|
+
|
|
377
|
+
// If we allowed removing the large file:
|
|
378
|
+
drop.removeFile(largeFileId); // ❌ We don't support this!
|
|
379
|
+
|
|
380
|
+
// Would need to re-validate remaining 4 files
|
|
381
|
+
// Creates complexity and race conditions
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**The Simple Solution:**
|
|
385
|
+
Use `clearAll()` to reset and try again:
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
// If validation fails, show user which files failed
|
|
389
|
+
{drop.validationError && (
|
|
390
|
+
<div>
|
|
391
|
+
<p>Validation failed. Please fix the issues and try again:</p>
|
|
392
|
+
{drop.files.map(file => (
|
|
393
|
+
<div key={file.id}>
|
|
394
|
+
{file.path}: {file.statusMessage}
|
|
395
|
+
</div>
|
|
396
|
+
))}
|
|
397
|
+
<button onClick={drop.clearAll}>Clear All & Try Again</button>
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Benefits:**
|
|
403
|
+
- ✅ No race conditions or stale validation state
|
|
404
|
+
- ✅ Simpler mental model (atomic = all-or-nothing)
|
|
405
|
+
- ✅ Aligns with Ship SDK's validation philosophy
|
|
406
|
+
- ✅ Clear UX: fix the problem, then re-drop
|
|
407
|
+
|
|
289
408
|
## Types
|
|
290
409
|
|
|
291
410
|
```typescript
|
|
292
411
|
/**
|
|
293
412
|
* ProcessedFile extends StaticFile from @shipstatic/types
|
|
294
413
|
* This means it can be passed directly to ship.deployments.create()
|
|
414
|
+
*
|
|
415
|
+
* Note: md5 is intentionally undefined - Ship SDK calculates it during deployment
|
|
295
416
|
*/
|
|
296
417
|
interface ProcessedFile extends StaticFile {
|
|
297
418
|
// StaticFile properties (SDK compatibility)
|
|
298
419
|
content: File; // File object (required by SDK)
|
|
299
420
|
path: string; // Normalized path (webkitRelativePath or file.name)
|
|
300
421
|
size: number; // File size in bytes
|
|
301
|
-
md5?: string; //
|
|
422
|
+
md5?: string; // Undefined - Ship SDK calculates during deployment
|
|
302
423
|
|
|
303
424
|
// ProcessedFile-specific properties (UI functionality)
|
|
304
425
|
id: string; // Unique identifier for React keys
|
|
@@ -307,18 +428,8 @@ interface ProcessedFile extends StaticFile {
|
|
|
307
428
|
type: string; // MIME type
|
|
308
429
|
lastModified: number;
|
|
309
430
|
status: FileStatus;
|
|
310
|
-
statusMessage?: string;
|
|
311
|
-
progress?: number;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* ValidationConfig is an alias to ConfigResponse from @shipstatic/types
|
|
316
|
-
* Use ship.getConfig() to get the exact validation limits from the server
|
|
317
|
-
*/
|
|
318
|
-
interface ValidationConfig {
|
|
319
|
-
maxFileSize: number; // Default: 5MB
|
|
320
|
-
maxTotalSize: number; // Default: 25MB
|
|
321
|
-
maxFilesCount: number; // Default: 100
|
|
431
|
+
statusMessage?: string; // Per-file error message
|
|
432
|
+
progress?: number; // Upload progress (0-100)
|
|
322
433
|
}
|
|
323
434
|
|
|
324
435
|
interface ClientError {
|
|
@@ -373,15 +484,39 @@ interface ProcessedFile extends StaticFile {
|
|
|
373
484
|
|
|
374
485
|
## Architecture Decisions
|
|
375
486
|
|
|
487
|
+
### Why Drop Doesn't Calculate MD5
|
|
488
|
+
|
|
489
|
+
**Design Philosophy:** Drop should only provide what Ship SDK doesn't have.
|
|
490
|
+
|
|
491
|
+
**What Drop provides:**
|
|
492
|
+
- ✅ ZIP extraction (Ship SDK doesn't have this)
|
|
493
|
+
- ✅ React state management (Ship SDK doesn't have this)
|
|
494
|
+
- ✅ Folder structure preservation (UI-specific concern)
|
|
495
|
+
- ✅ Path normalization (UI-specific concern)
|
|
496
|
+
|
|
497
|
+
**What Ship SDK provides:**
|
|
498
|
+
- ✅ MD5 calculation (already implemented)
|
|
499
|
+
- ✅ Validation (already implemented)
|
|
500
|
+
- ✅ Deployment (core functionality)
|
|
501
|
+
|
|
502
|
+
**Why this matters:**
|
|
503
|
+
- Avoids duplicate MD5 calculation (performance)
|
|
504
|
+
- Single source of truth for deployment logic
|
|
505
|
+
- Drop stays focused on UI concerns
|
|
506
|
+
- Ship SDK handles all deployment concerns
|
|
507
|
+
|
|
508
|
+
**StaticFile.md5 is optional** - Ship SDK calculates it during deployment if not provided.
|
|
509
|
+
|
|
376
510
|
### Why Not Abstract?
|
|
377
511
|
|
|
378
512
|
This package was extracted from the `web/drop` application and is purpose-built for Ship SDK. Key decisions:
|
|
379
513
|
|
|
380
|
-
**1.
|
|
381
|
-
-
|
|
382
|
-
-
|
|
383
|
-
- Folder structure preservation
|
|
384
|
-
-
|
|
514
|
+
**1. Focused on UI Concerns**
|
|
515
|
+
- ZIP extraction for user convenience
|
|
516
|
+
- File list state management for React UIs
|
|
517
|
+
- Folder structure preservation from drag-and-drop
|
|
518
|
+
- Path normalization for clean URLs
|
|
519
|
+
- These are UI/UX concerns, not deployment logic
|
|
385
520
|
|
|
386
521
|
**2. Loosely Coupled Integration Pattern**
|
|
387
522
|
Following industry standards (Firebase hooks, Supabase utilities), we chose:
|
|
@@ -413,14 +548,18 @@ We deliberately don't provide drop zone UI components because:
|
|
|
413
548
|
- Your deployment UI is unique to your application
|
|
414
549
|
- Providing a component that "works but loses paths" would be misleading
|
|
415
550
|
|
|
416
|
-
### Error Handling
|
|
551
|
+
### Error Handling Philosophy
|
|
417
552
|
|
|
418
|
-
|
|
419
|
-
-
|
|
420
|
-
-
|
|
421
|
-
-
|
|
553
|
+
All errors are surfaced at the per-file level:
|
|
554
|
+
- Each file has its own `status` and `statusMessage` property
|
|
555
|
+
- Processing errors (e.g., ZIP extraction failures) are marked with `status: 'processing_error'`
|
|
556
|
+
- Validation failures are marked with `status: 'validation_failed'`
|
|
557
|
+
- The `statusMessage` always contains specific error details
|
|
558
|
+
- Failed files are excluded from `getValidFiles()` and cannot be deployed
|
|
422
559
|
- No silent failures - all errors are visible to users
|
|
423
560
|
|
|
561
|
+
See the [Error Handling](#error-handling) section for examples of displaying per-file errors in your UI.
|
|
562
|
+
|
|
424
563
|
### Security
|
|
425
564
|
|
|
426
565
|
**Path Sanitization**: ZIP extraction includes defense-in-depth protection against directory traversal attacks:
|