@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 +21 -0
- package/README.md +495 -0
- package/dist/browser-files-57AJ3XWD.js +2 -0
- package/dist/browser-files-57AJ3XWD.js.map +1 -0
- package/dist/browser-files-DMOO6YMM.browser.js +2 -0
- package/dist/browser-files-DMOO6YMM.browser.js.map +1 -0
- package/dist/chunk-6KASX3WY.js +2 -0
- package/dist/chunk-6KASX3WY.js.map +1 -0
- package/dist/chunk-KBFFWP3K.browser.js +2 -0
- package/dist/chunk-KBFFWP3K.browser.js.map +1 -0
- package/dist/chunk-OJ6FY5CZ.js +2 -0
- package/dist/chunk-OJ6FY5CZ.js.map +1 -0
- package/dist/chunk-WJA55FEC.browser.js +2 -0
- package/dist/chunk-WJA55FEC.browser.js.map +1 -0
- package/dist/chunk-Z566D7DF.browser.js +2 -0
- package/dist/chunk-Z566D7DF.browser.js.map +1 -0
- package/dist/cli.cjs +18 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/index.browser.js +20 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib-SI4AR47O.browser.js +6 -0
- package/dist/lib-SI4AR47O.browser.js.map +1 -0
- package/dist/path-3OU57R5J.browser.js +2 -0
- package/dist/path-3OU57R5J.browser.js.map +1 -0
- package/dist/path-6I7MXXVI.js +2 -0
- package/dist/path-6I7MXXVI.js.map +1 -0
- package/dist/spark-md5-DLLZAUJQ.browser.js +2 -0
- package/dist/spark-md5-DLLZAUJQ.browser.js.map +1 -0
- package/package.json +82 -0
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 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -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
|