@shipstatic/types 0.7.3 → 0.7.5

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/dist/index.d.ts CHANGED
@@ -389,15 +389,38 @@ 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
423
+ * hasUnbuiltMarker('package.json') // true
401
424
  * hasUnbuiltMarker('dist/index.html') // false
402
425
  */
403
426
  export declare function hasUnbuiltMarker(filePath: string): boolean;
package/dist/index.js CHANGED
@@ -250,20 +250,49 @@ 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([
260
287
  'node_modules',
288
+ 'package.json',
261
289
  ]);
262
290
  /**
263
- * Check if a file path contains an unbuilt project marker directory.
291
+ * Check if a file path contains an unbuilt project marker.
264
292
  *
265
293
  * @example
266
294
  * hasUnbuiltMarker('node_modules/react/index.js') // true
295
+ * hasUnbuiltMarker('package.json') // true
267
296
  * hasUnbuiltMarker('dist/index.html') // false
268
297
  */
269
298
  export function hasUnbuiltMarker(filePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Shared types for Shipstatic platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^24.10.9",
37
+ "husky": "^9.1.7",
37
38
  "typescript": "^5.9.3",
38
39
  "vitest": "^2.1.8"
39
40
  },
package/src/index.ts CHANGED
@@ -571,23 +571,55 @@ 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([
583
613
  'node_modules',
614
+ 'package.json',
584
615
  ]);
585
616
 
586
617
  /**
587
- * Check if a file path contains an unbuilt project marker directory.
618
+ * Check if a file path contains an unbuilt project marker.
588
619
  *
589
620
  * @example
590
621
  * hasUnbuiltMarker('node_modules/react/index.js') // true
622
+ * hasUnbuiltMarker('package.json') // true
591
623
  * hasUnbuiltMarker('dist/index.html') // false
592
624
  */
593
625
  export function hasUnbuiltMarker(filePath: string): boolean {