refactor(expo-updates): 更新资产元数据结构,支持完整的hash计算和完整性检查

This commit is contained in:
2025-12-07 14:41:55 +08:00
parent 8fa741cc1b
commit 788534b981
4 changed files with 92 additions and 44 deletions

View File

@@ -70,7 +70,10 @@ export class ExpoUpdatesController {
},
});
form.append('extensions', JSON.stringify({ assetRequestHeaders: {} }), {
// protocol v1 recommends including integrity headers for every asset request
const extensions = this.expoUpdatesService.buildExtensions(manifest);
form.append('extensions', JSON.stringify(extensions), {
contentType: 'application/json',
});

View File

@@ -6,10 +6,13 @@ import * as crypto from 'crypto';
export interface AssetMetadata {
hash: string;
key: string;
// expo-updates protocol v1 uses fileSHA256 for integrity checks; keep hash for backward compatibility.
fileSHA256: string;
key: string; // md5 hash of file contents
contentType: string;
fileExtension?: string;
url: string;
metadata?: Record<string, any>;
}
export interface UpdateManifest {
@@ -23,6 +26,9 @@ export interface UpdateManifest {
expoClient?: Record<string, any>;
};
}
export interface ManifestExtensions {
assetRequestHeaders: Record<string, Record<string, string>>;
}
export interface NoUpdateAvailableDirective {
type: 'noUpdateAvailable';
@@ -31,6 +37,7 @@ export interface NoUpdateAvailableDirective {
interface MetadataFileAsset {
path: string;
ext: string;
metadata?: Record<string, any>;
}
interface MetadataFile {
@@ -56,9 +63,14 @@ interface MetadataCache {
// 缓存 hash 数据
interface HashCache {
hash: string;
sha256: string;
md5: string;
timestamp: number;
}
interface FileHashes {
sha256: string;
md5: string;
}
@Injectable()
export class ExpoUpdatesService {
@@ -108,23 +120,25 @@ export class ExpoUpdatesService {
// 构建 bundle URL 并计算真实 hash
const bundleUrl = baseUrl + platformMetadata.bundle;
const bundleHash = await this.calculateFileHash(bundleUrl);
const bundleHashes = await this.calculateFileHashes(bundleUrl);
// 构建 assets需要计算每个文件的真实 hash
const assets = await this.buildAssetsWithHash(platformMetadata.assets, baseUrl);
// ID 基于 bundle hash 生成,确保内容不变时 ID 固定
const updateId = this.configService.get<string>('EXPO_UPDATE_ID')
|| this.convertSHA256HashToUUID(bundleHash);
|| this.convertSHA256HashToUUID(bundleHashes.sha256);
return {
id: updateId,
createdAt: new Date().toISOString(),
runtimeVersion: configRuntimeVersion || runtimeVersion,
launchAsset: {
hash: bundleHash,
key: 'bundle',
hash: bundleHashes.sha256,
fileSHA256: bundleHashes.sha256,
key: bundleHashes.md5,
contentType: 'application/javascript',
fileExtension: '.bundle',
url: bundleUrl,
},
assets,
@@ -162,14 +176,14 @@ export class ExpoUpdatesService {
}
/**
* 计算文件的 SHA-256 hashBase64URL 编码
* 计算文件的 hashsha256 base64url + md5 hex
*/
private async calculateFileHash(url: string): Promise<string> {
private async calculateFileHashes(url: string): Promise<FileHashes> {
// 检查缓存
const cacheKey = `hash:${url}`;
const cached = this.hashCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.hash;
return { sha256: cached.sha256, md5: cached.md5 };
}
try {
@@ -177,13 +191,15 @@ export class ExpoUpdatesService {
responseType: 'arraybuffer',
timeout: 30000,
});
const hash = crypto.createHash('sha256').update(Buffer.from(response.data)).digest('base64url');
const buffer = Buffer.from(response.data);
const sha256 = crypto.createHash('sha256').update(buffer).digest('base64url');
const md5 = crypto.createHash('md5').update(buffer).digest('hex');
// 缓存 hash
this.hashCache.set(cacheKey, { hash, timestamp: Date.now() });
this.hashCache.set(cacheKey, { sha256, md5, timestamp: Date.now() });
logger.debug(`Calculated hash for ${url}: ${hash}`);
return hash;
logger.debug(`Calculated hashes for ${url}: sha256=${sha256}, md5=${md5}`);
return { sha256, md5 };
} catch (error) {
logger.error(`Failed to calculate hash for ${url}: ${error.message}`);
throw new BadRequestException(`Failed to fetch asset: ${url}`);
@@ -214,15 +230,16 @@ export class ExpoUpdatesService {
const batchResults = await Promise.all(
batch.map(async (asset) => {
const url = baseUrl + asset.path;
const key = asset.path.split('/').pop() || ''; // 使用文件名作为 key
const hash = await this.calculateFileHash(url);
const { sha256, md5 } = await this.calculateFileHashes(url);
return {
hash,
key,
hash: sha256,
fileSHA256: sha256,
key: md5,
contentType: this.getContentType(asset.ext),
fileExtension: `.${asset.ext}`,
url,
metadata: asset.metadata || {},
};
})
);
@@ -260,6 +277,24 @@ export class ExpoUpdatesService {
return { type: 'noUpdateAvailable' };
}
/**
* 构建 extensions主要提供每个资源的 integrity header
* 参考 expo-updates v1 协议,将 sha256 的 base64 形式用于 integrity。
*/
buildExtensions(manifest: UpdateManifest): ManifestExtensions {
const headers: Record<string, Record<string, string>> = {};
const allAssets = [manifest.launchAsset, ...manifest.assets];
for (const asset of allAssets) {
const b64 = this.base64FromBase64Url(asset.fileSHA256);
headers[asset.key] = {
integrity: `sha256-${b64}`,
};
}
return { assetRequestHeaders: headers };
}
/**
* 将 SHA-256 hash 转换为 UUID 格式
*/
@@ -268,4 +303,14 @@ export class ExpoUpdatesService {
const hex = Buffer.from(hash, 'base64url').toString('hex').slice(0, 32);
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
}
/**
* base64url -> base64用于 integrity header
*/
private base64FromBase64Url(value: string): string {
// Replace URL-safe chars and pad
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
const padding = (4 - (base64.length % 4)) % 4;
return base64.padEnd(base64.length + padding, '=');
}
}

View File

@@ -2971,7 +2971,7 @@ export class UsersService {
private getReleaseNotes(version: string): string {
// 这里可以从数据库或配置文件中获取版本发布说明
// 暂时返回示例数据
return '1. 优化多语言配置\n2. 锻炼通知点击直接查看锻炼详情\n3. 修复已知问题';
return '1. 支持健康档案管理\n2. 支持每日手绘健康图片生成\n3. 优化体重管理 UI';
}
/**

View File

@@ -889,12 +889,12 @@
"@napi-rs/nice-android-arm-eabi@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz#9a0cba12706ff56500df127d6f4caf28ddb94936"
integrity sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==
"@napi-rs/nice-android-arm64@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz#32fc32e9649bd759d2a39ad745e95766f6759d2f"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz#32fc32e9649bd759d2a39ad745e95766f6759d2f"
integrity sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==
"@napi-rs/nice-darwin-arm64@1.0.1":
@@ -904,67 +904,67 @@
"@napi-rs/nice-darwin-x64@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz#f1b1365a8370c6a6957e90085a9b4873d0e6a957"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz#f1b1365a8370c6a6957e90085a9b4873d0e6a957"
integrity sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==
"@napi-rs/nice-freebsd-x64@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz#4280f081efbe0b46c5165fdaea8b286e55a8f89e"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz#4280f081efbe0b46c5165fdaea8b286e55a8f89e"
integrity sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==
"@napi-rs/nice-linux-arm-gnueabihf@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz#07aec23a9467ed35eb7602af5e63d42c5d7bd473"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz#07aec23a9467ed35eb7602af5e63d42c5d7bd473"
integrity sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==
"@napi-rs/nice-linux-arm64-gnu@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz#038a77134cc6df3c48059d5a5e199d6f50fb9a90"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz#038a77134cc6df3c48059d5a5e199d6f50fb9a90"
integrity sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==
"@napi-rs/nice-linux-arm64-musl@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz#715d0906582ba0cff025109f42e5b84ea68c2bcc"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz#715d0906582ba0cff025109f42e5b84ea68c2bcc"
integrity sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==
"@napi-rs/nice-linux-ppc64-gnu@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz#ac1c8f781c67b0559fa7a1cd4ae3ca2299dc3d06"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz#ac1c8f781c67b0559fa7a1cd4ae3ca2299dc3d06"
integrity sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==
"@napi-rs/nice-linux-riscv64-gnu@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz#b0a430549acfd3920ffd28ce544e2fe17833d263"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz#b0a430549acfd3920ffd28ce544e2fe17833d263"
integrity sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==
"@napi-rs/nice-linux-s390x-gnu@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz#5b95caf411ad72a965885217db378c4d09733e97"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz#5b95caf411ad72a965885217db378c4d09733e97"
integrity sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==
"@napi-rs/nice-linux-x64-gnu@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz#a98cdef517549f8c17a83f0236a69418a90e77b7"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz#a98cdef517549f8c17a83f0236a69418a90e77b7"
integrity sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==
"@napi-rs/nice-linux-x64-musl@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz#5e26843eafa940138aed437c870cca751c8a8957"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.0.1.tgz#5e26843eafa940138aed437c870cca751c8a8957"
integrity sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==
"@napi-rs/nice-win32-arm64-msvc@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz#bd62617d02f04aa30ab1e9081363856715f84cd8"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz#bd62617d02f04aa30ab1e9081363856715f84cd8"
integrity sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==
"@napi-rs/nice-win32-ia32-msvc@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz#b8b7aad552a24836027473d9b9f16edaeabecf18"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz#b8b7aad552a24836027473d9b9f16edaeabecf18"
integrity sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==
"@napi-rs/nice-win32-x64-msvc@1.0.1":
version "1.0.1"
resolved "https://mirrors.tencent.com/npm/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz#37d8718b8f722f49067713e9f1e85540c9a3dd09"
resolved "https://registry.yarnpkg.com/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz#37d8718b8f722f49067713e9f1e85540c9a3dd09"
integrity sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==
"@napi-rs/nice@^1.0.1":
@@ -1271,47 +1271,47 @@
"@swc/core-darwin-x64@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-darwin-x64/-/core-darwin-x64-1.11.13.tgz#9cad870d48ebff805e8946ddcbe3d8312182f70b"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.11.13.tgz#9cad870d48ebff805e8946ddcbe3d8312182f70b"
integrity sha512-uSA4UwgsDCIysUPfPS8OrQTH2h9spO7IYFd+1NB6dJlVGUuR6jLKuMBOP1IeLeax4cGHayvkcwSJ3OvxHwgcZQ==
"@swc/core-linux-arm-gnueabihf@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.13.tgz#51839e5a850bfa300e2c838fee8379e4dba1de78"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.13.tgz#51839e5a850bfa300e2c838fee8379e4dba1de78"
integrity sha512-boVtyJzS8g30iQfe8Q46W5QE/cmhKRln/7NMz/5sBP/am2Lce9NL0d05NnFwEWJp1e2AMGHFOdRr3Xg1cDiPKw==
"@swc/core-linux-arm64-gnu@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.13.tgz#4145f1e504bdfa92604aee883d777bc8c4fba5d7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.13.tgz#4145f1e504bdfa92604aee883d777bc8c4fba5d7"
integrity sha512-+IK0jZ84zHUaKtwpV+T+wT0qIUBnK9v2xXD03vARubKF+eUqCsIvcVHXmLpFuap62dClMrhCiwW10X3RbXNlHw==
"@swc/core-linux-arm64-musl@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.13.tgz#b1813ae2e99e386ca16fff5af6601ac45ef57c5b"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.13.tgz#b1813ae2e99e386ca16fff5af6601ac45ef57c5b"
integrity sha512-+ukuB8RHD5BHPCUjQwuLP98z+VRfu+NkKQVBcLJGgp0/+w7y0IkaxLY/aKmrAS5ofCNEGqKL+AOVyRpX1aw+XA==
"@swc/core-linux-x64-gnu@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.13.tgz#13b89a0194c4033c01400e9c65d9c21c56a4a6cd"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.13.tgz#13b89a0194c4033c01400e9c65d9c21c56a4a6cd"
integrity sha512-q9H3WI3U3dfJ34tdv60zc8oTuWvSd5fOxytyAO9Pc5M82Hic3jjWaf2xBekUg07ubnMZpyfnv+MlD+EbUI3Llw==
"@swc/core-linux-x64-musl@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.13.tgz#0d0e5aa889dd4da69723e2287c3c1714d9bfd8aa"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.13.tgz#0d0e5aa889dd4da69723e2287c3c1714d9bfd8aa"
integrity sha512-9aaZnnq2pLdTbAzTSzy/q8dr7Woy3aYIcQISmw1+Q2/xHJg5y80ZzbWSWKYca/hKonDMjIbGR6dp299I5J0aeA==
"@swc/core-win32-arm64-msvc@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.13.tgz#ad7281f9467e3de09f52615afe2276a8ef738a9d"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.13.tgz#ad7281f9467e3de09f52615afe2276a8ef738a9d"
integrity sha512-n3QZmDewkHANcoHvtwvA6yJbmS4XJf0MBMmwLZoKDZ2dOnC9D/jHiXw7JOohEuzYcpLoL5tgbqmjxa3XNo9Oow==
"@swc/core-win32-ia32-msvc@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.13.tgz#046f6dbddb5b69a29bbaa98de104090a46088b74"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.13.tgz#046f6dbddb5b69a29bbaa98de104090a46088b74"
integrity sha512-wM+Nt4lc6YSJFthCx3W2dz0EwFNf++j0/2TQ0Js9QLJuIxUQAgukhNDVCDdq8TNcT0zuA399ALYbvj5lfIqG6g==
"@swc/core-win32-x64-msvc@1.11.13":
version "1.11.13"
resolved "https://mirrors.tencent.com/npm/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.13.tgz#0412620d8594a7d3e482d3e79d9e89d80f9a14c0"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.13.tgz#0412620d8594a7d3e482d3e79d9e89d80f9a14c0"
integrity sha512-+X5/uW3s1L5gK7wAo0E27YaAoidJDo51dnfKSfU7gF3mlEUuWH8H1bAy5OTt2mU4eXtfsdUMEVXSwhDlLtQkuA==
"@swc/core@^1.10.7":