import { createHash } from 'node:crypto' import { readFileSync } from 'node:fs' import { strFromU8, unzipSync } from 'fflate' function requiredString(manifest, key) { const value = manifest?.[key] if (typeof value !== 'string' || value.trim() === '') { throw new Error(`plugin package manifest ${key} is missing`) } return value } /** * 发布身份只能来自即将上传的 ZIP,不能根据环境变量或缓存再次推导。这样 Bundle、 * Source Map 和服务端登记始终使用同一个构建产生的精确身份。 */ export function readReleasePackageIdentity(packageFile, expected) { const archive = unzipSync(new Uint8Array(readFileSync(packageFile))) const manifestBytes = archive['rn-manifest.json'] if (!manifestBytes) throw new Error('plugin package manifest is missing') let manifest try { manifest = JSON.parse(strFromU8(manifestBytes)) } catch { throw new Error('plugin package manifest is invalid JSON') } const packageName = requiredString(manifest, 'packageName') const moduleId = requiredString(manifest, 'moduleId') const platform = requiredString(manifest, 'platform') const moduleVersion = requiredString(manifest, 'version') const appVersion = requiredString(manifest, 'appVersion') const buildId = requiredString(manifest, 'buildId') const bundleFile = requiredString(manifest, 'bundleFile') const bundleHash = requiredString(manifest, 'bundleSha256') const bundleFormat = requiredString(manifest, 'bundleFormat') const nativeBaselineId = requiredString(manifest, 'builtAgainstNativeBaselineId') const appVersionRange = requiredString(manifest, 'appVersionRange') const minNativeApiLevel = manifest.minNativeApiLevel if (buildId === 'development') { throw new Error('plugin package was built without a release buildId; rebuild before publishing') } if (expected.packageName !== packageName) { throw new Error( `plugin package packageName mismatch: expected ${expected.packageName}, got ${packageName}`, ) } if (expected.moduleId !== moduleId) { throw new Error( `plugin package moduleId mismatch: expected ${expected.moduleId}, got ${moduleId}`, ) } if (expected.platform !== platform) { throw new Error( `plugin package platform mismatch: expected ${expected.platform}, got ${platform}`, ) } if (expected.moduleVersion !== moduleVersion) { throw new Error( `plugin package version mismatch: expected ${expected.moduleVersion}, got ${moduleVersion}`, ) } if (expected.appVersion !== appVersion) { throw new Error( `plugin package appVersion mismatch: expected ${expected.appVersion}, got ${appVersion}`, ) } if (!/^[a-f0-9]{64}$/i.test(bundleHash)) { throw new Error('plugin package bundleSha256 is invalid') } if (!Number.isInteger(minNativeApiLevel) || minNativeApiLevel < 1) { throw new Error('plugin package minNativeApiLevel is invalid') } if ( manifest.commonVersionRange !== undefined && (typeof manifest.commonVersionRange !== 'string' || !manifest.commonVersionRange.trim()) ) { throw new Error('plugin package commonVersionRange is invalid') } const bundleBytes = archive[bundleFile] if (!bundleBytes) throw new Error(`plugin package bundle is missing: ${bundleFile}`) const actualBundleHash = createHash('sha256').update(bundleBytes).digest('hex') if (actualBundleHash !== bundleHash.toLowerCase()) { throw new Error('plugin package bundleSha256 does not match bundle content') } return { manifest, packageName, moduleId, platform, moduleVersion, appVersion, buildId, bundleHash: actualBundleHash, bundleFormat, nativeBaselineId, appVersionRange, commonVersionRange: manifest.commonVersionRange, minNativeApiLevel, } }