@holo-js/kernel 0.3.26 → 0.3.28

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.
@@ -0,0 +1,9 @@
1
+ type FileContentInspection = {
2
+ readonly mimeType: string | null;
3
+ readonly passive: boolean;
4
+ };
5
+ declare const fileContentSampleBytes = 65536;
6
+ declare function mimeTypeFromFileName(fileName: string): string | null;
7
+ declare function inspectFileContent(sample: Uint8Array, fileName?: string): Promise<FileContentInspection>;
8
+
9
+ export { type FileContentInspection, fileContentSampleBytes, inspectFileContent, mimeTypeFromFileName };
@@ -0,0 +1,54 @@
1
+ // src/fileContent.ts
2
+ var fileContentSampleBytes = 65536;
3
+ var mimeTypesByExtension = Object.freeze({
4
+ avif: "image/avif",
5
+ css: "text/css",
6
+ csv: "text/csv",
7
+ gif: "image/gif",
8
+ htm: "text/html",
9
+ html: "text/html",
10
+ jpeg: "image/jpeg",
11
+ jpg: "image/jpeg",
12
+ json: "application/json",
13
+ md: "text/markdown",
14
+ mp3: "audio/mpeg",
15
+ pdf: "application/pdf",
16
+ png: "image/png",
17
+ svg: "image/svg+xml",
18
+ txt: "text/plain",
19
+ webp: "image/webp",
20
+ woff: "font/woff",
21
+ woff2: "font/woff2",
22
+ xml: "application/xml"
23
+ });
24
+ var passiveDocumentMimeTypes = /* @__PURE__ */ new Set([
25
+ "application/json",
26
+ "application/pdf",
27
+ "text/css",
28
+ "text/csv",
29
+ "text/markdown",
30
+ "text/plain"
31
+ ]);
32
+ function isPassiveMimeType(mimeType) {
33
+ if (!mimeType) return false;
34
+ return passiveDocumentMimeTypes.has(mimeType) || /^(?:image|audio|video|font)\//u.test(mimeType) && !mimeType.endsWith("+xml");
35
+ }
36
+ function mimeTypeFromFileName(fileName) {
37
+ const baseName = fileName.slice(Math.max(fileName.lastIndexOf("/"), fileName.lastIndexOf("\\")) + 1);
38
+ const separator = baseName.lastIndexOf(".");
39
+ if (separator < 1) return null;
40
+ return mimeTypesByExtension[baseName.slice(separator + 1).toLowerCase()] ?? null;
41
+ }
42
+ async function inspectFileContent(sample, fileName) {
43
+ const { fileTypeFromBuffer, supportedMimeTypes } = await import("file-type");
44
+ const detectableMimeTypes = supportedMimeTypes;
45
+ const detected = await fileTypeFromBuffer(sample.subarray(0, fileContentSampleBytes)).catch(() => void 0);
46
+ const named = fileName ? mimeTypeFromFileName(fileName) : null;
47
+ const mimeType = detected?.mime ?? (named && !detectableMimeTypes.has(named) ? named : null);
48
+ return { mimeType, passive: isPassiveMimeType(mimeType) };
49
+ }
50
+ export {
51
+ fileContentSampleBytes,
52
+ inspectFileContent,
53
+ mimeTypeFromFileName
54
+ };
@@ -0,0 +1,3 @@
1
+ declare function verifyDecodedImage(_contents: Uint8Array, _mimeType: string): Promise<boolean>;
2
+
3
+ export { verifyDecodedImage };
@@ -0,0 +1,7 @@
1
+ // src/imageDecoding.browser.ts
2
+ async function verifyDecodedImage(_contents, _mimeType) {
3
+ return true;
4
+ }
5
+ export {
6
+ verifyDecodedImage
7
+ };
@@ -0,0 +1,3 @@
1
+ declare function verifyDecodedImage(_contents: Uint8Array, _mimeType: string): Promise<boolean>;
2
+
3
+ export { verifyDecodedImage };
@@ -0,0 +1,7 @@
1
+ // src/imageDecoding.ts
2
+ async function verifyDecodedImage(_contents, _mimeType) {
3
+ throw new Error("[@holo-js/kernel] Server-side image validation must run on Node.js, where images can be fully decoded.");
4
+ }
5
+ export {
6
+ verifyDecodedImage
7
+ };
@@ -0,0 +1,3 @@
1
+ declare function verifyDecodedImage(contents: Uint8Array, mimeType: string): Promise<boolean>;
2
+
3
+ export { verifyDecodedImage };
@@ -0,0 +1,70 @@
1
+ // src/imageDecoding.node.ts
2
+ var maximumDecodedPixels = 4e7;
3
+ var maximumFrames = 1e3;
4
+ var decodedFormatMatchers = Object.freeze({
5
+ "image/avif": (metadata) => metadata.format === "heif" && metadata.compression === "av1",
6
+ "image/gif": (metadata) => metadata.format === "gif",
7
+ "image/jpeg": (metadata) => metadata.format === "jpeg",
8
+ "image/png": (metadata) => metadata.format === "png",
9
+ "image/tiff": (metadata) => metadata.format === "tiff",
10
+ "image/webp": (metadata) => metadata.format === "webp"
11
+ });
12
+ function isMissingModule(error) {
13
+ if (!(error instanceof Error)) return false;
14
+ const code = error.code;
15
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || isMissingModule(error.cause);
16
+ }
17
+ async function loadSharp() {
18
+ try {
19
+ return (await import("sharp")).default;
20
+ } catch (error) {
21
+ if (!isMissingModule(error)) throw error;
22
+ throw new Error('[@holo-js/kernel] Server-side image validation requires the optional "sharp" package. Install sharp in your application.', { cause: error });
23
+ }
24
+ }
25
+ function skipSubBlocks(bytes, start) {
26
+ let offset = start;
27
+ while (offset < bytes.length) {
28
+ const size = bytes[offset] ?? 0;
29
+ offset += 1;
30
+ if (size === 0) return offset;
31
+ offset += size;
32
+ }
33
+ return null;
34
+ }
35
+ function colorTableBytes(flags) {
36
+ return (flags & 128) === 0 ? 0 : 3 * (1 << (flags & 7) + 1);
37
+ }
38
+ function hasCompleteGifStructure(bytes) {
39
+ let offset = 13 + colorTableBytes(bytes[10] ?? 0);
40
+ while (offset !== null && offset < bytes.length) {
41
+ const introducer = bytes[offset];
42
+ if (introducer === 59) return true;
43
+ if (introducer === 33) {
44
+ offset = skipSubBlocks(bytes, offset + 2);
45
+ } else if (introducer === 44) {
46
+ offset = skipSubBlocks(bytes, offset + 10 + colorTableBytes(bytes[offset + 9] ?? 0) + 1);
47
+ } else {
48
+ return false;
49
+ }
50
+ }
51
+ return false;
52
+ }
53
+ async function verifyDecodedImage(contents, mimeType) {
54
+ const matchesDecodedFormat = decodedFormatMatchers[mimeType];
55
+ if (!matchesDecodedFormat) return false;
56
+ if (mimeType === "image/gif" && !hasCompleteGifStructure(contents)) return false;
57
+ const decode = await loadSharp();
58
+ try {
59
+ const image = decode(contents, { animated: true, failOn: "warning", limitInputPixels: maximumDecodedPixels });
60
+ const metadata = await image.metadata();
61
+ if (!matchesDecodedFormat(metadata) || (metadata.pages ?? 1) > maximumFrames) return false;
62
+ await image.stats();
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+ export {
69
+ verifyDecodedImage
70
+ };
package/dist/index.d.ts CHANGED
@@ -409,4 +409,37 @@ declare function resolveNormalizedRedisConnection(config: NormalizedHoloRedisCon
409
409
  declare function normalizeRedisConfig(config?: HoloRedisConfig): NormalizedHoloRedisConfig;
410
410
  declare function defineRedisConfig<TConfig extends HoloRedisConfig>(config: TConfig): Readonly<TConfig>;
411
411
 
412
- export { type ConcurrencyOptions, DEFAULT_HOLO_PROJECT_PATHS, DEFAULT_REDIS_CONNECTION, DEFAULT_REDIS_DB, DEFAULT_REDIS_HOST, DEFAULT_REDIS_PORT, type DatabaseLogger, HOLO_PROJECT_PREPARE_API_VERSION, type HoloDatabaseDriver, type HoloGeneratedProjectArtifact, type HoloManagedProjectArtifact, type HoloPluginBroadcastContributions, type HoloPluginCacheContributions, type HoloPluginCliContributions, type HoloPluginConfigContributions, type HoloPluginContributions, type HoloPluginDefinition, type HoloPluginDependencyContributions, type HoloPluginFrameworkContribution, type HoloPluginFrameworkTsconfigKind, type HoloPluginMailContributions, type HoloPluginMigrationContributions, type HoloPluginNotificationContributions, type HoloPluginPackageManager, type HoloPluginProjectContributions, type HoloPluginQueueContributions, type HoloPluginRuntimeContributions, type HoloPluginRuntimeMapContributions, type HoloPluginRuntimeModule, type HoloProjectArtifactContents, type HoloProjectConfig, type HoloProjectConnectionConfig, type HoloProjectDatabaseConfig, type HoloProjectPaths, type HoloProjectPrepareChange, type HoloProjectPrepareChangeKind, type HoloProjectPrepareCommand, type HoloProjectPrepareContext, type HoloProjectPrepareDiagnostic, HoloProjectPrepareError, type HoloProjectPrepareFailure, type HoloProjectPrepareFramework, type HoloProjectPrepareFullRetry, type HoloProjectPrepareLogger, type HoloProjectPrepareModule, type HoloProjectPreparePlugin, type HoloProjectPrepareResult, type HoloProjectPrepareRun, type HoloProjectPrepareSource, type HoloProjectPrepareWatch, type HoloProjectPreparedResult, type HoloProjectPreparer, type HoloRedisClusterNodeConfig, type HoloRedisConfig, type HoloRedisConnectionConfig, type LoadedHoloPluginDefinition, type MigrationErrorLog, type MigrationStartLog, type MigrationSuccessLog, type NormalizedHoloProjectConfig, type NormalizedHoloRedisClusterNodeConfig, type NormalizedHoloRedisConfig, type NormalizedHoloRedisConnectionConfig, type PluginLoadOptions, type QueryErrorLog, type QueryStartLog, type QuerySuccessLog, type RuntimeContribution, RuntimeLifecycle, type RuntimeLifecycleContext, type SeederErrorLog, type SeederStartLog, type SeederSuccessLog, type TransactionLog, type TransactionScopeKind, createRuntimeLifecycle, defineHoloPlugin, defineHoloProject, defineHoloProjectPreparer, defineRedisConfig, holoRedisDefaults, loadHoloPluginBootModules, loadHoloPluginContributionModules, loadHoloPluginDefinitions, normalizeHoloPluginDefinition, normalizeHoloProjectConfig, normalizeRedisConfig, resolveHoloPluginModulePath, resolveNormalizedRedisConnection };
412
+ type HoloRequestHeaders = Headers | ReadonlyArray<readonly [string, string]> | Record<string, string | readonly string[] | undefined> | {
413
+ readonly get?: (name: string) => string | null | undefined;
414
+ readonly forEach?: (callback: (value: string, key: string) => void) => void;
415
+ readonly entries?: () => Iterable<readonly [string, string]>;
416
+ };
417
+ type RequestUrlParts = {
418
+ readonly url?: string | URL;
419
+ readonly path?: string;
420
+ readonly headers?: HoloRequestHeaders;
421
+ };
422
+ type HoloRequestInput = RequestUrlParts & {
423
+ readonly request?: Request;
424
+ readonly req?: RequestUrlParts;
425
+ readonly node?: {
426
+ readonly req?: RequestUrlParts;
427
+ };
428
+ readonly web?: {
429
+ readonly request?: RequestUrlParts;
430
+ };
431
+ };
432
+ type HoloTrustedProxyHeaders = 'x-forwarded' | 'forwarded';
433
+ declare function resolveTrustedProxyHeaders(): HoloTrustedProxyHeaders | null;
434
+ declare function readForwardedParameter(headers: HoloRequestHeaders | undefined, name: string): string | undefined;
435
+ declare class HoloRequestUrlError extends TypeError {
436
+ readonly status = 400;
437
+ constructor(message: string);
438
+ }
439
+ declare function resolveRequestUrl(input: HoloRequestInput): URL;
440
+ declare const requestUrlInternals: Readonly<{
441
+ readForwardedParameter: typeof readForwardedParameter;
442
+ resolveTrustedProxyHeaders: typeof resolveTrustedProxyHeaders;
443
+ }>;
444
+
445
+ export { type ConcurrencyOptions, DEFAULT_HOLO_PROJECT_PATHS, DEFAULT_REDIS_CONNECTION, DEFAULT_REDIS_DB, DEFAULT_REDIS_HOST, DEFAULT_REDIS_PORT, type DatabaseLogger, HOLO_PROJECT_PREPARE_API_VERSION, type HoloDatabaseDriver, type HoloGeneratedProjectArtifact, type HoloManagedProjectArtifact, type HoloPluginBroadcastContributions, type HoloPluginCacheContributions, type HoloPluginCliContributions, type HoloPluginConfigContributions, type HoloPluginContributions, type HoloPluginDefinition, type HoloPluginDependencyContributions, type HoloPluginFrameworkContribution, type HoloPluginFrameworkTsconfigKind, type HoloPluginMailContributions, type HoloPluginMigrationContributions, type HoloPluginNotificationContributions, type HoloPluginPackageManager, type HoloPluginProjectContributions, type HoloPluginQueueContributions, type HoloPluginRuntimeContributions, type HoloPluginRuntimeMapContributions, type HoloPluginRuntimeModule, type HoloProjectArtifactContents, type HoloProjectConfig, type HoloProjectConnectionConfig, type HoloProjectDatabaseConfig, type HoloProjectPaths, type HoloProjectPrepareChange, type HoloProjectPrepareChangeKind, type HoloProjectPrepareCommand, type HoloProjectPrepareContext, type HoloProjectPrepareDiagnostic, HoloProjectPrepareError, type HoloProjectPrepareFailure, type HoloProjectPrepareFramework, type HoloProjectPrepareFullRetry, type HoloProjectPrepareLogger, type HoloProjectPrepareModule, type HoloProjectPreparePlugin, type HoloProjectPrepareResult, type HoloProjectPrepareRun, type HoloProjectPrepareSource, type HoloProjectPrepareWatch, type HoloProjectPreparedResult, type HoloProjectPreparer, type HoloRedisClusterNodeConfig, type HoloRedisConfig, type HoloRedisConnectionConfig, type HoloRequestHeaders, type HoloRequestInput, HoloRequestUrlError, type HoloTrustedProxyHeaders, type LoadedHoloPluginDefinition, type MigrationErrorLog, type MigrationStartLog, type MigrationSuccessLog, type NormalizedHoloProjectConfig, type NormalizedHoloRedisClusterNodeConfig, type NormalizedHoloRedisConfig, type NormalizedHoloRedisConnectionConfig, type PluginLoadOptions, type QueryErrorLog, type QueryStartLog, type QuerySuccessLog, type RuntimeContribution, RuntimeLifecycle, type RuntimeLifecycleContext, type SeederErrorLog, type SeederStartLog, type SeederSuccessLog, type TransactionLog, type TransactionScopeKind, createRuntimeLifecycle, defineHoloPlugin, defineHoloProject, defineHoloProjectPreparer, defineRedisConfig, holoRedisDefaults, loadHoloPluginBootModules, loadHoloPluginContributionModules, loadHoloPluginDefinitions, normalizeHoloPluginDefinition, normalizeHoloProjectConfig, normalizeRedisConfig, requestUrlInternals, resolveHoloPluginModulePath, resolveNormalizedRedisConnection, resolveRequestUrl };
package/dist/index.mjs CHANGED
@@ -426,6 +426,134 @@ function normalizeRedisConfig(config = {}) {
426
426
  function defineRedisConfig(config) {
427
427
  return Object.freeze({ ...config });
428
428
  }
429
+
430
+ // src/requestUrl.ts
431
+ function resolveTrustedProxyHeaders() {
432
+ const value = typeof process === "undefined" ? void 0 : process.env.HOLO_SECURITY_TRUST_PROXY?.trim().toLowerCase();
433
+ if (value === "forwarded") return "forwarded";
434
+ if (value === "x-forwarded" || value === "1" || value === "true" || value === "yes" || value === "on") return "x-forwarded";
435
+ return null;
436
+ }
437
+ function isHeaderTuples(headers) {
438
+ return Array.isArray(headers);
439
+ }
440
+ function readHeader(headers, name) {
441
+ if (!headers) return void 0;
442
+ if (isHeaderTuples(headers)) {
443
+ return headers.find(([key]) => key.toLowerCase() === name)?.[1];
444
+ }
445
+ if ("get" in headers && typeof headers.get === "function") return headers.get(name) ?? void 0;
446
+ if ("entries" in headers && typeof headers.entries === "function") {
447
+ for (const [key, value] of headers.entries()) {
448
+ if (key.toLowerCase() === name) return value;
449
+ }
450
+ return void 0;
451
+ }
452
+ if ("forEach" in headers && typeof headers.forEach === "function") {
453
+ let result;
454
+ headers.forEach((value, key) => {
455
+ if (key.toLowerCase() === name) result = value;
456
+ });
457
+ return result;
458
+ }
459
+ for (const [key, value] of Object.entries(headers)) {
460
+ if (key.toLowerCase() === name) {
461
+ return typeof value === "string" ? value : Array.isArray(value) ? value.join(",") : void 0;
462
+ }
463
+ }
464
+ return void 0;
465
+ }
466
+ function firstForwardedValue(value) {
467
+ return value?.split(",", 1)[0]?.trim().replace(/^"|"$/g, "");
468
+ }
469
+ function firstForwardedElement(value) {
470
+ const pairs = /* @__PURE__ */ new Map();
471
+ let key = "";
472
+ let current = "";
473
+ let readingValue = false;
474
+ let quoted = false;
475
+ let escaped = false;
476
+ const commit = () => {
477
+ const name = key.trim().toLowerCase();
478
+ if (readingValue && name && !pairs.has(name)) pairs.set(name, current.trim());
479
+ key = "";
480
+ current = "";
481
+ readingValue = false;
482
+ };
483
+ for (const character of value) {
484
+ if (escaped) {
485
+ current += character;
486
+ escaped = false;
487
+ } else if (quoted) {
488
+ if (character === "\\") escaped = true;
489
+ else if (character === '"') quoted = false;
490
+ else current += character;
491
+ } else if (character === ",") {
492
+ break;
493
+ } else if (character === ";") {
494
+ commit();
495
+ } else if (character === "=" && !readingValue) {
496
+ readingValue = true;
497
+ } else if (character === '"' && readingValue) {
498
+ quoted = true;
499
+ } else if (readingValue) {
500
+ current += character;
501
+ } else {
502
+ key += character;
503
+ }
504
+ }
505
+ commit();
506
+ return pairs;
507
+ }
508
+ function readForwardedParameter(headers, name) {
509
+ const forwarded = readHeader(headers, "forwarded");
510
+ return forwarded === void 0 ? void 0 : firstForwardedElement(forwarded).get(name);
511
+ }
512
+ function readTrustedProxyValue(headers, trustedProxyHeaders, name) {
513
+ if (trustedProxyHeaders === "forwarded") return readForwardedParameter(headers, name);
514
+ if (trustedProxyHeaders === "x-forwarded") return firstForwardedValue(readHeader(headers, `x-forwarded-${name}`));
515
+ return void 0;
516
+ }
517
+ var HoloRequestUrlError = class extends TypeError {
518
+ status = 400;
519
+ constructor(message) {
520
+ super(`[@holo-js/kernel] ${message}`);
521
+ this.name = "HoloRequestUrlError";
522
+ }
523
+ };
524
+ var invalidRequestHostMessage = "Request hosts must be a valid hostname and optional port.";
525
+ function parseRequestUrl(value, invalidMessage) {
526
+ if (!URL.canParse(String(value), "http://localhost")) throw new HoloRequestUrlError(invalidMessage);
527
+ return new URL(value, "http://localhost");
528
+ }
529
+ function resolveRequestUrl(input) {
530
+ const nested = input instanceof Request ? void 0 : input.request ?? input.web?.request ?? input.req ?? input.node?.req;
531
+ const headers = input.headers ?? nested?.headers;
532
+ const value = input.url ?? nested?.url ?? input.path ?? (nested && "path" in nested ? nested.path : void 0) ?? "/";
533
+ const invalidUrlMessage = "Request URLs must use http or https without credentials.";
534
+ const url = parseRequestUrl(value, invalidUrlMessage);
535
+ if (url.username || url.password || url.protocol !== "http:" && url.protocol !== "https:") {
536
+ throw new HoloRequestUrlError(invalidUrlMessage);
537
+ }
538
+ const trustedProxyHeaders = resolveTrustedProxyHeaders();
539
+ const protocol = (readTrustedProxyValue(headers, trustedProxyHeaders, "proto") ?? url.protocol.slice(0, -1)).toLowerCase();
540
+ if (protocol !== "http" && protocol !== "https") {
541
+ throw new HoloRequestUrlError("Request protocols must be http or https.");
542
+ }
543
+ const host = readTrustedProxyValue(headers, trustedProxyHeaders, "host") ?? readHeader(headers, "host") ?? url.host;
544
+ if (!host || /[\s,/@\\?#]/u.test(host)) {
545
+ throw new HoloRequestUrlError(invalidRequestHostMessage);
546
+ }
547
+ const origin = parseRequestUrl(`${protocol}://${host}`, invalidRequestHostMessage);
548
+ if (origin.pathname !== "/" || origin.username || origin.password || origin.search || origin.hash) {
549
+ throw new HoloRequestUrlError(invalidRequestHostMessage);
550
+ }
551
+ url.protocol = origin.protocol;
552
+ url.host = origin.host;
553
+ url.port = origin.port;
554
+ return url;
555
+ }
556
+ var requestUrlInternals = Object.freeze({ readForwardedParameter, resolveTrustedProxyHeaders });
429
557
  export {
430
558
  DEFAULT_HOLO_PROJECT_PATHS,
431
559
  DEFAULT_REDIS_CONNECTION,
@@ -434,6 +562,7 @@ export {
434
562
  DEFAULT_REDIS_PORT,
435
563
  HOLO_PROJECT_PREPARE_API_VERSION,
436
564
  HoloProjectPrepareError,
565
+ HoloRequestUrlError,
437
566
  RuntimeLifecycle,
438
567
  createRuntimeLifecycle,
439
568
  defineHoloPlugin,
@@ -449,6 +578,8 @@ export {
449
578
  normalizeHoloPluginDefinition,
450
579
  normalizeHoloProjectConfig,
451
580
  normalizeRedisConfig,
581
+ requestUrlInternals,
452
582
  resolveHoloPluginModulePath,
453
- resolveNormalizedRedisConnection
583
+ resolveNormalizedRedisConnection,
584
+ resolveRequestUrl
454
585
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/kernel",
3
- "version": "0.3.26",
3
+ "version": "0.3.28",
4
4
  "description": "Holo-JS Framework - dependency-free contracts, plugins, and runtime lifecycle",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,6 +14,17 @@
14
14
  "types": "./dist/http-errors.d.ts",
15
15
  "import": "./dist/http-errors.mjs",
16
16
  "default": "./dist/http-errors.mjs"
17
+ },
18
+ "./file-content": {
19
+ "types": "./dist/file-content.d.ts",
20
+ "import": "./dist/file-content.mjs",
21
+ "default": "./dist/file-content.mjs"
22
+ },
23
+ "./image-decoding": {
24
+ "types": "./dist/image-decoding.d.ts",
25
+ "browser": "./dist/image-decoding.browser.mjs",
26
+ "node": "./dist/image-decoding.node.mjs",
27
+ "default": "./dist/image-decoding.mjs"
17
28
  }
18
29
  },
19
30
  "main": "./dist/index.mjs",
@@ -29,11 +40,25 @@
29
40
  },
30
41
  "devDependencies": {
31
42
  "@types/node": "^22.10.2",
43
+ "file-type": "^22.2.0",
44
+ "sharp": "^0.35.4",
32
45
  "tsup": "^8.3.5",
33
46
  "typescript": "^5.7.2",
34
47
  "vitest": "^4.1.5"
35
48
  },
36
49
  "engines": {
37
50
  "node": "^22.12.0 || ^24.0.0 || ^26.0.0"
51
+ },
52
+ "peerDependencies": {
53
+ "file-type": "^22.2.0",
54
+ "sharp": "^0.35.4"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "file-type": {
58
+ "optional": true
59
+ },
60
+ "sharp": {
61
+ "optional": true
62
+ }
38
63
  }
39
64
  }