@scout9/app 1.0.0-alpha.0.1.9 → 1.0.0-alpha.0.1.90

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.
Files changed (70) hide show
  1. package/README.md +32 -0
  2. package/dist/{index-92deaa5f.cjs → exports-e7d51b70.cjs} +46618 -4591
  3. package/dist/index.cjs +58 -15
  4. package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-e09a67c9.cjs} +13 -7
  5. package/dist/spirits-3b603262.cjs +1218 -0
  6. package/dist/spirits.cjs +9 -0
  7. package/dist/testing-tools.cjs +48 -0
  8. package/package.json +30 -8
  9. package/src/cli.js +162 -69
  10. package/src/core/config/agents.js +300 -7
  11. package/src/core/config/entities.js +58 -28
  12. package/src/core/config/index.js +37 -15
  13. package/src/core/config/project.js +160 -6
  14. package/src/core/config/workflow.js +13 -12
  15. package/src/core/data.js +27 -0
  16. package/src/core/index.js +386 -137
  17. package/src/core/sync.js +71 -0
  18. package/src/core/templates/Dockerfile +22 -0
  19. package/src/core/templates/app.js +453 -0
  20. package/src/core/templates/project-files.js +36 -0
  21. package/src/core/templates/template-package.json +13 -0
  22. package/src/exports.js +21 -17
  23. package/src/platform.js +189 -33
  24. package/src/public.d.ts.text +330 -0
  25. package/src/report.js +117 -0
  26. package/src/runtime/client/api.js +56 -159
  27. package/src/runtime/client/config.js +60 -11
  28. package/src/runtime/client/entity.js +19 -6
  29. package/src/runtime/client/index.js +5 -3
  30. package/src/runtime/client/message.js +13 -3
  31. package/src/runtime/client/platform.js +86 -0
  32. package/src/runtime/client/{agent.js → users.js} +35 -3
  33. package/src/runtime/client/utils.js +10 -9
  34. package/src/runtime/client/workflow.js +131 -9
  35. package/src/runtime/entry.js +2 -2
  36. package/src/testing-tools/dev.js +373 -0
  37. package/src/testing-tools/index.js +1 -0
  38. package/src/testing-tools/mocks.js +37 -5
  39. package/src/testing-tools/spirits.js +530 -0
  40. package/src/utils/audio-buffer.js +16 -0
  41. package/src/utils/audio-type.js +27 -0
  42. package/src/utils/configs/agents.js +68 -0
  43. package/src/utils/configs/entities.js +145 -0
  44. package/src/utils/configs/project.js +23 -0
  45. package/src/utils/configs/workflow.js +47 -0
  46. package/src/utils/file-type.js +569 -0
  47. package/src/utils/file.js +158 -0
  48. package/src/utils/glob.js +30 -0
  49. package/src/utils/image-buffer.js +23 -0
  50. package/src/utils/image-type.js +39 -0
  51. package/src/utils/index.js +1 -0
  52. package/src/utils/is-svg.js +37 -0
  53. package/src/utils/logger.js +111 -0
  54. package/src/utils/module.js +14 -25
  55. package/src/utils/project-templates.js +191 -0
  56. package/src/utils/project.js +387 -0
  57. package/src/utils/video-type.js +29 -0
  58. package/types/index.d.ts +7588 -206
  59. package/types/index.d.ts.map +97 -22
  60. package/dist/index-1b8d7dd2.cjs +0 -49555
  61. package/dist/index-2ccb115e.cjs +0 -49514
  62. package/dist/index-66b06a30.cjs +0 -49549
  63. package/dist/index-bc029a1d.cjs +0 -49528
  64. package/dist/index-d9a93523.cjs +0 -49527
  65. package/dist/multipart-parser-1508046a.cjs +0 -413
  66. package/dist/multipart-parser-7007403a.cjs +0 -413
  67. package/dist/multipart-parser-70c32c1d.cjs +0 -413
  68. package/dist/multipart-parser-71dec101.cjs +0 -413
  69. package/dist/multipart-parser-f15bf2e0.cjs +0 -414
  70. package/src/public.d.ts +0 -209
@@ -0,0 +1,569 @@
1
+ /**
2
+ * @typedef {Object} FileTypeResult
3
+ * @property {string} ext - One of the supported [file types](https://github.com/sindresorhus/image-type#supported-file-types).
4
+ * @property {string} mime - The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
5
+ */
6
+
7
+ /**
8
+ *
9
+ * @param {Buffer | Uint8Array} input
10
+ * @returns {Promise<{ext: string, mime: string}|null>}
11
+ */
12
+ export async function fileTypeFromBuffer(input) {
13
+
14
+ const buf = new Uint8Array(input);
15
+
16
+ if (!(buf && buf.length > 1)) {
17
+ return null;
18
+ }
19
+
20
+ const check = (header, opts) => {
21
+ opts = Object.assign({
22
+ offset: 0
23
+ }, opts);
24
+
25
+ for (let i = 0; i < header.length; i++) {
26
+ if (header[i] !== buf[i + opts.offset]) {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ return true;
32
+ };
33
+
34
+ if (check([0xFF, 0xD8, 0xFF])) {
35
+ return {
36
+ ext: 'jpg',
37
+ mime: 'image/jpeg'
38
+ };
39
+ }
40
+
41
+ if (check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
42
+ return {
43
+ ext: 'png',
44
+ mime: 'image/png'
45
+ };
46
+ }
47
+
48
+ if (check([0x47, 0x49, 0x46])) {
49
+ return {
50
+ ext: 'gif',
51
+ mime: 'image/gif'
52
+ };
53
+ }
54
+
55
+ if (check([0x57, 0x45, 0x42, 0x50], {offset: 8})) {
56
+ return {
57
+ ext: 'webp',
58
+ mime: 'image/webp'
59
+ };
60
+ }
61
+
62
+ if (check([0x46, 0x4C, 0x49, 0x46])) {
63
+ return {
64
+ ext: 'flif',
65
+ mime: 'image/flif'
66
+ };
67
+ }
68
+
69
+ // Needs to be before `tif` check
70
+ if (
71
+ (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
72
+ check([0x43, 0x52], {offset: 8})
73
+ ) {
74
+ return {
75
+ ext: 'cr2',
76
+ mime: 'image/x-canon-cr2'
77
+ };
78
+ }
79
+
80
+ if (
81
+ check([0x49, 0x49, 0x2A, 0x0]) ||
82
+ check([0x4D, 0x4D, 0x0, 0x2A])
83
+ ) {
84
+ return {
85
+ ext: 'tif',
86
+ mime: 'image/tiff'
87
+ };
88
+ }
89
+
90
+ if (check([0x42, 0x4D])) {
91
+ return {
92
+ ext: 'bmp',
93
+ mime: 'image/bmp'
94
+ };
95
+ }
96
+
97
+ if (check([0x49, 0x49, 0xBC])) {
98
+ return {
99
+ ext: 'jxr',
100
+ mime: 'image/vnd.ms-photo'
101
+ };
102
+ }
103
+
104
+ if (check([0x38, 0x42, 0x50, 0x53])) {
105
+ return {
106
+ ext: 'psd',
107
+ mime: 'image/vnd.adobe.photoshop'
108
+ };
109
+ }
110
+
111
+ // Needs to be before the `zip` check
112
+ if (
113
+ check([0x50, 0x4B, 0x3, 0x4]) &&
114
+ check([0x6D, 0x69, 0x6D, 0x65, 0x74, 0x79, 0x70, 0x65, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x65, 0x70, 0x75, 0x62, 0x2B, 0x7A, 0x69, 0x70], {offset: 30})
115
+ ) {
116
+ return {
117
+ ext: 'epub',
118
+ mime: 'application/epub+zip'
119
+ };
120
+ }
121
+
122
+ // Needs to be before `zip` check
123
+ // Assumes signed `.xpi` from addons.mozilla.org
124
+ if (
125
+ check([0x50, 0x4B, 0x3, 0x4]) &&
126
+ check([0x4D, 0x45, 0x54, 0x41, 0x2D, 0x49, 0x4E, 0x46, 0x2F, 0x6D, 0x6F, 0x7A, 0x69, 0x6C, 0x6C, 0x61, 0x2E, 0x72, 0x73, 0x61], {offset: 30})
127
+ ) {
128
+ return {
129
+ ext: 'xpi',
130
+ mime: 'application/x-xpinstall'
131
+ };
132
+ }
133
+
134
+ if (
135
+ check([0x50, 0x4B]) &&
136
+ (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) &&
137
+ (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)
138
+ ) {
139
+ return {
140
+ ext: 'zip',
141
+ mime: 'application/zip'
142
+ };
143
+ }
144
+
145
+ if (check([0x75, 0x73, 0x74, 0x61, 0x72], {offset: 257})) {
146
+ return {
147
+ ext: 'tar',
148
+ mime: 'application/x-tar'
149
+ };
150
+ }
151
+
152
+ if (
153
+ check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
154
+ (buf[6] === 0x0 || buf[6] === 0x1)
155
+ ) {
156
+ return {
157
+ ext: 'rar',
158
+ mime: 'application/x-rar-compressed'
159
+ };
160
+ }
161
+
162
+ if (check([0x1F, 0x8B, 0x8])) {
163
+ return {
164
+ ext: 'gz',
165
+ mime: 'application/gzip'
166
+ };
167
+ }
168
+
169
+ if (check([0x42, 0x5A, 0x68])) {
170
+ return {
171
+ ext: 'bz2',
172
+ mime: 'application/x-bzip2'
173
+ };
174
+ }
175
+
176
+ if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
177
+ return {
178
+ ext: '7z',
179
+ mime: 'application/x-7z-compressed'
180
+ };
181
+ }
182
+
183
+ if (check([0x78, 0x01])) {
184
+ return {
185
+ ext: 'dmg',
186
+ mime: 'application/x-apple-diskimage'
187
+ };
188
+ }
189
+
190
+ if (
191
+ (
192
+ check([0x0, 0x0, 0x0]) &&
193
+ (buf[3] === 0x18 || buf[3] === 0x20) &&
194
+ check([0x66, 0x74, 0x79, 0x70], {offset: 4})
195
+ ) ||
196
+ check([0x33, 0x67, 0x70, 0x35]) ||
197
+ (
198
+ check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32]) &&
199
+ check([0x6D, 0x70, 0x34, 0x31, 0x6D, 0x70, 0x34, 0x32, 0x69, 0x73, 0x6F, 0x6D], {offset: 16})
200
+ ) ||
201
+ check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D]) ||
202
+ check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32, 0x0, 0x0, 0x0, 0x0])
203
+ ) {
204
+ return {
205
+ ext: 'mp4',
206
+ mime: 'video/mp4'
207
+ };
208
+ }
209
+
210
+ if (check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x56])) {
211
+ return {
212
+ ext: 'm4v',
213
+ mime: 'video/x-m4v'
214
+ };
215
+ }
216
+
217
+ if (check([0x4D, 0x54, 0x68, 0x64])) {
218
+ return {
219
+ ext: 'mid',
220
+ mime: 'audio/midi'
221
+ };
222
+ }
223
+
224
+ // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
225
+ if (check([0x1A, 0x45, 0xDF, 0xA3])) {
226
+ const sliced = buf.subarray(4, 4 + 4096);
227
+ const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
228
+
229
+ if (idPos >= 0) {
230
+ const docTypePos = idPos + 3;
231
+ const findDocType = (type) => Array.from(type).every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
232
+
233
+ if (findDocType('matroska')) {
234
+ return {
235
+ ext: 'mkv',
236
+ mime: 'video/x-matroska'
237
+ };
238
+ }
239
+
240
+ if (findDocType('webm')) {
241
+ return {
242
+ ext: 'webm',
243
+ mime: 'video/webm'
244
+ };
245
+ }
246
+ }
247
+ }
248
+
249
+ if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
250
+ check([0x66, 0x72, 0x65, 0x65], {offset: 4}) ||
251
+ check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
252
+ check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
253
+ check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
254
+ return {
255
+ ext: 'mov',
256
+ mime: 'video/quicktime'
257
+ };
258
+ }
259
+
260
+ if (
261
+ check([0x52, 0x49, 0x46, 0x46]) &&
262
+ check([0x41, 0x56, 0x49], {offset: 8})
263
+ ) {
264
+ return {
265
+ ext: 'avi',
266
+ mime: 'video/x-msvideo'
267
+ };
268
+ }
269
+
270
+ if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
271
+ return {
272
+ ext: 'wmv',
273
+ mime: 'video/x-ms-wmv'
274
+ };
275
+ }
276
+
277
+ if (check([0x0, 0x0, 0x1, 0xBA])) {
278
+ return {
279
+ ext: 'mpg',
280
+ mime: 'video/mpeg'
281
+ };
282
+ }
283
+
284
+ if (
285
+ check([0x49, 0x44, 0x33]) ||
286
+ check([0xFF, 0xFB])
287
+ ) {
288
+ return {
289
+ ext: 'mp3',
290
+ mime: 'audio/mpeg'
291
+ };
292
+ }
293
+
294
+ if (
295
+ check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4}) ||
296
+ check([0x4D, 0x34, 0x41, 0x20])
297
+ ) {
298
+ return {
299
+ ext: 'm4a',
300
+ mime: 'audio/m4a'
301
+ };
302
+ }
303
+
304
+ // Needs to be before `ogg` check
305
+ if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
306
+ return {
307
+ ext: 'opus',
308
+ mime: 'audio/opus'
309
+ };
310
+ }
311
+
312
+ if (check([0x4F, 0x67, 0x67, 0x53])) {
313
+ return {
314
+ ext: 'ogg',
315
+ mime: 'audio/ogg'
316
+ };
317
+ }
318
+
319
+ if (check([0x66, 0x4C, 0x61, 0x43])) {
320
+ return {
321
+ ext: 'flac',
322
+ mime: 'audio/x-flac'
323
+ };
324
+ }
325
+
326
+ if (
327
+ check([0x52, 0x49, 0x46, 0x46]) &&
328
+ check([0x57, 0x41, 0x56, 0x45], {offset: 8})
329
+ ) {
330
+ return {
331
+ ext: 'wav',
332
+ mime: 'audio/x-wav'
333
+ };
334
+ }
335
+
336
+ if (check([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) {
337
+ return {
338
+ ext: 'amr',
339
+ mime: 'audio/amr'
340
+ };
341
+ }
342
+
343
+ if (check([0x25, 0x50, 0x44, 0x46])) {
344
+ return {
345
+ ext: 'pdf',
346
+ mime: 'application/pdf'
347
+ };
348
+ }
349
+
350
+ if (check([0x4D, 0x5A])) {
351
+ return {
352
+ ext: 'exe',
353
+ mime: 'application/x-msdownload'
354
+ };
355
+ }
356
+
357
+ if (
358
+ (buf[0] === 0x43 || buf[0] === 0x46) &&
359
+ check([0x57, 0x53], {offset: 1})
360
+ ) {
361
+ return {
362
+ ext: 'swf',
363
+ mime: 'application/x-shockwave-flash'
364
+ };
365
+ }
366
+
367
+ if (check([0x7B, 0x5C, 0x72, 0x74, 0x66])) {
368
+ return {
369
+ ext: 'rtf',
370
+ mime: 'application/rtf'
371
+ };
372
+ }
373
+
374
+ if (check([0x00, 0x61, 0x73, 0x6D])) {
375
+ return {
376
+ ext: 'wasm',
377
+ mime: 'application/wasm'
378
+ };
379
+ }
380
+
381
+ if (
382
+ check([0x77, 0x4F, 0x46, 0x46]) &&
383
+ (
384
+ check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
385
+ check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
386
+ )
387
+ ) {
388
+ return {
389
+ ext: 'woff',
390
+ mime: 'font/woff'
391
+ };
392
+ }
393
+
394
+ if (
395
+ check([0x77, 0x4F, 0x46, 0x32]) &&
396
+ (
397
+ check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
398
+ check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
399
+ )
400
+ ) {
401
+ return {
402
+ ext: 'woff2',
403
+ mime: 'font/woff2'
404
+ };
405
+ }
406
+
407
+ if (
408
+ check([0x4C, 0x50], {offset: 34}) &&
409
+ (
410
+ check([0x00, 0x00, 0x01], {offset: 8}) ||
411
+ check([0x01, 0x00, 0x02], {offset: 8}) ||
412
+ check([0x02, 0x00, 0x02], {offset: 8})
413
+ )
414
+ ) {
415
+ return {
416
+ ext: 'eot',
417
+ mime: 'application/octet-stream'
418
+ };
419
+ }
420
+
421
+ if (check([0x00, 0x01, 0x00, 0x00, 0x00])) {
422
+ return {
423
+ ext: 'ttf',
424
+ mime: 'font/ttf'
425
+ };
426
+ }
427
+
428
+ if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
429
+ return {
430
+ ext: 'otf',
431
+ mime: 'font/otf'
432
+ };
433
+ }
434
+
435
+ if (check([0x00, 0x00, 0x01, 0x00])) {
436
+ return {
437
+ ext: 'ico',
438
+ mime: 'image/x-icon'
439
+ };
440
+ }
441
+
442
+ if (check([0x46, 0x4C, 0x56, 0x01])) {
443
+ return {
444
+ ext: 'flv',
445
+ mime: 'video/x-flv'
446
+ };
447
+ }
448
+
449
+ if (check([0x25, 0x21])) {
450
+ return {
451
+ ext: 'ps',
452
+ mime: 'application/postscript'
453
+ };
454
+ }
455
+
456
+ if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
457
+ return {
458
+ ext: 'xz',
459
+ mime: 'application/x-xz'
460
+ };
461
+ }
462
+
463
+ if (check([0x53, 0x51, 0x4C, 0x69])) {
464
+ return {
465
+ ext: 'sqlite',
466
+ mime: 'application/x-sqlite3'
467
+ };
468
+ }
469
+
470
+ if (check([0x4E, 0x45, 0x53, 0x1A])) {
471
+ return {
472
+ ext: 'nes',
473
+ mime: 'application/x-nintendo-nes-rom'
474
+ };
475
+ }
476
+
477
+ if (check([0x43, 0x72, 0x32, 0x34])) {
478
+ return {
479
+ ext: 'crx',
480
+ mime: 'application/x-google-chrome-extension'
481
+ };
482
+ }
483
+
484
+ if (
485
+ check([0x4D, 0x53, 0x43, 0x46]) ||
486
+ check([0x49, 0x53, 0x63, 0x28])
487
+ ) {
488
+ return {
489
+ ext: 'cab',
490
+ mime: 'application/vnd.ms-cab-compressed'
491
+ };
492
+ }
493
+
494
+ // Needs to be before `ar` check
495
+ if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79])) {
496
+ return {
497
+ ext: 'deb',
498
+ mime: 'application/x-deb'
499
+ };
500
+ }
501
+
502
+ if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) {
503
+ return {
504
+ ext: 'ar',
505
+ mime: 'application/x-unix-archive'
506
+ };
507
+ }
508
+
509
+ if (check([0xED, 0xAB, 0xEE, 0xDB])) {
510
+ return {
511
+ ext: 'rpm',
512
+ mime: 'application/x-rpm'
513
+ };
514
+ }
515
+
516
+ if (
517
+ check([0x1F, 0xA0]) ||
518
+ check([0x1F, 0x9D])
519
+ ) {
520
+ return {
521
+ ext: 'Z',
522
+ mime: 'application/x-compress'
523
+ };
524
+ }
525
+
526
+ if (check([0x4C, 0x5A, 0x49, 0x50])) {
527
+ return {
528
+ ext: 'lz',
529
+ mime: 'application/x-lzip'
530
+ };
531
+ }
532
+
533
+ if (check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
534
+ return {
535
+ ext: 'msi',
536
+ mime: 'application/x-msi'
537
+ };
538
+ }
539
+
540
+ if (check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
541
+ return {
542
+ ext: 'mxf',
543
+ mime: 'application/mxf'
544
+ };
545
+ }
546
+
547
+ if (check([0x47], {offset: 4}) && (check([0x47], {offset: 192}) || check([0x47], {offset: 196}))) {
548
+ return {
549
+ ext: 'mts',
550
+ mime: 'video/mp2t'
551
+ };
552
+ }
553
+
554
+ if (check([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) {
555
+ return {
556
+ ext: 'blend',
557
+ mime: 'application/x-blender'
558
+ };
559
+ }
560
+
561
+ if (check([0x42, 0x50, 0x47, 0xFB])) {
562
+ return {
563
+ ext: 'bpg',
564
+ mime: 'image/bpg'
565
+ };
566
+ }
567
+
568
+ return null;
569
+ }
@@ -0,0 +1,158 @@
1
+ import {extname, resolve, join, dirname, basename, parse} from 'node:path';
2
+ import {tmpdir} from 'node:os';
3
+ import {mkdir, readFile, writeFile} from 'node:fs/promises';
4
+ import { constants } from 'node:fs';
5
+ import { imageExtensions } from './image-type.js';
6
+ import { audioExtensions } from './audio-type.js';
7
+ import { videoExtensions } from './video-type.js';
8
+ import { fileTypeFromBuffer } from './file-type.js';
9
+ import { access } from 'fs/promises';
10
+
11
+ /**
12
+ * @typedef {Object} BufferResult
13
+ * @property {Buffer} buffer
14
+ * @property {string} ext
15
+ * @property {string} mime
16
+ */
17
+
18
+ /**
19
+ * @param {string} url
20
+ * @returns {boolean}
21
+ */
22
+ function isRemoteUrl(url) {
23
+ try {
24
+ const parsedUrl = new URL(url);
25
+ // Check if the URL's protocol is HTTP, HTTPS, FTP, etc.
26
+ return ['http:', 'https:', 'ftp:'].includes(parsedUrl.protocol);
27
+ } catch (error) {
28
+ // If parsing fails, it might be a local path or an invalid URL
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @param {string} url
35
+ * @returns {Promise<BufferResult>}
36
+ */
37
+ async function fetchFileToBuffer(url) {
38
+ if (!isRemoteUrl(url)) {
39
+ throw new Error('URL must be a remote resource');
40
+ }
41
+ const response = await fetch(url);
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to fetch file "${url}": ${response.statusText}`);
44
+ }
45
+ return {
46
+ buffer: response.buffer(),
47
+ ext: extname(url).slice(1),
48
+ mime: response.headers.get('content-type')
49
+ };
50
+ }
51
+
52
+
53
+ /**
54
+ * @typedef {Object} ToBufferResult
55
+ * @property {Buffer} buffer
56
+ * @property {string} ext
57
+ * @property {string} mime
58
+ * @property {boolean} isAudio
59
+ * @property {boolean} isVideo
60
+ * @property {boolean} isImage
61
+ */
62
+
63
+ /**
64
+ * @param {string | Buffer} fileBufferOrFilepath
65
+ * @param {string} [source]
66
+ * @returns {Promise<ToBufferResult>}
67
+ */
68
+ export async function toBuffer(fileBufferOrFilepath, source) {
69
+ let buffer;
70
+ let mime;
71
+ let ext;
72
+ if (typeof fileBufferOrFilepath === 'string') {
73
+ // Read file
74
+ if (isRemoteUrl(fileBufferOrFilepath)) {
75
+ const result = await fetchFileToBuffer(fileBufferOrFilepath);
76
+ buffer = result.buffer;
77
+ ext = result.ext;
78
+ mime = result.mime;
79
+ } else {
80
+ if (source) {
81
+ // @TODO support windows
82
+ let relative = resolve(dirname(source), fileBufferOrFilepath);
83
+ if (await access(relative, constants.R_OK).then(() => true).catch(() => false)) {
84
+ buffer = await readFile(relative);
85
+ } else if (await access(resolve(source, fileBufferOrFilepath), constants.R_OK).then(() => true).catch(() => false)) {
86
+ buffer = await readFile(resolve(source, fileBufferOrFilepath));
87
+ } else {
88
+ console.error(`File not found: ${fileBufferOrFilepath} in relation to source: ${source}`);
89
+ buffer = await readFile(fileBufferOrFilepath);
90
+ }
91
+ } else {
92
+ buffer = await readFile(fileBufferOrFilepath);
93
+ }
94
+ }
95
+ } else if (Buffer.isBuffer(fileBufferOrFilepath)) {
96
+ buffer = fileBufferOrFilepath;
97
+ } else {
98
+ throw new Error(`Invalid img type: ${typeof fileBufferOrFilepath}`);
99
+ }
100
+
101
+ if (!buffer) {
102
+ throw new Error('No buffer found');
103
+ }
104
+
105
+ // Check if audio, then image, if all fails, then text
106
+ const result = await fileTypeFromBuffer(buffer);
107
+ if (!result) {
108
+ throw new Error('Invalid file type');
109
+ }
110
+
111
+ mime = result.mime;
112
+ ext = result.ext;
113
+ const isVideo = videoExtensions.has(ext);
114
+ const isAudio = audioExtensions.has(ext);
115
+ const isImage = imageExtensions.has(ext);
116
+
117
+ return {
118
+ buffer,
119
+ ext,
120
+ mime,
121
+ isAudio,
122
+ isImage,
123
+ isVideo
124
+ };
125
+ }
126
+
127
+ /**
128
+ * @param inputs
129
+ * @param {string} [inputs.dest]
130
+ * @param {string} inputs.fileName
131
+ * @param {string | Buffer} inputs.file
132
+ * @returns {Promise<ToBufferResult & {uri: string}>}
133
+ */
134
+ export async function writeFileToLocal({dest = resolve(tmpdir(), 'scout9'), file, fileName, source} = {}) {
135
+ // Ensure folder exists
136
+ // const fileFolder = resolve(cwd, dest);
137
+ const fileFolder = dest;
138
+ let filePath = resolve(fileFolder, fileName);
139
+ await mkdir(dirname(filePath), {recursive: true});
140
+
141
+ // Retrieve buffer and extension
142
+ const result = await toBuffer(file, source);
143
+ const {buffer, ext} = result;
144
+
145
+ // If file path has no extension, add it or replace it with the correct one
146
+ if (extname(filePath) === '') {
147
+ filePath = `${filePath}.${ext}`;
148
+ } else if (extname(filePath) !== ext) {
149
+ console.warn(`File extension mismatch: ${extname(filePath)} !== ${ext}, replacing to .${ext}`);
150
+ filePath = resolve(fileFolder, `${parse(filePath).name}.${ext}`);
151
+ }
152
+
153
+ // Write file to disk
154
+ await writeFile(filePath, buffer);
155
+
156
+ // Return file path
157
+ return {uri: filePath, ...result};
158
+ }