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

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 +33 -0
  2. package/dist/{index-92deaa5f.cjs → exports-212ef6be.cjs} +46636 -4591
  3. package/dist/index.cjs +58 -15
  4. package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-54a3ab5f.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 +37 -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 +132 -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 +164 -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,164 @@
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
+ let buffer;
46
+ if ('buffer' in response) {
47
+ buffer = await response.buffer();
48
+ } else {
49
+ buffer = await response.arrayBuffer().then(a => Buffer.from(a));
50
+ }
51
+ return {
52
+ buffer,
53
+ ext: extname(url).slice(1),
54
+ mime: response.headers.get('content-type')
55
+ };
56
+ }
57
+
58
+
59
+ /**
60
+ * @typedef {Object} ToBufferResult
61
+ * @property {Buffer} buffer
62
+ * @property {string} ext
63
+ * @property {string} mime
64
+ * @property {boolean} isAudio
65
+ * @property {boolean} isVideo
66
+ * @property {boolean} isImage
67
+ */
68
+
69
+ /**
70
+ * @param {string | Buffer} fileBufferOrFilepath
71
+ * @param {string} [source]
72
+ * @returns {Promise<ToBufferResult>}
73
+ */
74
+ export async function toBuffer(fileBufferOrFilepath, source) {
75
+ let buffer;
76
+ let mime;
77
+ let ext;
78
+ if (typeof fileBufferOrFilepath === 'string') {
79
+ // Read file
80
+ if (isRemoteUrl(fileBufferOrFilepath)) {
81
+ const result = await fetchFileToBuffer(fileBufferOrFilepath);
82
+ buffer = result.buffer;
83
+ ext = result.ext;
84
+ mime = result.mime;
85
+ } else {
86
+ if (source) {
87
+ // @TODO support windows
88
+ let relative = resolve(dirname(source), fileBufferOrFilepath);
89
+ if (await access(relative, constants.R_OK).then(() => true).catch(() => false)) {
90
+ buffer = await readFile(relative);
91
+ } else if (await access(resolve(source, fileBufferOrFilepath), constants.R_OK).then(() => true).catch(() => false)) {
92
+ buffer = await readFile(resolve(source, fileBufferOrFilepath));
93
+ } else {
94
+ console.error(`File not found: ${fileBufferOrFilepath} in relation to source: ${source}`);
95
+ buffer = await readFile(fileBufferOrFilepath);
96
+ }
97
+ } else {
98
+ buffer = await readFile(fileBufferOrFilepath);
99
+ }
100
+ }
101
+ } else if (Buffer.isBuffer(fileBufferOrFilepath)) {
102
+ buffer = fileBufferOrFilepath;
103
+ } else {
104
+ throw new Error(`Invalid img type: ${typeof fileBufferOrFilepath}`);
105
+ }
106
+
107
+ if (!buffer) {
108
+ throw new Error('No buffer found');
109
+ }
110
+
111
+ // Check if audio, then image, if all fails, then text
112
+ const result = await fileTypeFromBuffer(buffer);
113
+ if (!result) {
114
+ throw new Error('Invalid file type');
115
+ }
116
+
117
+ mime = result.mime;
118
+ ext = result.ext;
119
+ const isVideo = videoExtensions.has(ext);
120
+ const isAudio = audioExtensions.has(ext);
121
+ const isImage = imageExtensions.has(ext);
122
+
123
+ return {
124
+ buffer,
125
+ ext,
126
+ mime,
127
+ isAudio,
128
+ isImage,
129
+ isVideo
130
+ };
131
+ }
132
+
133
+ /**
134
+ * @param inputs
135
+ * @param {string} [inputs.dest]
136
+ * @param {string} inputs.fileName
137
+ * @param {string | Buffer} inputs.file
138
+ * @returns {Promise<ToBufferResult & {uri: string}>}
139
+ */
140
+ export async function writeFileToLocal({dest = resolve(tmpdir(), 'scout9'), file, fileName, source} = {}) {
141
+ // Ensure folder exists
142
+ // const fileFolder = resolve(cwd, dest);
143
+ const fileFolder = dest;
144
+ let filePath = resolve(fileFolder, fileName);
145
+ await mkdir(dirname(filePath), {recursive: true});
146
+
147
+ // Retrieve buffer and extension
148
+ const result = await toBuffer(file, source);
149
+ const {buffer, ext} = result;
150
+
151
+ // If file path has no extension, add it or replace it with the correct one
152
+ if (extname(filePath) === '') {
153
+ filePath = `${filePath}.${ext}`;
154
+ } else if (extname(filePath) !== ext) {
155
+ console.warn(`File extension mismatch: ${extname(filePath)} !== ${ext}, replacing to .${ext}`);
156
+ filePath = resolve(fileFolder, `${parse(filePath).name}.${ext}`);
157
+ }
158
+
159
+ // Write file to disk
160
+ await writeFile(filePath, buffer);
161
+
162
+ // Return file path
163
+ return {uri: filePath, ...result};
164
+ }