mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-07-20 03:07:43 +00:00
186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
/**
|
|
* 构建前确保 esbuild 原生二进制可用。
|
|
*
|
|
* Windows 上 @esbuild/win32-x64/esbuild.exe 常被杀毒删除,或 npm postinstall
|
|
* 在二进制缺失时调用 install.js 形成死循环。本脚本直接从 npm 拉取 tgz 解压,
|
|
* 不依赖 esbuild/install.js。
|
|
*
|
|
* 若 npm install 因 esbuild postinstall 失败,请先:
|
|
* npm install --ignore-scripts
|
|
* node scripts/ensure-esbuild.cjs
|
|
*/
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const https = require('https')
|
|
const zlib = require('zlib')
|
|
const { spawnSync, execFileSync } = require('child_process')
|
|
|
|
const ROOT = path.resolve(__dirname, '..')
|
|
const DEFAULT_ESBUILD_VERSION = '0.16.17'
|
|
|
|
function platformPackage() {
|
|
const key = `${process.platform} ${process.arch} ${require('os').endianness()}`
|
|
const map = {
|
|
'win32 x64 LE': { pkg: '@esbuild/win32-x64', subpath: 'esbuild.exe' },
|
|
'win32 ia32 LE': { pkg: '@esbuild/win32-ia32', subpath: 'esbuild.exe' },
|
|
'win32 arm64 LE': { pkg: '@esbuild/win32-arm64', subpath: 'esbuild.exe' },
|
|
'darwin x64 LE': { pkg: '@esbuild/darwin-x64', subpath: 'bin/esbuild' },
|
|
'darwin arm64 LE': { pkg: '@esbuild/darwin-arm64', subpath: 'bin/esbuild' },
|
|
'linux x64 LE': { pkg: '@esbuild/linux-x64', subpath: 'bin/esbuild' }
|
|
}
|
|
const hit = map[key]
|
|
if (!hit) throw new Error(`Unsupported platform for esbuild: ${key}`)
|
|
return hit
|
|
}
|
|
|
|
function resolveEsbuildVersion() {
|
|
const candidates = [
|
|
path.join(ROOT, 'node_modules', 'esbuild', 'package.json'),
|
|
path.join(ROOT, 'node_modules', 'vite', 'node_modules', 'esbuild', 'package.json')
|
|
]
|
|
for (const file of candidates) {
|
|
try {
|
|
return require(file).version
|
|
} catch {
|
|
// continue
|
|
}
|
|
}
|
|
return DEFAULT_ESBUILD_VERSION
|
|
}
|
|
|
|
function platformBinaryPath() {
|
|
const { pkg, subpath } = platformPackage()
|
|
return path.join(ROOT, 'node_modules', ...pkg.split('/'), subpath)
|
|
}
|
|
|
|
function downloadedBinaryPath(version) {
|
|
const { pkg, subpath } = platformPackage()
|
|
const libDir = path.join(ROOT, 'node_modules', 'esbuild', 'lib')
|
|
const base = path.basename(subpath)
|
|
return path.join(libDir, `downloaded-${pkg.replace('/', '-')}-${base}`)
|
|
}
|
|
|
|
function binaryCandidates(version) {
|
|
return [platformBinaryPath(), downloadedBinaryPath(version)]
|
|
}
|
|
|
|
function fetchBuffer(url) {
|
|
return new Promise((resolve, reject) => {
|
|
https.get(url, (res) => {
|
|
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
return fetchBuffer(res.headers.location).then(resolve, reject)
|
|
}
|
|
if (res.statusCode !== 200) {
|
|
return reject(new Error(`HTTP ${res.statusCode} for ${url}`))
|
|
}
|
|
const chunks = []
|
|
res.on('data', (c) => chunks.push(c))
|
|
res.on('end', () => resolve(Buffer.concat(chunks)))
|
|
}).on('error', reject)
|
|
})
|
|
}
|
|
|
|
function extractFileFromTarGzip(buffer, subpath) {
|
|
try {
|
|
buffer = zlib.unzipSync(buffer)
|
|
} catch (err) {
|
|
throw new Error(`Invalid gzip: ${err.message}`)
|
|
}
|
|
const str = (i, n) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '')
|
|
let offset = 0
|
|
const target = `package/${subpath}`
|
|
while (offset < buffer.length) {
|
|
const name = str(offset, 100)
|
|
const size = parseInt(str(offset + 124, 12), 8)
|
|
offset += 512
|
|
if (!Number.isNaN(size)) {
|
|
if (name === target) return buffer.subarray(offset, offset + size)
|
|
offset += (size + 511) & ~511
|
|
}
|
|
}
|
|
throw new Error(`Not found in archive: ${target}`)
|
|
}
|
|
|
|
async function downloadPlatformBinary(version) {
|
|
const { pkg, subpath } = platformPackage()
|
|
const shortName = pkg.replace('@esbuild/', '')
|
|
const url = `https://registry.npmjs.org/${pkg}/-/${shortName}-${version}.tgz`
|
|
console.log(`[ensure-esbuild] downloading ${pkg}@${version} ...`)
|
|
const tgz = await fetchBuffer(url)
|
|
const data = extractFileFromTarGzip(tgz, subpath)
|
|
const dest = platformBinaryPath()
|
|
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
fs.writeFileSync(dest, data)
|
|
if (process.platform !== 'win32') fs.chmodSync(dest, 0o755)
|
|
console.log(`[ensure-esbuild] wrote ${path.relative(ROOT, dest)} (${data.length} bytes)`)
|
|
return dest
|
|
}
|
|
|
|
function copyToDownloadedCache(version, src) {
|
|
const cache = downloadedBinaryPath(version)
|
|
const libDir = path.dirname(cache)
|
|
if (!fs.existsSync(path.join(ROOT, 'node_modules', 'esbuild'))) return
|
|
fs.mkdirSync(libDir, { recursive: true })
|
|
fs.copyFileSync(src, cache)
|
|
}
|
|
|
|
function verifyBinary(binPath, version) {
|
|
const out = execFileSync(binPath, ['--version'], { stdio: 'pipe' }).toString().trim()
|
|
if (out !== version) {
|
|
throw new Error(`Expected esbuild ${version}, got ${out}`)
|
|
}
|
|
}
|
|
|
|
function hasWorkingBinary(version) {
|
|
for (const p of binaryCandidates(version)) {
|
|
try {
|
|
if (fs.existsSync(p) && fs.statSync(p).size > 1024) {
|
|
verifyBinary(p, version)
|
|
return p
|
|
}
|
|
} catch {
|
|
// try next
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function repair(version) {
|
|
const dest = await downloadPlatformBinary(version)
|
|
copyToDownloadedCache(version, dest)
|
|
verifyBinary(dest, version)
|
|
}
|
|
|
|
async function main() {
|
|
const version = resolveEsbuildVersion()
|
|
const existing = hasWorkingBinary(version)
|
|
if (existing) {
|
|
console.log(`[ensure-esbuild] ok (${path.relative(ROOT, existing)}, v${version})`)
|
|
return
|
|
}
|
|
|
|
console.log(`[ensure-esbuild] binary missing or invalid, repairing (esbuild@${version})...`)
|
|
try {
|
|
await repair(version)
|
|
} catch (err) {
|
|
console.error('[ensure-esbuild] repair failed:', err.message || err)
|
|
console.error('')
|
|
console.error('Try:')
|
|
console.error(' npm install --ignore-scripts')
|
|
console.error(' node scripts/ensure-esbuild.cjs')
|
|
process.exit(1)
|
|
}
|
|
|
|
const ok = hasWorkingBinary(version)
|
|
if (!ok) {
|
|
console.error('[ensure-esbuild] binary still not usable after repair.')
|
|
process.exit(1)
|
|
}
|
|
console.log('[ensure-esbuild] ok')
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('[ensure-esbuild]', err)
|
|
process.exit(1)
|
|
})
|