79 行
2.6 KiB
JavaScript
79 行
2.6 KiB
JavaScript
|
|
import { createHash } from 'node:crypto'
|
||
|
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
||
|
|
import path from 'node:path'
|
||
|
|
|
||
|
|
const NATIVE_EXTENSIONS = new Set([
|
||
|
|
'.c',
|
||
|
|
'.cc',
|
||
|
|
'.cmake',
|
||
|
|
'.cpp',
|
||
|
|
'.gradle',
|
||
|
|
'.h',
|
||
|
|
'.java',
|
||
|
|
'.kts',
|
||
|
|
'.kt',
|
||
|
|
'.properties',
|
||
|
|
'.toml',
|
||
|
|
'.xml',
|
||
|
|
])
|
||
|
|
const IGNORED_DIRECTORIES = new Set(['.cxx', '.gradle', 'build', 'generated'])
|
||
|
|
const IGNORED_FILES = new Set(['local.properties'])
|
||
|
|
|
||
|
|
function walkNativeFiles(directory, root = directory) {
|
||
|
|
if (!existsSync(directory)) return []
|
||
|
|
const files = []
|
||
|
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
||
|
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) continue
|
||
|
|
const absolute = path.join(directory, entry.name)
|
||
|
|
if (entry.isDirectory()) files.push(...walkNativeFiles(absolute, root))
|
||
|
|
else if (
|
||
|
|
!IGNORED_FILES.has(entry.name) &&
|
||
|
|
(NATIVE_EXTENSIONS.has(path.extname(entry.name)) || entry.name === 'gradlew')
|
||
|
|
) {
|
||
|
|
files.push({ absolute, relative: path.relative(root, absolute).split(path.sep).join('/') })
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return files
|
||
|
|
}
|
||
|
|
|
||
|
|
function packageJson(directory) {
|
||
|
|
const file = path.join(directory, 'package.json')
|
||
|
|
return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : null
|
||
|
|
}
|
||
|
|
|
||
|
|
function nativeDependencyVersions(root, hostPackage) {
|
||
|
|
const dependencies = { ...hostPackage.dependencies, ...hostPackage.devDependencies }
|
||
|
|
const native = []
|
||
|
|
for (const name of Object.keys(dependencies).sort()) {
|
||
|
|
const directory = path.join(root, 'node_modules', ...name.split('/'))
|
||
|
|
const manifest = packageJson(directory)
|
||
|
|
if (!manifest) continue
|
||
|
|
const hasNativeCode =
|
||
|
|
existsSync(path.join(directory, 'android')) ||
|
||
|
|
existsSync(path.join(directory, 'react-native.config.js')) ||
|
||
|
|
manifest.codegenConfig !== undefined
|
||
|
|
if (hasNativeCode || name === 'react-native') {
|
||
|
|
native.push(`${name}@${manifest.version ?? dependencies[name]}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return native
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Deterministic identity of code and dependencies that can only change through a full APK. */
|
||
|
|
export function computeNativeBaseline(root = process.cwd()) {
|
||
|
|
const hostPackage = packageJson(root)
|
||
|
|
if (!hostPackage) throw new Error('package.json is missing')
|
||
|
|
const hash = createHash('sha256')
|
||
|
|
for (const dependency of nativeDependencyVersions(root, hostPackage)) {
|
||
|
|
hash.update(`dependency\0${dependency}\0`)
|
||
|
|
}
|
||
|
|
for (const file of walkNativeFiles(path.join(root, 'android')).sort((a, b) =>
|
||
|
|
a.relative.localeCompare(b.relative),
|
||
|
|
)) {
|
||
|
|
hash.update(`file\0${file.relative}\0`)
|
||
|
|
hash.update(readFileSync(file.absolute))
|
||
|
|
hash.update('\0')
|
||
|
|
}
|
||
|
|
return `android-sha256:${hash.digest('hex')}`
|
||
|
|
}
|