@shipstatic/types 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @shipstatic/types
2
2
 
3
- Shared TypeScript types for the Shipstatic platform.
3
+ Shared TypeScript types for the ShipStatic platform.
4
4
 
5
5
  Single source of truth for types used across API, SDK, CLI, and web applications.
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @file Shared TypeScript types, constants, and utilities for the Shipstatic platform.
2
+ * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.
3
3
  * This package is the single source of truth for all shared data structures.
4
4
  */
5
5
  /**
@@ -272,7 +272,7 @@ export interface AccountOverrides {
272
272
  totalSize?: number;
273
273
  }
274
274
  /**
275
- * All possible error types in the Shipstatic platform
275
+ * All possible error types in the ShipStatic platform
276
276
  * Names are developer-friendly while wire format stays consistent
277
277
  */
278
278
  export declare enum ErrorType {
@@ -389,12 +389,34 @@ export declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
389
389
  */
390
390
  export declare function isBlockedExtension(filename: string): boolean;
391
391
  /**
392
- * Directory names that indicate an unbuilt project was uploaded instead of build output.
392
+ * Characters that are unsafe in filenames for static hosting.
393
+ *
394
+ * Blocks only characters that genuinely break the upload→serve round-trip:
395
+ * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)
396
+ * - \ Path separator confusion (upload splits on backslash)
397
+ * - < > " XSS vectors with zero legitimate use in filenames
398
+ * - \x00-\x1f \x7f Control characters (header injection, display corruption)
399
+ *
400
+ * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
401
+ */
402
+ export declare const UNSAFE_FILENAME_CHARS: RegExp;
403
+ /**
404
+ * Check if a filename contains unsafe characters.
405
+ *
406
+ * @example
407
+ * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe
408
+ * hasUnsafeChars('page[slug].js') // false — brackets are safe
409
+ * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution
410
+ * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector
411
+ */
412
+ export declare function hasUnsafeChars(filename: string): boolean;
413
+ /**
414
+ * Path segment names that indicate an unbuilt project was uploaded instead of build output.
393
415
  * Used for early detection in CLI, browser, and server validation.
394
416
  */
395
417
  export declare const UNBUILT_PROJECT_MARKERS: ReadonlySet<string>;
396
418
  /**
397
- * Check if a file path contains an unbuilt project marker directory.
419
+ * Check if a file path contains an unbuilt project marker.
398
420
  *
399
421
  * @example
400
422
  * hasUnbuiltMarker('node_modules/react/index.js') // true
@@ -802,24 +824,24 @@ export interface UploadedFile {
802
824
  * Check if a domain is a platform domain (subdomain of our platform).
803
825
  * Platform domains are free and don't require DNS verification.
804
826
  *
805
- * @example isPlatformDomain("www.shipstatic.dev", "shipstatic.dev") → true
806
- * @example isPlatformDomain("example.com", "shipstatic.dev") → false
827
+ * @example isPlatformDomain("www.shipstatic.com", "shipstatic.com") → true
828
+ * @example isPlatformDomain("example.com", "shipstatic.com") → false
807
829
  */
808
830
  export declare function isPlatformDomain(domain: string, platformDomain: string): boolean;
809
831
  /**
810
832
  * Check if a domain is a custom domain (not a platform subdomain).
811
833
  * Custom domains are billable and require DNS verification.
812
834
  *
813
- * @example isCustomDomain("example.com", "shipstatic.dev") → true
814
- * @example isCustomDomain("www.shipstatic.dev", "shipstatic.dev") → false
835
+ * @example isCustomDomain("example.com", "shipstatic.com") → true
836
+ * @example isCustomDomain("www.shipstatic.com", "shipstatic.com") → false
815
837
  */
816
838
  export declare function isCustomDomain(domain: string, platformDomain: string): boolean;
817
839
  /**
818
840
  * Extract subdomain from a platform domain.
819
841
  * Returns null if not a platform domain.
820
842
  *
821
- * @example extractSubdomain("www.shipstatic.dev", "shipstatic.dev") → "www"
822
- * @example extractSubdomain("example.com", "shipstatic.dev") → null
843
+ * @example extractSubdomain("www.shipstatic.com", "shipstatic.com") → "www"
844
+ * @example extractSubdomain("example.com", "shipstatic.com") → null
823
845
  */
824
846
  export declare function extractSubdomain(domain: string, platformDomain: string): string | null;
825
847
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @file Shared TypeScript types, constants, and utilities for the Shipstatic platform.
2
+ * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.
3
3
  * This package is the single source of truth for all shared data structures.
4
4
  */
5
5
  // =============================================================================
@@ -50,7 +50,7 @@ export const AccountPlan = {
50
50
  // ERROR SYSTEM
51
51
  // =============================================================================
52
52
  /**
53
- * All possible error types in the Shipstatic platform
53
+ * All possible error types in the ShipStatic platform
54
54
  * Names are developer-friendly while wire format stays consistent
55
55
  */
56
56
  export var ErrorType;
@@ -250,10 +250,37 @@ export function isBlockedExtension(filename) {
250
250
  return BLOCKED_EXTENSIONS.has(ext);
251
251
  }
252
252
  // =============================================================================
253
+ // FILENAME CHARACTER VALIDATION
254
+ // =============================================================================
255
+ /**
256
+ * Characters that are unsafe in filenames for static hosting.
257
+ *
258
+ * Blocks only characters that genuinely break the upload→serve round-trip:
259
+ * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)
260
+ * - \ Path separator confusion (upload splits on backslash)
261
+ * - < > " XSS vectors with zero legitimate use in filenames
262
+ * - \x00-\x1f \x7f Control characters (header injection, display corruption)
263
+ *
264
+ * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
265
+ */
266
+ export const UNSAFE_FILENAME_CHARS = /[\x00-\x1f\x7f#?%\\<>"]/;
267
+ /**
268
+ * Check if a filename contains unsafe characters.
269
+ *
270
+ * @example
271
+ * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe
272
+ * hasUnsafeChars('page[slug].js') // false — brackets are safe
273
+ * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution
274
+ * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector
275
+ */
276
+ export function hasUnsafeChars(filename) {
277
+ return UNSAFE_FILENAME_CHARS.test(filename);
278
+ }
279
+ // =============================================================================
253
280
  // UNBUILT PROJECT MARKERS
254
281
  // =============================================================================
255
282
  /**
256
- * Directory names that indicate an unbuilt project was uploaded instead of build output.
283
+ * Path segment names that indicate an unbuilt project was uploaded instead of build output.
257
284
  * Used for early detection in CLI, browser, and server validation.
258
285
  */
259
286
  export const UNBUILT_PROJECT_MARKERS = new Set([
@@ -261,7 +288,7 @@ export const UNBUILT_PROJECT_MARKERS = new Set([
261
288
  'package.json',
262
289
  ]);
263
290
  /**
264
- * Check if a file path contains an unbuilt project marker directory.
291
+ * Check if a file path contains an unbuilt project marker.
265
292
  *
266
293
  * @example
267
294
  * hasUnbuiltMarker('node_modules/react/index.js') // true
@@ -384,8 +411,8 @@ export const FileValidationStatus = {
384
411
  * Check if a domain is a platform domain (subdomain of our platform).
385
412
  * Platform domains are free and don't require DNS verification.
386
413
  *
387
- * @example isPlatformDomain("www.shipstatic.dev", "shipstatic.dev") → true
388
- * @example isPlatformDomain("example.com", "shipstatic.dev") → false
414
+ * @example isPlatformDomain("www.shipstatic.com", "shipstatic.com") → true
415
+ * @example isPlatformDomain("example.com", "shipstatic.com") → false
389
416
  */
390
417
  export function isPlatformDomain(domain, platformDomain) {
391
418
  return domain.endsWith(`.${platformDomain}`);
@@ -394,8 +421,8 @@ export function isPlatformDomain(domain, platformDomain) {
394
421
  * Check if a domain is a custom domain (not a platform subdomain).
395
422
  * Custom domains are billable and require DNS verification.
396
423
  *
397
- * @example isCustomDomain("example.com", "shipstatic.dev") → true
398
- * @example isCustomDomain("www.shipstatic.dev", "shipstatic.dev") → false
424
+ * @example isCustomDomain("example.com", "shipstatic.com") → true
425
+ * @example isCustomDomain("www.shipstatic.com", "shipstatic.com") → false
399
426
  */
400
427
  export function isCustomDomain(domain, platformDomain) {
401
428
  return !isPlatformDomain(domain, platformDomain);
@@ -404,8 +431,8 @@ export function isCustomDomain(domain, platformDomain) {
404
431
  * Extract subdomain from a platform domain.
405
432
  * Returns null if not a platform domain.
406
433
  *
407
- * @example extractSubdomain("www.shipstatic.dev", "shipstatic.dev") → "www"
408
- * @example extractSubdomain("example.com", "shipstatic.dev") → null
434
+ * @example extractSubdomain("www.shipstatic.com", "shipstatic.com") → "www"
435
+ * @example extractSubdomain("example.com", "shipstatic.com") → null
409
436
  */
410
437
  export function extractSubdomain(domain, platformDomain) {
411
438
  if (!isPlatformDomain(domain, platformDomain)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.7.4",
4
- "description": "Shared types for Shipstatic platform",
3
+ "version": "0.7.6",
4
+ "description": "Shared types for ShipStatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @file Shared TypeScript types, constants, and utilities for the Shipstatic platform.
2
+ * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.
3
3
  * This package is the single source of truth for all shared data structures.
4
4
  */
5
5
 
@@ -315,7 +315,7 @@ export interface AccountOverrides {
315
315
  // =============================================================================
316
316
 
317
317
  /**
318
- * All possible error types in the Shipstatic platform
318
+ * All possible error types in the ShipStatic platform
319
319
  * Names are developer-friendly while wire format stays consistent
320
320
  */
321
321
  export enum ErrorType {
@@ -571,12 +571,42 @@ export function isBlockedExtension(filename: string): boolean {
571
571
  return BLOCKED_EXTENSIONS.has(ext);
572
572
  }
573
573
 
574
+ // =============================================================================
575
+ // FILENAME CHARACTER VALIDATION
576
+ // =============================================================================
577
+
578
+ /**
579
+ * Characters that are unsafe in filenames for static hosting.
580
+ *
581
+ * Blocks only characters that genuinely break the upload→serve round-trip:
582
+ * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)
583
+ * - \ Path separator confusion (upload splits on backslash)
584
+ * - < > " XSS vectors with zero legitimate use in filenames
585
+ * - \x00-\x1f \x7f Control characters (header injection, display corruption)
586
+ *
587
+ * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
588
+ */
589
+ export const UNSAFE_FILENAME_CHARS = /[\x00-\x1f\x7f#?%\\<>"]/;
590
+
591
+ /**
592
+ * Check if a filename contains unsafe characters.
593
+ *
594
+ * @example
595
+ * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe
596
+ * hasUnsafeChars('page[slug].js') // false — brackets are safe
597
+ * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution
598
+ * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector
599
+ */
600
+ export function hasUnsafeChars(filename: string): boolean {
601
+ return UNSAFE_FILENAME_CHARS.test(filename);
602
+ }
603
+
574
604
  // =============================================================================
575
605
  // UNBUILT PROJECT MARKERS
576
606
  // =============================================================================
577
607
 
578
608
  /**
579
- * Directory names that indicate an unbuilt project was uploaded instead of build output.
609
+ * Path segment names that indicate an unbuilt project was uploaded instead of build output.
580
610
  * Used for early detection in CLI, browser, and server validation.
581
611
  */
582
612
  export const UNBUILT_PROJECT_MARKERS: ReadonlySet<string> = new Set([
@@ -585,7 +615,7 @@ export const UNBUILT_PROJECT_MARKERS: ReadonlySet<string> = new Set([
585
615
  ]);
586
616
 
587
617
  /**
588
- * Check if a file path contains an unbuilt project marker directory.
618
+ * Check if a file path contains an unbuilt project marker.
589
619
  *
590
620
  * @example
591
621
  * hasUnbuiltMarker('node_modules/react/index.js') // true
@@ -1201,8 +1231,8 @@ export interface UploadedFile {
1201
1231
  * Check if a domain is a platform domain (subdomain of our platform).
1202
1232
  * Platform domains are free and don't require DNS verification.
1203
1233
  *
1204
- * @example isPlatformDomain("www.shipstatic.dev", "shipstatic.dev") → true
1205
- * @example isPlatformDomain("example.com", "shipstatic.dev") → false
1234
+ * @example isPlatformDomain("www.shipstatic.com", "shipstatic.com") → true
1235
+ * @example isPlatformDomain("example.com", "shipstatic.com") → false
1206
1236
  */
1207
1237
  export function isPlatformDomain(domain: string, platformDomain: string): boolean {
1208
1238
  return domain.endsWith(`.${platformDomain}`);
@@ -1212,8 +1242,8 @@ export function isPlatformDomain(domain: string, platformDomain: string): boolea
1212
1242
  * Check if a domain is a custom domain (not a platform subdomain).
1213
1243
  * Custom domains are billable and require DNS verification.
1214
1244
  *
1215
- * @example isCustomDomain("example.com", "shipstatic.dev") → true
1216
- * @example isCustomDomain("www.shipstatic.dev", "shipstatic.dev") → false
1245
+ * @example isCustomDomain("example.com", "shipstatic.com") → true
1246
+ * @example isCustomDomain("www.shipstatic.com", "shipstatic.com") → false
1217
1247
  */
1218
1248
  export function isCustomDomain(domain: string, platformDomain: string): boolean {
1219
1249
  return !isPlatformDomain(domain, platformDomain);
@@ -1223,8 +1253,8 @@ export function isCustomDomain(domain: string, platformDomain: string): boolean
1223
1253
  * Extract subdomain from a platform domain.
1224
1254
  * Returns null if not a platform domain.
1225
1255
  *
1226
- * @example extractSubdomain("www.shipstatic.dev", "shipstatic.dev") → "www"
1227
- * @example extractSubdomain("example.com", "shipstatic.dev") → null
1256
+ * @example extractSubdomain("www.shipstatic.com", "shipstatic.com") → "www"
1257
+ * @example extractSubdomain("example.com", "shipstatic.com") → null
1228
1258
  */
1229
1259
  export function extractSubdomain(domain: string, platformDomain: string): string | null {
1230
1260
  if (!isPlatformDomain(domain, platformDomain)) {