@shipstatic/ship 0.1.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/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 ADDED
@@ -0,0 +1,495 @@
1
+ # Ship CLI & SDK
2
+
3
+ A modern, lightweight SDK and CLI for deploying static files, designed for both **Node.js** and **Browser** environments with a clean resource-based API.
4
+
5
+ ## Features
6
+
7
+ - **🚀 Modern Resource API**: Clean `ship.deployments.create()` interface - no legacy wrappers
8
+ - **🌍 Universal**: Automatic environment detection (Node.js/Browser) with optimized implementations
9
+ - **🔧 Dynamic Configuration**: Automatically fetches platform limits from API
10
+ - **📁 Flexible Input**: File paths (Node.js) or File objects (Browser/drag-drop)
11
+ - **🔐 Secure**: MD5 checksums and data integrity validation
12
+ - **📊 Progress Tracking**: Real-time deployment progress and statistics
13
+ - **⚡ Cancellable**: AbortSignal support for deployment cancellation
14
+ - **🛠️ CLI Ready**: Command-line interface for automation and CI/CD
15
+ - **📦 Bundle Optimized**: Lightweight builds (16KB Node, 275KB Browser)
16
+ - **🎯 Unified Error System**: Consistent `ShipError` handling across all components
17
+
18
+ ## Installation
19
+
20
+ ### CLI Usage
21
+
22
+ ```bash
23
+ npm install -g @shipstatic/ship
24
+ ```
25
+
26
+ ### SDK Usage
27
+
28
+ ```bash
29
+ npm install @shipstatic/ship
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### SDK Usage
35
+
36
+ ```typescript
37
+ import { Ship } from '@shipstatic/ship';
38
+
39
+ const ship = new Ship({
40
+ apiUrl: 'https://api.shipstatic.com',
41
+ apiKey: 'ship-your-64-char-hex-string' // API key: ship- prefix + 64-char hex (69 chars total)
42
+ });
43
+
44
+ // Deploy files - SDK automatically fetches platform configuration
45
+ const result = await ship.deployments.create(['./dist'], {
46
+ onProgress: (progress) => console.log(`${progress}%`)
47
+ });
48
+
49
+ console.log(`Deployed: ${result.deployment}`);
50
+ ```
51
+
52
+ ### CLI Usage
53
+
54
+ ```bash
55
+ # Deploy shortcut - deploy a directory
56
+ ship ./dist
57
+
58
+ # Or deploy current directory
59
+ ship
60
+
61
+ # Explicit commands
62
+ ship deploy ./build # Deploy files from path
63
+ ship list # List deployments
64
+ ship get abc123 # Get deployment details
65
+ ship remove abc123 # Remove deployment
66
+
67
+ # Manage aliases
68
+ ship aliases # List aliases
69
+ ship alias staging abc123 # Set alias to deployment
70
+
71
+ # Account
72
+ ship account # Get account details
73
+
74
+ # Connectivity
75
+ ship ping # Check API connectivity
76
+ ```
77
+
78
+ ## Dynamic Platform Configuration
79
+
80
+ Ship SDK automatically fetches platform configuration from the API on initialization:
81
+
82
+ ```typescript
83
+ // SDK automatically calls GET /config and applies limits
84
+ const ship = new Ship({ apiKey: 'ship-your-key' });
85
+
86
+ // Platform limits are now available for validation:
87
+ // - maxFileSize: Dynamic file size limit
88
+ // - maxFilesCount: Dynamic file count limit
89
+ // - maxTotalSize: Dynamic total size limit
90
+ ```
91
+
92
+ **Benefits:**
93
+ - **Single source of truth** - Limits only need to be changed on the API side
94
+ - **Automatic updates** - SDK always uses current platform limits
95
+ - **Fail fast** - SDK fails if unable to fetch valid configuration
96
+
97
+ ## API Reference
98
+
99
+ ### Ship Class
100
+
101
+ ```typescript
102
+ const ship = new Ship(options?: ShipOptions)
103
+ ```
104
+
105
+ #### Options
106
+
107
+ ```typescript
108
+ interface ShipOptions {
109
+ apiUrl?: string; // API endpoint (default: https://api.shipstatic.com)
110
+ apiKey?: string; // API key: ship- prefix + 64-char hex (69 chars total)
111
+ timeout?: number; // Request timeout (ms)
112
+ }
113
+ ```
114
+
115
+ #### Methods
116
+
117
+ - `ship.ping()` - Check API connectivity
118
+ - `ship.deployments` - Access deployment resource
119
+ - `ship.aliases` - Access alias resource
120
+ - `ship.account` - Access account resource
121
+
122
+ ### Deployments Resource
123
+
124
+ ```typescript
125
+ // Deploy files
126
+ await ship.deployments.create(input, options?)
127
+
128
+ // List deployments
129
+ await ship.deployments.list()
130
+
131
+ // Remove deployment
132
+ await ship.deployments.remove(id)
133
+
134
+ // Get deployment details
135
+ await ship.deployments.get(id)
136
+ ```
137
+
138
+ #### Deploy Input Types
139
+
140
+ **Node.js Environment:**
141
+ ```typescript
142
+ type NodeDeployInput = string[]; // File paths
143
+ ```
144
+
145
+ **Browser Environment:**
146
+ ```typescript
147
+ type BrowserDeployInput = FileList | File[] | HTMLInputElement;
148
+ ```
149
+
150
+ #### Deploy Options
151
+
152
+ ```typescript
153
+ interface DeployOptions {
154
+ apiUrl?: string;
155
+ apiKey?: string; // API key: ship- prefix + 64-char hex (69 chars total)
156
+ signal?: AbortSignal; // Cancellation
157
+ subdomain?: string; // Custom subdomain
158
+ onCancel?: () => void;
159
+ onProgress?: (progress: number) => void;
160
+ progress?: (stats: ProgressStats) => void;
161
+ maxConcurrency?: number;
162
+ timeout?: number;
163
+ stripCommonPrefix?: boolean; // Remove common path prefix
164
+ }
165
+ ```
166
+
167
+ ### Environment-Specific Examples
168
+
169
+ #### Node.js File Deployment
170
+
171
+ ```typescript
172
+ import { Ship } from '@shipstatic/ship';
173
+
174
+ const ship = new Ship({
175
+ apiUrl: 'https://api.shipstatic.com',
176
+ apiKey: process.env.SHIP_API_KEY // ship-abc123...
177
+ });
178
+
179
+ // Deploy files and directories
180
+ const result = await ship.deployments.create([
181
+ './dist/index.html',
182
+ './dist/assets',
183
+ './public'
184
+ ], {
185
+ stripCommonPrefix: true,
186
+ onProgress: (progress) => {
187
+ console.log(`Deployment: ${progress}% complete`);
188
+ }
189
+ });
190
+
191
+ console.log(`✅ Deployed: ${result.deployment}`);
192
+ ```
193
+
194
+ #### Browser File Upload
195
+
196
+ ```typescript
197
+ import { Ship } from '@shipstatic/ship';
198
+
199
+ const ship = new Ship({
200
+ apiUrl: 'https://api.shipstatic.com',
201
+ apiKey: 'ship-your-64-char-hex-string' // 69 chars total
202
+ });
203
+
204
+ // From file input
205
+ const fileInput = document.getElementById('fileInput') as HTMLInputElement;
206
+ const result = await ship.deployments.create(fileInput, {
207
+ onProgress: (progress) => {
208
+ document.getElementById('progress').textContent = `${progress}%`;
209
+ }
210
+ });
211
+
212
+ // From File objects
213
+ const files: File[] = Array.from(fileInput.files || []);
214
+ const result2 = await ship.deployments.create(files);
215
+ ```
216
+
217
+ ## Unified Error Handling
218
+
219
+ The Ship SDK uses a unified error system with a single `ShipError` class:
220
+
221
+ ```typescript
222
+ import { ShipError } from '@shipstatic/ship';
223
+
224
+ try {
225
+ await ship.deployments.create(['./dist']);
226
+ } catch (error) {
227
+ if (error instanceof ShipError) {
228
+ console.error(`Ship Error: ${error.message}`);
229
+
230
+ // Type-safe error checking
231
+ if (error.isClientError()) {
232
+ console.error(`Client Error: ${error.message}`);
233
+ } else if (error.isNetworkError()) {
234
+ console.error(`Network Error: ${error.message}`);
235
+ } else if (error.isAuthError()) {
236
+ console.error(`Auth Error: ${error.message}`);
237
+ } else if (error.isValidationError()) {
238
+ console.error(`Validation Error: ${error.message}`);
239
+ }
240
+ }
241
+ }
242
+ ```
243
+
244
+ ### Error Types
245
+
246
+ ```typescript
247
+ // Factory methods for creating errors
248
+ ShipError.validation(message, details) // Validation failed (400)
249
+ ShipError.notFound(resource, id) // Resource not found (404)
250
+ ShipError.rateLimit(message) // Rate limit exceeded (429)
251
+ ShipError.authentication(message) // Authentication required (401)
252
+ ShipError.business(message, status) // Business logic error (400)
253
+ ShipError.network(message, cause) // Network/connection error
254
+ ShipError.cancelled(message) // Operation was cancelled
255
+ ShipError.file(message, filePath) // File operation error
256
+ ShipError.config(message) // Configuration error
257
+
258
+ // Type checking methods
259
+ error.isClientError() // Client-side errors
260
+ error.isNetworkError() // Network/connection issues
261
+ error.isAuthError() // Authentication problems
262
+ error.isValidationError() // Input validation failures
263
+ error.isFileError() // File operation errors
264
+ error.isConfigError() // Configuration problems
265
+ ```
266
+
267
+ ## Authentication
268
+
269
+ The Ship SDK uses **Bearer token authentication** with API keys that have a `ship-` prefix:
270
+
271
+ ```typescript
272
+ const ship = new Ship({
273
+ apiKey: 'ship-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
274
+ });
275
+ ```
276
+
277
+ ### API Requests
278
+
279
+ The SDK automatically sends API keys using standard Bearer token format:
280
+
281
+ ```
282
+ Authorization: Bearer ship-your-64-char-hex-string // 69 chars total
283
+ ```
284
+
285
+ ## Configuration
286
+
287
+ Configuration is loaded hierarchically (highest precedence first):
288
+
289
+ 1. **Constructor options** - Direct parameters to `new Ship()`
290
+ 2. **Environment variables** - `SHIP_API_URL`, `SHIP_API_KEY` (Node.js only)
291
+ 3. **Config files** - `.shiprc` or `package.json` (ship key) in project directory (Node.js only)
292
+
293
+ ### Config File Format
294
+
295
+ **.shiprc:**
296
+ ```json
297
+ {
298
+ "apiUrl": "https://api.shipstatic.com",
299
+ "apiKey": "ship-your-api-key"
300
+ }
301
+ ```
302
+
303
+ **package.json:**
304
+ ```json
305
+ {
306
+ "ship": {
307
+ "apiUrl": "https://api.shipstatic.com",
308
+ "apiKey": "ship-your-api-key"
309
+ }
310
+ }
311
+ ```
312
+
313
+ ### Environment Variables
314
+
315
+ ```bash
316
+ export SHIP_API_URL="https://api.shipstatic.com"
317
+ export SHIP_API_KEY="ship-your-api-key"
318
+ ```
319
+
320
+ ## CLI Commands
321
+
322
+ ### Deployment Commands
323
+
324
+ ```bash
325
+ ship # List deployments (no args)
326
+ ship ./dist # Deploy specific directory
327
+ ship deploy ./build # Explicit deploy command
328
+ ship list # List deployments
329
+ ship get abc123 # Get deployment details
330
+ ship remove abc123 # Remove deployment
331
+ ```
332
+
333
+ ### Alias Commands
334
+
335
+ ```bash
336
+ ship aliases # List aliases
337
+ ship alias staging abc123 # Set alias to deployment
338
+ ```
339
+
340
+ ### Account Commands
341
+
342
+ ```bash
343
+ ship account # Get account details
344
+ ```
345
+
346
+ ### Global Options
347
+
348
+ ```bash
349
+ -u, --apiUrl <URL> # API endpoint
350
+ -k, --apiKey <KEY> # API key
351
+ --json # JSON output
352
+ ```
353
+
354
+ ## Bundle Sizes
355
+
356
+ **Optimized for production:**
357
+ - **Node.js**: 16KB (ESM), 17KB (CJS)
358
+ - **Browser**: 275KB (ESM with dependencies)
359
+ - **CLI**: 25KB (CJS)
360
+
361
+ **Recent Optimizations:**
362
+ - ✅ **Unified error system** - Single `ShipError` class for all components
363
+ - ✅ **Dynamic platform configuration** - Fetches limits from API
364
+ - ✅ **Replaced axios with native fetch** - Bundle size reduction
365
+ - ✅ **Simplified configuration loading** - Removed async complexity
366
+ - ✅ **Streamlined multipart uploads** - `files[]` + JSON checksums format
367
+ - ✅ **Direct validation throwing** - Eliminated verbose ValidationResult pattern
368
+
369
+ ## TypeScript Support
370
+
371
+ Full TypeScript support with exported types from shared `@shipstatic/types`:
372
+
373
+ ```typescript
374
+ import type {
375
+ ShipOptions,
376
+ NodeDeployInput,
377
+ BrowserDeployInput,
378
+ DeployOptions,
379
+ DeploySuccessResponse,
380
+ ProgressStats,
381
+ StaticFile,
382
+ ShipError,
383
+ ErrorType
384
+ } from '@shipstatic/ship';
385
+ ```
386
+
387
+ ## Architecture
388
+
389
+ ### Modern SDK Design
390
+ - **Class-based API**: `new Ship()` with resource properties
391
+ - **Environment detection**: Automatic Node.js/Browser optimizations
392
+ - **Native dependencies**: Uses built-in `fetch`, `crypto`, and `fs` APIs
393
+ - **Type safety**: Strict TypeScript with comprehensive error types
394
+ - **Dynamic configuration**: Platform limits fetched from API
395
+ - **Unified DTOs**: Shared type definitions from `@shipstatic/types`
396
+
397
+ ### Codebase Organization
398
+ ```
399
+ src/
400
+ ├── core/ # Cross-cutting concerns
401
+ │ ├── config.ts # Configuration loading and merging
402
+ │ └── constants.ts # Platform constants and defaults
403
+ ├── lib/ # Utility libraries (renamed from utils/)
404
+ │ ├── env.js # Environment detection
405
+ │ ├── node-files.ts # Node.js file system operations
406
+ │ ├── prepare-input.ts # Input preparation (renamed from input-conversion.ts)
407
+ │ └── path.ts # Path utilities (renamed from path-helpers.ts)
408
+ ├── cli.ts # CLI implementation (moved from cli/index.ts)
409
+ ├── index.ts # Main SDK exports
410
+ └── types.ts # All SDK types (consolidated from types/)
411
+
412
+ ### File Processing Pipeline
413
+ **Node.js:**
414
+ ```
415
+ File Paths → Discovery → Junk Filtering → Base Directory → Content Processing → StaticFile[]
416
+ ```
417
+
418
+ **Browser:**
419
+ ```
420
+ File Objects → Path Extraction → Junk Filtering → Content Processing → StaticFile[]
421
+ ```
422
+
423
+ ### Configuration System
424
+ - **Synchronous loading**: No async complexity for file config
425
+ - **Dynamic platform config**: Fetched from API on first use
426
+ - **Minimal search**: Only `.shiprc` and `package.json`
427
+ - **Simple validation**: Native type checking
428
+ - **Environment variables**: `SHIP_*` prefix for clarity
429
+
430
+ ### Error System Architecture
431
+ - **Single source of truth**: All errors use `ShipError` from `@shipstatic/types`
432
+ - **Type-safe factories**: Specific factory methods for each error type
433
+ - **Wire format support**: Automatic serialization/deserialization
434
+ - **Helper methods**: Easy type checking with `is*Error()` methods
435
+
436
+ ## Development Status
437
+
438
+ This is an **unlaunched project** optimized for modern development:
439
+
440
+ - ✅ **Deployment Resource**: Full implementation (create, list, get, remove)
441
+ - ✅ **Alias Resource**: Full implementation (set, get, list, remove)
442
+ - ✅ **Account Resource**: Full implementation (get account details)
443
+ - ✅ **Unified Error System**: Single `ShipError` class with factory methods
444
+ - ✅ **Dynamic Platform Config**: Automatic limit fetching from API
445
+ - ✅ **Ultra-Simple CLI**: Deploy shortcut + explicit commands
446
+ - ✅ **Streamlined Multipart**: `files[]` array + JSON checksums format
447
+ - ✅ **Direct Validation**: Functions throw errors instead of returning results
448
+ - ✅ **Shared DTOs**: All types from `@shipstatic/types` package
449
+ - ✅ **Impossible Simplicity**: Maximum functionality with minimal complexity
450
+ - 🎯 No legacy compatibility constraints
451
+ - 🔧 Native fetch API for optimal performance
452
+ - ⚡ Modern ESM modules with TypeScript
453
+
454
+ ## Testing
455
+
456
+ Comprehensive test coverage with modern tooling:
457
+
458
+ ```bash
459
+ # Run all tests
460
+ pnpm test --run
461
+
462
+ # Run specific test suites
463
+ pnpm test tests/utils/node-files.test.ts --run
464
+ pnpm test tests/api/http.test.ts --run
465
+
466
+ # Build and test
467
+ pnpm build && pnpm test --run
468
+ ```
469
+
470
+ **Test Organization:**
471
+ - **Unit tests**: Pure function testing
472
+ - **Integration tests**: Component interaction testing
473
+ - **Edge case tests**: Boundary condition testing
474
+ - **Browser tests**: FileList and File object handling
475
+ - **Node.js tests**: Filesystem and path manipulation
476
+ - **Error tests**: Unified error handling patterns
477
+
478
+ **Current Status:** 264 tests passing ✅
479
+
480
+ ## Contributing
481
+
482
+ The codebase prioritizes simplicity and maintainability:
483
+
484
+ - **"Do More with Less"** - Built-in over dependencies
485
+ - **No backward compatibility** constraints
486
+ - **Modern ES modules** and TypeScript
487
+ - **Comprehensive test coverage**
488
+ - **Clean resource-based** architecture
489
+ - **Unified error handling** across all components
490
+ - **Shared type system** via `@shipstatic/types`
491
+ - **Declarative code patterns** over imperative complexity
492
+
493
+ ---
494
+
495
+ **Ship** - Deploy static files with modern tooling ⚡
@@ -0,0 +1,2 @@
1
+ import{e as a,f as b}from"./chunk-6KASX3WY.js";import"./chunk-OJ6FY5CZ.js";export{b as findBrowserCommonParentDirectory,a as processFilesForBrowser};
2
+ //# sourceMappingURL=browser-files-57AJ3XWD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ import{e as a,f as b}from"./chunk-WJA55FEC.browser.js";import"./chunk-Z566D7DF.browser.js";import"./chunk-KBFFWP3K.browser.js";export{b as findBrowserCommonParentDirectory,a as processFilesForBrowser};
2
+ //# sourceMappingURL=browser-files-DMOO6YMM.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ import{e as v,h as w}from"./chunk-OJ6FY5CZ.js";var g=null;function S(e){g=e}function b(){return typeof process<"u"&&process.versions&&process.versions.node?"node":typeof window<"u"||typeof self<"u"?"browser":"unknown"}function p(){return g||b()}import{ShipError as u}from"@shipstatic/types";async function E(e){let n=(await import("spark-md5")).default;return new Promise((o,s)=>{let f=Math.ceil(e.size/2097152),r=0,l=new n.ArrayBuffer,m=new FileReader,d=()=>{let c=r*2097152,t=Math.min(c+2097152,e.size);m.readAsArrayBuffer(e.slice(c,t))};m.onload=c=>{let t=c.target?.result;if(!t){s(u.business("Failed to read file chunk"));return}l.append(t),r++,r<f?d():o({md5:l.end()})},m.onerror=()=>{s(u.business("Failed to calculate MD5: FileReader error"))},d()})}async function P(e){let n=await import("crypto");if(Buffer.isBuffer(e)){let s=n.createHash("md5");return s.update(e),{md5:s.digest("hex")}}let o=await import("fs");return new Promise((s,a)=>{let f=n.createHash("md5"),r=o.createReadStream(e);r.on("error",l=>a(u.business(`Failed to read file for MD5: ${l.message}`))),r.on("data",l=>f.update(l)),r.on("end",()=>s({md5:f.digest("hex")}))})}async function y(e){let n=p();if(n==="browser"){if(!(e instanceof Blob))throw u.business("Invalid input for browser MD5 calculation: Expected Blob or File.");return E(e)}if(n==="node"){if(!(Buffer.isBuffer(e)||typeof e=="string"))throw u.business("Invalid input for Node.js MD5 calculation: Expected Buffer or file path string.");return P(e)}throw u.business("Unknown or unsupported execution environment for MD5 calculation.")}import{ShipError as B}from"@shipstatic/types";import{isJunk as D}from"junk";var k=["__MACOSX",".Trashes",".fseventsd",".Spotlight-V100"];function x(e){return!e||e.length===0?[]:e.filter(n=>{if(!n)return!1;let o=n.replace(/\\/g,"/").split("/").filter(Boolean);if(o.length===0)return!0;let s=o[o.length-1];if(D(s))return!1;let a=o.slice(0,-1);for(let f of a)if(k.some(r=>f.toLowerCase()===r.toLowerCase()))return!1;return!0})}async function H(e,n={}){if(p()!=="browser")throw B.business("processFilesForBrowser can only be called in a browser environment.");let{explicitBaseDirInput:o,stripCommonPrefix:s}=n,a=[],f=Array.isArray(e)?e:Array.from(e),r="";s?r=F(e):o&&(r=o);for(let t of f){let i=t.webkitRelativePath||t.name;if(r){i=w(i);let h=r.endsWith("/")?r:`${r}/`;(i===r||i===h||i.startsWith(h))&&(i=i.substring(h.length))}i=w(i),a.push({file:t,relativePath:i})}let l=a.map(t=>t.relativePath),m=x(l),d=new Set(m),c=[];for(let t of a){if(!d.has(t.relativePath)||t.file.size===0)continue;let{md5:i}=await y(t.file);c.push({content:t.file,path:t.relativePath,size:t.file.size,md5:i})}return c}function F(e){if(p()!=="browser")throw B.business("findBrowserCommonParentDirectory can only be called in a browser environment.");if(!e||e.length===0)return"";let n=Array.from(e).map(o=>o.webkitRelativePath);return n.some(o=>!o)?"":v(n,"/")}export{S as a,p as b,y as c,x as d,H as e,F as f};
2
+ //# sourceMappingURL=chunk-6KASX3WY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/env.ts","../src/lib/md5.ts","../src/lib/browser-files.ts","../src/lib/junk.ts"],"sourcesContent":["/**\n * @file Environment detection utilities for the Ship SDK.\n * Helps in determining whether the SDK is running in a Node.js, browser, or unknown environment.\n */\n\n/**\n * Represents the detected or simulated JavaScript execution environment.\n */\nexport type ExecutionEnvironment = 'browser' | 'node' | 'unknown';\n\n/** @internal Environment override for testing. */\nlet _testEnvironment: ExecutionEnvironment | null = null;\n\n/**\n * **FOR TESTING PURPOSES ONLY.**\n *\n * Allows tests to override the detected environment, forcing the SDK to behave\n * as if it's running in the specified environment.\n *\n * @param env - The environment to simulate ('node', 'browser', 'unknown'),\n * or `null` to clear the override and revert to actual environment detection.\n * @internal\n */\nexport function __setTestEnvironment(env: ExecutionEnvironment | null): void {\n _testEnvironment = env;\n}\n\n/**\n * Detects the actual JavaScript execution environment (Node.js, browser, or unknown)\n * by checking for characteristic global objects.\n * @returns The detected environment as {@link ExecutionEnvironment}.\n * @internal\n */\nfunction detectEnvironment(): ExecutionEnvironment {\n // Check for Node.js environment\n if (typeof process !== 'undefined' && process.versions && process.versions.node) {\n return 'node';\n }\n\n // Check for Browser environment (including Web Workers)\n if (typeof window !== 'undefined' || typeof self !== 'undefined') {\n return 'browser';\n }\n\n return 'unknown';\n}\n\n/**\n * Gets the current effective execution environment.\n *\n * This function first checks if a test environment override is active via {@link __setTestEnvironment}.\n * If not, it detects the actual environment (Node.js, browser, or unknown).\n *\n * @returns The current execution environment: 'browser', 'node', or 'unknown'.\n * @public\n */\nexport function getENV(): ExecutionEnvironment {\n // Return test override if set\n if (_testEnvironment) {\n return _testEnvironment;\n }\n \n // Detect actual environment\n return detectEnvironment();\n}\n","/**\n * @file Simplified MD5 calculation utility with separate environment handlers.\n */\nimport { getENV } from './env';\nimport { ShipError } from '@shipstatic/types';\n\nexport interface MD5Result {\n md5: string;\n}\n\n/**\n * Browser-specific MD5 calculation for Blob/File objects\n */\nasync function calculateMD5Browser(blob: Blob): Promise<MD5Result> {\n const SparkMD5 = (await import('spark-md5')).default;\n \n return new Promise((resolve, reject) => {\n const chunkSize = 2097152; // 2MB chunks\n const chunks = Math.ceil(blob.size / chunkSize);\n let currentChunk = 0;\n const spark = new SparkMD5.ArrayBuffer();\n const fileReader = new FileReader();\n\n const loadNext = () => {\n const start = currentChunk * chunkSize;\n const end = Math.min(start + chunkSize, blob.size);\n fileReader.readAsArrayBuffer(blob.slice(start, end));\n };\n\n fileReader.onload = (e) => {\n const result = e.target?.result as ArrayBuffer;\n if (!result) {\n reject(ShipError.business('Failed to read file chunk'));\n return;\n }\n \n spark.append(result);\n currentChunk++;\n \n if (currentChunk < chunks) {\n loadNext();\n } else {\n resolve({ md5: spark.end() });\n }\n };\n\n fileReader.onerror = () => {\n reject(ShipError.business('Failed to calculate MD5: FileReader error'));\n };\n\n loadNext();\n });\n}\n\n/**\n * Node.js-specific MD5 calculation for Buffer or file path\n */\nasync function calculateMD5Node(input: Buffer | string): Promise<MD5Result> {\n const crypto = await import('crypto');\n \n if (Buffer.isBuffer(input)) {\n const hash = crypto.createHash('md5');\n hash.update(input);\n return { md5: hash.digest('hex') };\n }\n \n // Handle file path\n const fs = await import('fs');\n return new Promise((resolve, reject) => {\n const hash = crypto.createHash('md5');\n const stream = fs.createReadStream(input);\n \n stream.on('error', err => \n reject(ShipError.business(`Failed to read file for MD5: ${err.message}`))\n );\n stream.on('data', chunk => hash.update(chunk));\n stream.on('end', () => resolve({ md5: hash.digest('hex') }));\n });\n}\n\n/**\n * Unified MD5 calculation that delegates to environment-specific handlers\n */\nexport async function calculateMD5(input: Blob | Buffer | string): Promise<MD5Result> {\n const env = getENV();\n \n if (env === 'browser') {\n if (!(input instanceof Blob)) {\n throw ShipError.business('Invalid input for browser MD5 calculation: Expected Blob or File.');\n }\n return calculateMD5Browser(input);\n }\n \n if (env === 'node') {\n if (!(Buffer.isBuffer(input) || typeof input === 'string')) {\n throw ShipError.business('Invalid input for Node.js MD5 calculation: Expected Buffer or file path string.');\n }\n return calculateMD5Node(input);\n }\n \n throw ShipError.business('Unknown or unsupported execution environment for MD5 calculation.');\n}\n","/**\n * @file Browser-specific file utilities for the Ship SDK.\n * Provides helpers for processing browser files into deploy-ready objects and extracting common directory info.\n */\nimport { getENV } from './env.js';\nimport { StaticFile } from '../types.js';\nimport { calculateMD5 } from './md5.js';\nimport { ShipError } from '@shipstatic/types';\nimport { findCommonParentDirectory, normalizeWebPath } from './path.js';\nimport { filterJunk } from './junk.js';\n\n\n/**\n * Internal structure representing a browser file to be processed for deploy.\n * @internal\n */\ninterface BrowserFileProcessItem {\n file: File;\n relativePath: string;\n}\n\n/**\n * Processes browser files (FileList or File[]) into an array of StaticFile objects ready for deploy.\n * Calculates MD5, filters junk files, and determines relative paths (stripping basePath if provided).\n *\n * @param browserFiles - FileList or File[] to process for deploy.\n * @param options - Optional processing options (basePath for path stripping, stripCommonPrefix).\n * @returns Promise resolving to an array of StaticFile objects.\n * @throws {ShipClientError} If called outside a browser or with invalid input.\n */\nexport async function processFilesForBrowser(\n browserFiles: FileList | File[],\n options: { explicitBaseDirInput?: string; stripCommonPrefix?: boolean } = {}\n): Promise<StaticFile[]> {\n if (getENV() !== 'browser') {\n throw ShipError.business('processFilesForBrowser can only be called in a browser environment.');\n }\n\n const { explicitBaseDirInput, stripCommonPrefix } = options;\n const initialFileInfos: BrowserFileProcessItem[] = [];\n const filesArray = Array.isArray(browserFiles) ? browserFiles : Array.from(browserFiles);\n \n // If stripCommonPrefix is true and no explicit basePath is provided,\n // Determine the parent directory for path stripping if applicable\n let parentDir = '';\n if (stripCommonPrefix) {\n parentDir = findBrowserCommonParentDirectory(browserFiles);\n } else if (explicitBaseDirInput) {\n parentDir = explicitBaseDirInput;\n }\n\n // Prepare the initial file information with appropriate relative paths\n for (const file of filesArray) {\n let relativePath = (file as any).webkitRelativePath || file.name;\n if (parentDir) {\n // Normalize all paths to use forward slashes\n relativePath = normalizeWebPath(relativePath);\n const basePathWithSlash = parentDir.endsWith('/') ? parentDir : `${parentDir}/`;\n // Robustly strip deeply nested basePath prefix\n if (relativePath === parentDir || relativePath === basePathWithSlash || relativePath.startsWith(basePathWithSlash)) {\n relativePath = relativePath.substring(basePathWithSlash.length);\n }\n }\n // Always normalize output path to forward slashes\n relativePath = normalizeWebPath(relativePath);\n initialFileInfos.push({ file, relativePath });\n }\n\n // Filter out junk files\n const allRelativePaths = initialFileInfos.map(info => info.relativePath);\n const nonJunkRelativePathsArray = filterJunk(allRelativePaths);\n const nonJunkRelativePathsSet = new Set(nonJunkRelativePathsArray);\n\n // Create StaticFile objects for each valid file\n const result: StaticFile[] = [];\n for (const fileInfo of initialFileInfos) {\n // Skip junk files and empty files\n if (!nonJunkRelativePathsSet.has(fileInfo.relativePath) || fileInfo.file.size === 0) {\n continue;\n }\n \n // Calculate MD5 hash\n const { md5 } = await calculateMD5(fileInfo.file);\n \n // Create and add the StaticFile\n result.push({\n content: fileInfo.file,\n path: fileInfo.relativePath,\n size: fileInfo.file.size,\n md5,\n });\n }\n \n return result;\n}\n\n/**\n * Finds the common parent directory from a FileList or File[] using webkitRelativePath.\n * Useful for stripping a common prefix if files are selected from a single folder.\n *\n * @param files - FileList or File[] to analyze.\n * @returns Common parent directory string, or empty string if not consistent.\n * @throws {ShipClientError} If called outside a browser.\n */\nexport function findBrowserCommonParentDirectory(files: FileList | File[]): string {\n if (getENV() !== 'browser') {\n throw ShipError.business('findBrowserCommonParentDirectory can only be called in a browser environment.');\n }\n if (!files || files.length === 0) return '';\n \n const paths: (string | null | undefined)[] = Array.from(files)\n .map(file => (file as any).webkitRelativePath);\n\n // If any file is missing webkitRelativePath, we can't determine a common parent.\n if (paths.some(p => !p)) {\n return '';\n }\n\n return findCommonParentDirectory(paths as string[], '/');\n}\n","/**\n * @file Utility for filtering out junk files and directories from file paths\n * \n * This module provides functionality to filter out common system junk files and directories\n * from a list of file paths. It uses the 'junk' package to identify junk filenames and\n * a custom list to filter out common junk directories.\n */\nimport { isJunk } from 'junk';\n\n/**\n * List of directory names considered as junk\n * \n * Files within these directories (at any level in the path hierarchy) will be excluded.\n * The comparison is case-insensitive for cross-platform compatibility.\n * \n * @internal\n */\nexport const JUNK_DIRECTORIES = [\n '__MACOSX',\n '.Trashes',\n '.fseventsd',\n '.Spotlight-V100',\n] as const;\n\n/**\n * Filters an array of file paths, removing those considered junk\n * \n * A path is filtered out if either:\n * 1. The basename is identified as junk by the 'junk' package (e.g., .DS_Store, Thumbs.db)\n * 2. Any directory segment in the path matches an entry in JUNK_DIRECTORIES (case-insensitive)\n *\n * All path separators are normalized to forward slashes for consistent cross-platform behavior.\n * \n * @param filePaths - An array of file path strings to filter\n * @returns A new array containing only non-junk file paths\n */\nexport function filterJunk(filePaths: string[]): string[] {\n if (!filePaths || filePaths.length === 0) {\n return [];\n }\n\n return filePaths.filter(filePath => {\n if (!filePath) {\n return false; // Exclude null or undefined paths\n }\n\n // Normalize path separators to forward slashes and split into segments\n const parts = filePath.replace(/\\\\/g, '/').split('/').filter(Boolean);\n if (parts.length === 0) return true;\n \n // Check if the basename is a junk file (using junk package)\n const basename = parts[parts.length - 1];\n if (isJunk(basename)) {\n return false;\n }\n\n // Check if any directory segment is in our junk directories list\n const directorySegments = parts.slice(0, -1);\n for (const segment of directorySegments) {\n if (JUNK_DIRECTORIES.some(junkDir => \n segment.toLowerCase() === junkDir.toLowerCase())) {\n return false;\n }\n }\n\n return true;\n });\n}\n"],"mappings":"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","names":["_testEnvironment","__setTestEnvironment","env","detectEnvironment","getENV","ShipError","calculateMD5Browser","blob","SparkMD5","resolve","reject","chunks","currentChunk","spark","fileReader","loadNext","start","end","e","result","calculateMD5Node","input","crypto","hash","fs","stream","err","chunk","calculateMD5","env","getENV","ShipError","isJunk","JUNK_DIRECTORIES","filterJunk","filePaths","filePath","parts","basename","directorySegments","segment","junkDir","processFilesForBrowser","browserFiles","options","getENV","ShipError","explicitBaseDirInput","stripCommonPrefix","initialFileInfos","filesArray","parentDir","findBrowserCommonParentDirectory","file","relativePath","normalizeWebPath","basePathWithSlash","allRelativePaths","info","nonJunkRelativePathsArray","filterJunk","nonJunkRelativePathsSet","result","fileInfo","md5","calculateMD5","files","paths","p","findCommonParentDirectory"]}
@@ -0,0 +1,2 @@
1
+ var i=Object.create;var g=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty;var h=a=>{throw TypeError(a)};var o=(a=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(a,{get:(b,c)=>(typeof require<"u"?require:b)[c]}):a)(function(a){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+a+'" is not supported')});var p=(a,b)=>()=>(b||a((b={exports:{}}).exports,b),b.exports);var e=(a,b,c,f)=>{if(b&&typeof b=="object"||typeof b=="function")for(let d of k(b))!m.call(a,d)&&d!==c&&g(a,d,{get:()=>b[d],enumerable:!(f=j(b,d))||f.enumerable});return a},q=(a,b,c)=>(e(a,b,"default"),c&&e(c,b,"default")),r=(a,b,c)=>(c=a!=null?i(l(a)):{},e(b||!a||!a.__esModule?g(c,"default",{value:a,enumerable:!0}):c,a));var n=(a,b,c)=>b.has(a)||h("Cannot "+c);var s=(a,b,c)=>b.has(a)?h("Cannot add the same private member more than once"):b instanceof WeakSet?b.add(a):b.set(a,c);var t=(a,b,c)=>(n(a,b,"access private method"),c);export{o as a,p as b,q as c,r as d,s as e,t as f};
2
+ //# sourceMappingURL=chunk-KBFFWP3K.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ var x=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var h=n=>{throw TypeError(n)};var g=(n=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(n,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):n)(function(n){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+n+'" is not supported')});var a=(n,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of P(t))!p.call(n,e)&&e!==r&&x(n,e,{get:()=>t[e],enumerable:!(i=m(t,e))||i.enumerable});return n},b=(n,t,r)=>(a(n,t,"default"),r&&a(r,t,"default"));var y=(n,t,r)=>t.has(n)||h("Cannot "+r);var S=(n,t,r)=>t.has(n)?h("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(n):t.set(n,r);var q=(n,t,r)=>(y(n,t,"access private method"),r);function d(n,t){if(!n||!Array.isArray(n)||n.length===0)return"";let r=n.filter(o=>o&&typeof o=="string"&&o.includes(t));if(r.length===0)return"";if(r.length===1){let o=r[0],c=o.lastIndexOf(t);return c>-1?o.substring(0,c):""}let i=[...r].sort(),e=i[0],l=i[i.length-1],s=0;for(;s<e.length&&s<l.length&&e[s]===l[s];)s++;let u=e.substring(0,s);if(u.endsWith(t))return u.slice(0,-1);let f=u.lastIndexOf(t);return f>-1?u.substring(0,f):""}function z(n){if(typeof g>"u")return d(n,"/");let t=g("path");return d(n,t.sep)}function A(n){return n.replace(/\\/g,"/")}function I(n){return n.replace(/\\/g,"/").replace(/^\/+/,"")}function C(n){return I(n)}export{g as a,b,S as c,q as d,d as e,z as f,A as g,I as h,C as i};
2
+ //# sourceMappingURL=chunk-OJ6FY5CZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/path.ts"],"sourcesContent":["/**\n * @file Path helper utilities that work in both browser and Node.js environments.\n * Provides environment-agnostic path manipulation functions.\n */\n\n/**\n * Finds the common parent directory from an array of paths.\n * This function is the single shared implementation to be used by both browser and Node.js environments.\n * \n * @param paths - Array of paths to analyze for common parent directory.\n * @param separator - Path separator character (e.g., '/' for browser, path.sep for Node.js).\n * @returns The common parent directory path, or an empty string if none is found.\n */\nexport function findCommonParentDirectory(paths: string[], separator: string): string {\n // Validate input\n if (!paths || !Array.isArray(paths) || paths.length === 0) {\n return '';\n }\n \n // Filter out empty paths and paths without separators\n const validPaths = paths.filter(p => p && typeof p === 'string' && p.includes(separator));\n \n if (validPaths.length === 0) {\n return '';\n }\n\n // Special case for single path: return the directory containing this path\n if (validPaths.length === 1) {\n const path = validPaths[0];\n const lastSepIndex = path.lastIndexOf(separator);\n if (lastSepIndex > -1) {\n // Return the directory part without the file name\n return path.substring(0, lastSepIndex);\n }\n return ''; // No directory part found\n }\n\n // Sort paths alphabetically to easily find the common prefix\n const sortedPaths = [...validPaths].sort();\n const firstPath = sortedPaths[0];\n const lastPath = sortedPaths[sortedPaths.length - 1];\n \n // Find the length of the common prefix\n let i = 0;\n while (i < firstPath.length && i < lastPath.length && firstPath[i] === lastPath[i]) {\n i++;\n }\n\n const commonPrefix = firstPath.substring(0, i);\n\n // The prefix must be a directory. If it doesn't end with a separator,\n // find the last separator to get the parent directory.\n if (commonPrefix.endsWith(separator)) {\n // It's a full directory path that matches, so return it without the trailing slash\n return commonPrefix.slice(0, -1);\n }\n\n const lastSepIndex = commonPrefix.lastIndexOf(separator);\n if (lastSepIndex > -1) {\n return commonPrefix.substring(0, lastSepIndex);\n }\n\n return ''; // No common directory\n}\n\n/**\n * Simple helper to find common parent of absolute paths using the system path separator.\n * More declarative wrapper around findCommonParentDirectory for Node.js usage.\n * @param absolutePaths - Array of absolute file paths\n * @returns Common parent directory path or empty string if none found\n */\nexport function findCommonParent(absolutePaths: string[]): string {\n if (typeof require === 'undefined') {\n // Browser environment - use forward slash\n return findCommonParentDirectory(absolutePaths, '/');\n }\n \n // Node.js environment - use system separator\n const path = require('path');\n return findCommonParentDirectory(absolutePaths, path.sep);\n}\n\n\n/**\n * Converts backslashes to forward slashes for cross-platform compatibility.\n * Does not remove leading slashes (preserves absolute paths).\n * @param path - The path to normalize\n * @returns Path with forward slashes\n */\nexport function normalizeSlashes(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\n/**\n * Normalizes a path for web usage by converting backslashes to forward slashes\n * and removing leading slashes.\n * @param path - The path to normalize\n * @returns Normalized path suitable for web deployment\n */\nexport function normalizeWebPath(path: string): string {\n return path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n}\n\n/**\n * Ensures a path is relative by normalizing it and removing any leading slashes.\n * @param path - The path to make relative\n * @returns Relative path suitable for web deployment\n */\nexport function ensureRelativePath(path: string): string {\n return normalizeWebPath(path);\n}\n"],"mappings":"i1BAaO,SAASA,EAA0BC,EAAiBC,EAA2B,CAEpF,GAAI,CAACD,GAAS,CAAC,MAAM,QAAQA,CAAK,GAAKA,EAAM,SAAW,EACtD,MAAO,GAIT,IAAME,EAAaF,EAAM,OAAOG,GAAKA,GAAK,OAAOA,GAAM,UAAYA,EAAE,SAASF,CAAS,CAAC,EAExF,GAAIC,EAAW,SAAW,EACxB,MAAO,GAIT,GAAIA,EAAW,SAAW,EAAG,CAC3B,IAAME,EAAOF,EAAW,CAAC,EACnBG,EAAeD,EAAK,YAAYH,CAAS,EAC/C,OAAII,EAAe,GAEVD,EAAK,UAAU,EAAGC,CAAY,EAEhC,EACT,CAGA,IAAMC,EAAc,CAAC,GAAGJ,CAAU,EAAE,KAAK,EACnCK,EAAYD,EAAY,CAAC,EACzBE,EAAWF,EAAYA,EAAY,OAAS,CAAC,EAG/CG,EAAI,EACR,KAAOA,EAAIF,EAAU,QAAUE,EAAID,EAAS,QAAUD,EAAUE,CAAC,IAAMD,EAASC,CAAC,GAC/EA,IAGF,IAAMC,EAAeH,EAAU,UAAU,EAAGE,CAAC,EAI7C,GAAIC,EAAa,SAAST,CAAS,EAEjC,OAAOS,EAAa,MAAM,EAAG,EAAE,EAGjC,IAAML,EAAeK,EAAa,YAAYT,CAAS,EACvD,OAAII,EAAe,GACVK,EAAa,UAAU,EAAGL,CAAY,EAGxC,EACT,CAQO,SAASM,EAAiBC,EAAiC,CAChE,GAAI,OAAOC,EAAY,IAErB,OAAOd,EAA0Ba,EAAe,GAAG,EAIrD,IAAMR,EAAO,EAAQ,MAAM,EAC3B,OAAOL,EAA0Ba,EAAeR,EAAK,GAAG,CAC1D,CASO,SAASU,EAAiBV,EAAsB,CACrD,OAAOA,EAAK,QAAQ,MAAO,GAAG,CAChC,CAQO,SAASW,EAAiBX,EAAsB,CACrD,OAAOA,EAAK,QAAQ,MAAO,GAAG,EAAE,QAAQ,OAAQ,EAAE,CACpD,CAOO,SAASY,EAAmBZ,EAAsB,CACvD,OAAOW,EAAiBX,CAAI,CAC9B","names":["findCommonParentDirectory","paths","separator","validPaths","p","path","lastSepIndex","sortedPaths","firstPath","lastPath","i","commonPrefix","findCommonParent","absolutePaths","__require","normalizeSlashes","normalizeWebPath","ensureRelativePath"]}
@@ -0,0 +1,2 @@
1
+ import{a as x,d as w}from"./chunk-Z566D7DF.browser.js";var g=null;function R(e){g=e}function B(){return typeof process<"u"&&process.versions&&process.versions.node?"node":typeof window<"u"||typeof self<"u"?"browser":"unknown"}function p(){return g||B()}import{ShipError as u}from"@shipstatic/types";async function k(e){let n=(await import("./spark-md5-DLLZAUJQ.browser.js")).default;return new Promise((o,s)=>{let c=Math.ceil(e.size/2097152),t=0,l=new n.ArrayBuffer,m=new FileReader,d=()=>{let f=t*2097152,r=Math.min(f+2097152,e.size);m.readAsArrayBuffer(e.slice(f,r))};m.onload=f=>{let r=f.target?.result;if(!r){s(u.business("Failed to read file chunk"));return}l.append(r),t++,t<c?d():o({md5:l.end()})},m.onerror=()=>{s(u.business("Failed to calculate MD5: FileReader error"))},d()})}async function E(e){let n=await import("crypto");if(Buffer.isBuffer(e)){let s=n.createHash("md5");return s.update(e),{md5:s.digest("hex")}}let o=await import("fs");return new Promise((s,a)=>{let c=n.createHash("md5"),t=o.createReadStream(e);t.on("error",l=>a(u.business(`Failed to read file for MD5: ${l.message}`))),t.on("data",l=>c.update(l)),t.on("end",()=>s({md5:c.digest("hex")}))})}async function v(e){let n=p();if(n==="browser"){if(!(e instanceof Blob))throw u.business("Invalid input for browser MD5 calculation: Expected Blob or File.");return k(e)}if(n==="node"){if(!(Buffer.isBuffer(e)||typeof e=="string"))throw u.business("Invalid input for Node.js MD5 calculation: Expected Buffer or file path string.");return E(e)}throw u.business("Unknown or unsupported execution environment for MD5 calculation.")}import{ShipError as D}from"@shipstatic/types";var S=["^npm-debug\\.log$","^\\..*\\.swp$","^\\.DS_Store$","^\\.AppleDouble$","^\\.LSOverride$","^Icon\\r$","^\\._.*","^\\.Spotlight-V100(?:$|\\/)","\\.Trashes","^__MACOSX$","~$","^Thumbs\\.db$","^ehthumbs\\.db$","^[Dd]esktop\\.ini$","@eaDir$"],P=new RegExp(S.join("|"));function b(e){return P.test(e)}var F=["__MACOSX",".Trashes",".fseventsd",".Spotlight-V100"];function y(e){return!e||e.length===0?[]:e.filter(n=>{if(!n)return!1;let o=n.replace(/\\/g,"/").split("/").filter(Boolean);if(o.length===0)return!0;let s=o[o.length-1];if(b(s))return!1;let a=o.slice(0,-1);for(let c of a)if(F.some(t=>c.toLowerCase()===t.toLowerCase()))return!1;return!0})}async function H(e,n={}){if(p()!=="browser")throw D.business("processFilesForBrowser can only be called in a browser environment.");let{explicitBaseDirInput:o,stripCommonPrefix:s}=n,a=[],c=Array.isArray(e)?e:Array.from(e),t="";s?t=M(e):o&&(t=o);for(let r of c){let i=r.webkitRelativePath||r.name;if(t){i=w(i);let h=t.endsWith("/")?t:`${t}/`;(i===t||i===h||i.startsWith(h))&&(i=i.substring(h.length))}i=w(i),a.push({file:r,relativePath:i})}let l=a.map(r=>r.relativePath),m=y(l),d=new Set(m),f=[];for(let r of a){if(!d.has(r.relativePath)||r.file.size===0)continue;let{md5:i}=await v(r.file);f.push({content:r.file,path:r.relativePath,size:r.file.size,md5:i})}return f}function M(e){if(p()!=="browser")throw D.business("findBrowserCommonParentDirectory can only be called in a browser environment.");if(!e||e.length===0)return"";let n=Array.from(e).map(o=>o.webkitRelativePath);return n.some(o=>!o)?"":x(n,"/")}export{R as a,p as b,v as c,y as d,H as e,M as f};
2
+ //# sourceMappingURL=chunk-WJA55FEC.browser.js.map