@shipstatic/types 2.5.0-beta.16 → 2.5.0-beta.18
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 +24 -1
- package/dist/index.js +158 -2
- package/package.json +1 -1
- package/src/index.ts +161 -2
package/dist/index.d.ts
CHANGED
|
@@ -668,7 +668,8 @@ export declare class ShipError extends Error {
|
|
|
668
668
|
* Routing:
|
|
669
669
|
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
670
670
|
* - `AbortError` → `ShipError.cancelled(...)`
|
|
671
|
-
* -
|
|
671
|
+
* - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
|
|
672
|
+
* for what each runtime offers as evidence
|
|
672
673
|
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
673
674
|
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
674
675
|
*
|
|
@@ -775,6 +776,28 @@ export declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
|
|
|
775
776
|
* isBlockedExtension('README') // false
|
|
776
777
|
*/
|
|
777
778
|
export declare function isBlockedExtension(filename: string): boolean;
|
|
779
|
+
/**
|
|
780
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
781
|
+
*
|
|
782
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
783
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
784
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
785
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
786
|
+
*
|
|
787
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
788
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
789
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
790
|
+
* authority would reject files the platform serves happily. It is also not
|
|
791
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
792
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
793
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
794
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
795
|
+
*
|
|
796
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
797
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
798
|
+
* picker must never offer a file the platform will refuse.
|
|
799
|
+
*/
|
|
800
|
+
export declare const WEB_FILE_ACCEPT: string;
|
|
778
801
|
/**
|
|
779
802
|
* Characters that are unsafe in filenames for static hosting.
|
|
780
803
|
*
|
package/dist/index.js
CHANGED
|
@@ -237,6 +237,42 @@ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t
|
|
|
237
237
|
* contract, and truncating a long validation message would be the bug.
|
|
238
238
|
*/
|
|
239
239
|
const MAX_FOREIGN_MESSAGE_LENGTH = 200;
|
|
240
|
+
/**
|
|
241
|
+
* Did the runtime say the exchange never completed?
|
|
242
|
+
*
|
|
243
|
+
* WHATWG has `fetch` reject with a **TypeError** on network error, and undici,
|
|
244
|
+
* Chromium and Firefox comply. Bun does not: it rejects with a plain `Error`
|
|
245
|
+
* carrying a system `code` string. Captured 2026-08-05 (the capture script is
|
|
246
|
+
* in `tests/errors.test.ts`, "runtime failure shapes"):
|
|
247
|
+
*
|
|
248
|
+
* | failure | Node 22 / undici | Bun 1.3.14 |
|
|
249
|
+
* |---------------|---------------------------|----------------------------------------------|
|
|
250
|
+
* | refused | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
|
|
251
|
+
* | DNS failure | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
|
|
252
|
+
* | reset | `TypeError: fetch failed` | `Error` `code: 'ECONNRESET'` |
|
|
253
|
+
* | TLS rejected | `TypeError: fetch failed` | `Error` `code: 'UNKNOWN_CERTIFICATE_…ERROR'` |
|
|
254
|
+
*
|
|
255
|
+
* So the test is the **evidence, not a list of dialect strings**: a string
|
|
256
|
+
* `code` is a runtime naming a transport-level failure. An allowlist of codes
|
|
257
|
+
* was written first and rejected — the TLS row alone would mean enumerating
|
|
258
|
+
* BoringSSL's certificate table, and a code nobody guessed is precisely the bug
|
|
259
|
+
* this closes. Two kinds of error are deliberately NOT caught: ordinary JS
|
|
260
|
+
* faults carry no `code` at all, and a `DOMException`'s is a **number**, so
|
|
261
|
+
* aborts and timeouts fall through to their own arms.
|
|
262
|
+
*
|
|
263
|
+
* The accepted trade: a caller's `TokenProvider` that throws a coded error
|
|
264
|
+
* (`ENOENT` from a keychain read) is typed `Network` rather than `Api`. Both
|
|
265
|
+
* are wrong for it, `Network` is the cheaper wrong — it says "nothing was
|
|
266
|
+
* exchanged", which is true, where `Api` claims a server answered.
|
|
267
|
+
*/
|
|
268
|
+
function isTransportFailure(cause) {
|
|
269
|
+
if (typeof cause.code === 'string')
|
|
270
|
+
return true;
|
|
271
|
+
// Spec runtimes put no code on the rejection itself. The message test is what
|
|
272
|
+
// keeps fetch's ARGUMENT errors out — `Failed to parse URL from …` is a
|
|
273
|
+
// caller's config mistake, not a transport failure.
|
|
274
|
+
return cause instanceof TypeError && cause.message.includes('fetch');
|
|
275
|
+
}
|
|
240
276
|
/**
|
|
241
277
|
* Simple unified error class for both API and SDK
|
|
242
278
|
*/
|
|
@@ -362,7 +398,8 @@ export class ShipError extends Error {
|
|
|
362
398
|
* Routing:
|
|
363
399
|
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
364
400
|
* - `AbortError` → `ShipError.cancelled(...)`
|
|
365
|
-
* -
|
|
401
|
+
* - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
|
|
402
|
+
* for what each runtime offers as evidence
|
|
366
403
|
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
367
404
|
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
368
405
|
*
|
|
@@ -378,7 +415,7 @@ export class ShipError extends Error {
|
|
|
378
415
|
if (cause.name === 'AbortError') {
|
|
379
416
|
return ShipError.cancelled(`${op} was cancelled`);
|
|
380
417
|
}
|
|
381
|
-
if (cause
|
|
418
|
+
if (isTransportFailure(cause)) {
|
|
382
419
|
return ShipError.network(`${op} failed: ${cause.message}`, { cause });
|
|
383
420
|
}
|
|
384
421
|
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
@@ -560,6 +597,125 @@ export function isBlockedExtension(filename) {
|
|
|
560
597
|
return BLOCKED_EXTENSIONS.has(ext);
|
|
561
598
|
}
|
|
562
599
|
// =============================================================================
|
|
600
|
+
// PICKER ACCEPT HINT
|
|
601
|
+
// =============================================================================
|
|
602
|
+
/**
|
|
603
|
+
* The extensions a browser file picker offers by default, grouped by role.
|
|
604
|
+
*
|
|
605
|
+
* Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the
|
|
606
|
+
* attribute value itself. A published set would invite a call site to ask it
|
|
607
|
+
* whether a file is allowed — which is the one thing this list must never
|
|
608
|
+
* answer. See `WEB_FILE_ACCEPT`.
|
|
609
|
+
*
|
|
610
|
+
* Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible
|
|
611
|
+
* in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.
|
|
612
|
+
*/
|
|
613
|
+
const WEB_FILE_EXTENSIONS = [
|
|
614
|
+
// Markup & documents
|
|
615
|
+
'html',
|
|
616
|
+
'htm',
|
|
617
|
+
'xhtml',
|
|
618
|
+
'xml',
|
|
619
|
+
'txt',
|
|
620
|
+
'md',
|
|
621
|
+
'markdown',
|
|
622
|
+
'pdf',
|
|
623
|
+
'csv',
|
|
624
|
+
// Data & config
|
|
625
|
+
'json',
|
|
626
|
+
'jsonc',
|
|
627
|
+
'webmanifest',
|
|
628
|
+
'map',
|
|
629
|
+
'toml',
|
|
630
|
+
'yaml',
|
|
631
|
+
'yml',
|
|
632
|
+
'rss',
|
|
633
|
+
'atom',
|
|
634
|
+
// Styles
|
|
635
|
+
'css',
|
|
636
|
+
'scss',
|
|
637
|
+
'sass',
|
|
638
|
+
'less',
|
|
639
|
+
// Scripts & modules
|
|
640
|
+
'js',
|
|
641
|
+
'mjs',
|
|
642
|
+
'cjs',
|
|
643
|
+
'jsx',
|
|
644
|
+
'ts',
|
|
645
|
+
'tsx',
|
|
646
|
+
'wasm',
|
|
647
|
+
'vue',
|
|
648
|
+
'svelte',
|
|
649
|
+
// Images
|
|
650
|
+
'png',
|
|
651
|
+
'jpg',
|
|
652
|
+
'jpeg',
|
|
653
|
+
'gif',
|
|
654
|
+
'webp',
|
|
655
|
+
'avif',
|
|
656
|
+
'svg',
|
|
657
|
+
'ico',
|
|
658
|
+
'bmp',
|
|
659
|
+
'tif',
|
|
660
|
+
'tiff',
|
|
661
|
+
'heic',
|
|
662
|
+
'heif',
|
|
663
|
+
// Fonts
|
|
664
|
+
'woff',
|
|
665
|
+
'woff2',
|
|
666
|
+
'ttf',
|
|
667
|
+
'otf',
|
|
668
|
+
'eot',
|
|
669
|
+
// Audio
|
|
670
|
+
'mp3',
|
|
671
|
+
'wav',
|
|
672
|
+
'ogg',
|
|
673
|
+
'oga',
|
|
674
|
+
'opus',
|
|
675
|
+
'm4a',
|
|
676
|
+
'aac',
|
|
677
|
+
'flac',
|
|
678
|
+
'weba',
|
|
679
|
+
// Video
|
|
680
|
+
'mp4',
|
|
681
|
+
'webm',
|
|
682
|
+
'ogv',
|
|
683
|
+
'mov',
|
|
684
|
+
'm4v',
|
|
685
|
+
'avi',
|
|
686
|
+
// 3D models
|
|
687
|
+
'glb',
|
|
688
|
+
'gltf',
|
|
689
|
+
'usdz',
|
|
690
|
+
// Text tracks
|
|
691
|
+
'vtt',
|
|
692
|
+
'srt',
|
|
693
|
+
// Archive — a whole site in one file
|
|
694
|
+
'zip',
|
|
695
|
+
];
|
|
696
|
+
/**
|
|
697
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
698
|
+
*
|
|
699
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
700
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
701
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
702
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
703
|
+
*
|
|
704
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
705
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
706
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
707
|
+
* authority would reject files the platform serves happily. It is also not
|
|
708
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
709
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
710
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
711
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
712
|
+
*
|
|
713
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
714
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
715
|
+
* picker must never offer a file the platform will refuse.
|
|
716
|
+
*/
|
|
717
|
+
export const WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');
|
|
718
|
+
// =============================================================================
|
|
563
719
|
// FILENAME CHARACTER VALIDATION
|
|
564
720
|
// =============================================================================
|
|
565
721
|
/**
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -754,6 +754,42 @@ const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
|
|
|
754
754
|
*/
|
|
755
755
|
const MAX_FOREIGN_MESSAGE_LENGTH = 200;
|
|
756
756
|
|
|
757
|
+
/**
|
|
758
|
+
* Did the runtime say the exchange never completed?
|
|
759
|
+
*
|
|
760
|
+
* WHATWG has `fetch` reject with a **TypeError** on network error, and undici,
|
|
761
|
+
* Chromium and Firefox comply. Bun does not: it rejects with a plain `Error`
|
|
762
|
+
* carrying a system `code` string. Captured 2026-08-05 (the capture script is
|
|
763
|
+
* in `tests/errors.test.ts`, "runtime failure shapes"):
|
|
764
|
+
*
|
|
765
|
+
* | failure | Node 22 / undici | Bun 1.3.14 |
|
|
766
|
+
* |---------------|---------------------------|----------------------------------------------|
|
|
767
|
+
* | refused | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
|
|
768
|
+
* | DNS failure | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |
|
|
769
|
+
* | reset | `TypeError: fetch failed` | `Error` `code: 'ECONNRESET'` |
|
|
770
|
+
* | TLS rejected | `TypeError: fetch failed` | `Error` `code: 'UNKNOWN_CERTIFICATE_…ERROR'` |
|
|
771
|
+
*
|
|
772
|
+
* So the test is the **evidence, not a list of dialect strings**: a string
|
|
773
|
+
* `code` is a runtime naming a transport-level failure. An allowlist of codes
|
|
774
|
+
* was written first and rejected — the TLS row alone would mean enumerating
|
|
775
|
+
* BoringSSL's certificate table, and a code nobody guessed is precisely the bug
|
|
776
|
+
* this closes. Two kinds of error are deliberately NOT caught: ordinary JS
|
|
777
|
+
* faults carry no `code` at all, and a `DOMException`'s is a **number**, so
|
|
778
|
+
* aborts and timeouts fall through to their own arms.
|
|
779
|
+
*
|
|
780
|
+
* The accepted trade: a caller's `TokenProvider` that throws a coded error
|
|
781
|
+
* (`ENOENT` from a keychain read) is typed `Network` rather than `Api`. Both
|
|
782
|
+
* are wrong for it, `Network` is the cheaper wrong — it says "nothing was
|
|
783
|
+
* exchanged", which is true, where `Api` claims a server answered.
|
|
784
|
+
*/
|
|
785
|
+
function isTransportFailure(cause: Error): boolean {
|
|
786
|
+
if (typeof (cause as { code?: unknown }).code === 'string') return true;
|
|
787
|
+
// Spec runtimes put no code on the rejection itself. The message test is what
|
|
788
|
+
// keeps fetch's ARGUMENT errors out — `Failed to parse URL from …` is a
|
|
789
|
+
// caller's config mistake, not a transport failure.
|
|
790
|
+
return cause instanceof TypeError && cause.message.includes('fetch');
|
|
791
|
+
}
|
|
792
|
+
|
|
757
793
|
/**
|
|
758
794
|
* Standard error response format used everywhere
|
|
759
795
|
*/
|
|
@@ -900,7 +936,8 @@ export class ShipError extends Error {
|
|
|
900
936
|
* Routing:
|
|
901
937
|
* - Already a `ShipError` → returned as-is (caller's intent preserved)
|
|
902
938
|
* - `AbortError` → `ShipError.cancelled(...)`
|
|
903
|
-
* -
|
|
939
|
+
* - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
|
|
940
|
+
* for what each runtime offers as evidence
|
|
904
941
|
* - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
|
|
905
942
|
* - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
|
|
906
943
|
*
|
|
@@ -917,7 +954,7 @@ export class ShipError extends Error {
|
|
|
917
954
|
if (cause.name === 'AbortError') {
|
|
918
955
|
return ShipError.cancelled(`${op} was cancelled`);
|
|
919
956
|
}
|
|
920
|
-
if (cause
|
|
957
|
+
if (isTransportFailure(cause)) {
|
|
921
958
|
return ShipError.network(`${op} failed: ${cause.message}`, { cause });
|
|
922
959
|
}
|
|
923
960
|
return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);
|
|
@@ -1147,6 +1184,128 @@ export function isBlockedExtension(filename: string): boolean {
|
|
|
1147
1184
|
return BLOCKED_EXTENSIONS.has(ext);
|
|
1148
1185
|
}
|
|
1149
1186
|
|
|
1187
|
+
// =============================================================================
|
|
1188
|
+
// PICKER ACCEPT HINT
|
|
1189
|
+
// =============================================================================
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* The extensions a browser file picker offers by default, grouped by role.
|
|
1193
|
+
*
|
|
1194
|
+
* Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the
|
|
1195
|
+
* attribute value itself. A published set would invite a call site to ask it
|
|
1196
|
+
* whether a file is allowed — which is the one thing this list must never
|
|
1197
|
+
* answer. See `WEB_FILE_ACCEPT`.
|
|
1198
|
+
*
|
|
1199
|
+
* Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible
|
|
1200
|
+
* in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.
|
|
1201
|
+
*/
|
|
1202
|
+
const WEB_FILE_EXTENSIONS = [
|
|
1203
|
+
// Markup & documents
|
|
1204
|
+
'html',
|
|
1205
|
+
'htm',
|
|
1206
|
+
'xhtml',
|
|
1207
|
+
'xml',
|
|
1208
|
+
'txt',
|
|
1209
|
+
'md',
|
|
1210
|
+
'markdown',
|
|
1211
|
+
'pdf',
|
|
1212
|
+
'csv',
|
|
1213
|
+
// Data & config
|
|
1214
|
+
'json',
|
|
1215
|
+
'jsonc',
|
|
1216
|
+
'webmanifest',
|
|
1217
|
+
'map',
|
|
1218
|
+
'toml',
|
|
1219
|
+
'yaml',
|
|
1220
|
+
'yml',
|
|
1221
|
+
'rss',
|
|
1222
|
+
'atom',
|
|
1223
|
+
// Styles
|
|
1224
|
+
'css',
|
|
1225
|
+
'scss',
|
|
1226
|
+
'sass',
|
|
1227
|
+
'less',
|
|
1228
|
+
// Scripts & modules
|
|
1229
|
+
'js',
|
|
1230
|
+
'mjs',
|
|
1231
|
+
'cjs',
|
|
1232
|
+
'jsx',
|
|
1233
|
+
'ts',
|
|
1234
|
+
'tsx',
|
|
1235
|
+
'wasm',
|
|
1236
|
+
'vue',
|
|
1237
|
+
'svelte',
|
|
1238
|
+
// Images
|
|
1239
|
+
'png',
|
|
1240
|
+
'jpg',
|
|
1241
|
+
'jpeg',
|
|
1242
|
+
'gif',
|
|
1243
|
+
'webp',
|
|
1244
|
+
'avif',
|
|
1245
|
+
'svg',
|
|
1246
|
+
'ico',
|
|
1247
|
+
'bmp',
|
|
1248
|
+
'tif',
|
|
1249
|
+
'tiff',
|
|
1250
|
+
'heic',
|
|
1251
|
+
'heif',
|
|
1252
|
+
// Fonts
|
|
1253
|
+
'woff',
|
|
1254
|
+
'woff2',
|
|
1255
|
+
'ttf',
|
|
1256
|
+
'otf',
|
|
1257
|
+
'eot',
|
|
1258
|
+
// Audio
|
|
1259
|
+
'mp3',
|
|
1260
|
+
'wav',
|
|
1261
|
+
'ogg',
|
|
1262
|
+
'oga',
|
|
1263
|
+
'opus',
|
|
1264
|
+
'm4a',
|
|
1265
|
+
'aac',
|
|
1266
|
+
'flac',
|
|
1267
|
+
'weba',
|
|
1268
|
+
// Video
|
|
1269
|
+
'mp4',
|
|
1270
|
+
'webm',
|
|
1271
|
+
'ogv',
|
|
1272
|
+
'mov',
|
|
1273
|
+
'm4v',
|
|
1274
|
+
'avi',
|
|
1275
|
+
// 3D models
|
|
1276
|
+
'glb',
|
|
1277
|
+
'gltf',
|
|
1278
|
+
'usdz',
|
|
1279
|
+
// Text tracks
|
|
1280
|
+
'vtt',
|
|
1281
|
+
'srt',
|
|
1282
|
+
// Archive — a whole site in one file
|
|
1283
|
+
'zip',
|
|
1284
|
+
] as const;
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
1288
|
+
*
|
|
1289
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
1290
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
1291
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
1292
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
1293
|
+
*
|
|
1294
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
1295
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
1296
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
1297
|
+
* authority would reject files the platform serves happily. It is also not
|
|
1298
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
1299
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
1300
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
1301
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
1302
|
+
*
|
|
1303
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
1304
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
1305
|
+
* picker must never offer a file the platform will refuse.
|
|
1306
|
+
*/
|
|
1307
|
+
export const WEB_FILE_ACCEPT: string = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');
|
|
1308
|
+
|
|
1150
1309
|
// =============================================================================
|
|
1151
1310
|
// FILENAME CHARACTER VALIDATION
|
|
1152
1311
|
// =============================================================================
|