| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- import { execFile } from 'node:child_process'
- import { createHash } from 'node:crypto'
- import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs'
- import { join, sep } from 'node:path'
- export const NPM_PACKAGE = '@lsamien/yaoyao'
- export const NPM_RELEASE_SOURCE = `npm:${NPM_PACKAGE}`
- const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
- export function isNpmInstallation(root) {
- if (!root.includes(`${sep}node_modules${sep}`) || existsSync(join(root, '.git'))) return false
- try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name === NPM_PACKAGE }
- catch { return false }
- }
- export function parseNpmRelease(value) {
- if (!value || value.name !== NPM_PACKAGE || typeof value.version !== 'string' || !versionPattern.test(value.version))
- throw new Error('npm 发布包名称或版本无效')
- const { integrity, tarball } = value.dist ?? {}
- if (typeof integrity !== 'string' || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity))
- throw new Error('npm 发布包缺少 SHA-512 完整性信息')
- let url
- try { url = new URL(tarball) } catch { throw new Error('npm 下载地址无效') }
- if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password)
- throw new Error('npm 下载地址无效')
- return { version: value.version, integrity, tarball: url.href }
- }
- export function inspectNpmRelease(cwd) {
- return new Promise((resolve, reject) => {
- execFile('npm', ['view', `${NPM_PACKAGE}@latest`, '--json'], {
- cwd, encoding: 'utf8', timeout: 60000, maxBuffer: 4 * 1024 * 1024,
- }, (error, stdout) => {
- if (error) { reject(new Error('无法读取 npm 最新版本,请检查仓库连接和 npm 配置')); return }
- try { resolve(parseNpmRelease(JSON.parse(stdout))) } catch (cause) { reject(cause) }
- })
- })
- }
- /** Hash the published application's files. npm installs dependencies separately;
- * these files must remain identical between preparation and activation. */
- export function npmRuntimeDigest(root, version) {
- const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
- const release = JSON.parse(readFileSync(join(root, 'release.json'), 'utf8'))
- const build = JSON.parse(readFileSync(join(root, 'build-info.json'), 'utf8'))
- if (packageJson.name !== NPM_PACKAGE || packageJson.version !== version
- || release.schemaVersion !== 1 || release.releaseVersion !== version || release.webVersion !== version || release.gitTag !== `v${version}`)
- throw new Error('npm 包与目标发布版本不一致')
- if (!/^[a-f0-9]{40,64}$/.test(build.commit) || !Number.isSafeInteger(build.buildNumber))
- throw new Error('npm 包缺少有效的构建信息')
- for (const file of ['dist/index.html', 'dist-server/server/index.js', 'bin/hermes-yaoyao.mjs', 'bin/hermes-yaoyao-updater.mjs']) {
- if (!lstatSync(join(root, file)).isFile()) throw new Error(`npm 包缺少服务文件:${file}`)
- }
- const hash = createHash('sha256')
- function visit(directory, prefix = '') {
- for (const name of readdirSync(directory).sort()) {
- if (name === 'node_modules') continue
- const path = join(directory, name), relative = `${prefix}${name}`, stat = lstatSync(path)
- if (stat.isDirectory()) visit(path, `${relative}/`)
- else if (stat.isFile()) hash.update(relative).update('\0').update(createHash('sha256').update(readFileSync(path)).digest())
- else throw new Error('npm 服务文件包含不支持的链接或文件')
- }
- }
- visit(root)
- return hash.digest('hex')
- }
|