Files
Ncurator/scripts/build/native/better-sqlite3.mjs
T
hy 7b057c05f8 refactor: reorganize scripts and update paths for build and release processes
- Removed obsolete test script `test_crawl4ai_fit_markdown.py`.
- Deleted `verify-package-main-obfuscation.mjs` as it is no longer needed.
- Updated paths in `app-update-publish-config.test.ts` to reflect new script locations.
- Refactored `package-content-policy.test.ts` to use new paths for content policy scripts.
- Adjusted `package-main-obfuscation.test.ts` to point to the new obfuscation script locations.
- Modified `package-size-budget.test.ts` to use updated paths for size budget scripts.
- Changed `r2-update-metadata-contract.test.ts` to reference the new metadata script.
- Updated `release-fragment-contract.test.ts` to use the new fragment contract script.
- Refactored `release-storage-layout.test.ts` to point to the new storage layout script.
- Added `runtime-trace.test.ts` to test runtime tracing functionality.
- Updated `node-pty-native-release.test.ts` to reflect new paths for native release scripts.
2026-07-20 17:03:38 +08:00

268 lines
8.5 KiB
JavaScript

import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { mkdir, mkdtemp, rm } from 'node:fs/promises'
import { createWriteStream, createReadStream } from 'node:fs'
import { get } from 'node:https'
import os from 'node:os'
import path from 'node:path'
import { pipeline } from 'node:stream/promises'
import { createGunzip } from 'node:zlib'
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const root = process.cwd()
const cacheRoot = path.join(root, 'node_modules', '.native-cache', 'better-sqlite3')
const resourceRoot = path.join(root, 'resources', 'native', 'better-sqlite3')
const manifestPath = path.join(cacheRoot, 'manifest.json')
const binaryRelativePath = path.join('build', 'Release', 'better_sqlite3.node')
const resolveFromRoot = (id) => require.resolve(id, { paths: [root] })
const resolveFrom = (id, paths = [root]) => require.resolve(id, { paths })
const readPackage = (id) => require(resolveFromRoot(`${id}/package.json`))
const getBetterSqliteDir = () => path.dirname(resolveFromRoot('better-sqlite3/package.json'))
const getElectronAbi = () => {
const electronVersion = readPackage('electron').version
const betterSqliteDir = getBetterSqliteDir()
const prebuildInstallPackagePath = require.resolve('prebuild-install/package.json', {
paths: [betterSqliteDir]
})
const prebuildInstallDir = path.dirname(prebuildInstallPackagePath)
const { getAbi } = require(require.resolve('node-abi', { paths: [prebuildInstallDir] }))
return {
electronVersion,
abi: getAbi(electronVersion, 'electron')
}
}
const targetInfo = (target) => {
const platform = process.platform
const arch = process.arch
if (target === 'node') {
return {
target,
runtime: 'node',
runtimeVersion: process.versions.node,
abi: process.versions.modules,
platform,
arch,
cacheDirName: `node-v${process.versions.modules}-${platform}-${arch}`
}
}
if (target === 'electron') {
const { electronVersion, abi } = getElectronAbi()
return {
target,
runtime: 'electron',
runtimeVersion: electronVersion,
abi,
platform,
arch,
cacheDirName: `electron-v${abi}-${platform}-${arch}`
}
}
throw new Error(`unknown native target: ${target}`)
}
const cacheDir = (info) => path.join(cacheRoot, info.cacheDirName)
const cacheBinaryPath = (info) => path.join(cacheDir(info), 'better_sqlite3.node')
const resourceDir = (info) => path.join(resourceRoot, info.cacheDirName)
const resourceBinaryPath = (info) => path.join(resourceDir(info), 'better_sqlite3.node')
const ensureBinaryExists = (binaryPath, label) => {
if (!existsSync(binaryPath)) {
throw new Error(`${label} does not exist: ${binaryPath}`)
}
}
const download = (url, targetPath, redirectCount = 0) =>
new Promise((resolve, reject) => {
if (redirectCount > 5) {
reject(new Error(`too many redirects while downloading ${url}`))
return
}
const request = get(
url,
{
headers: {
'User-Agent': 'ncurator-native-prepare'
}
},
(response) => {
const statusCode = response.statusCode || 0
const location = response.headers.location
if (statusCode >= 300 && statusCode < 400 && location) {
response.resume()
download(new URL(location, url).toString(), targetPath, redirectCount + 1)
.then(resolve)
.catch(reject)
return
}
if (statusCode !== 200) {
response.resume()
reject(new Error(`download failed with HTTP ${statusCode}: ${url}`))
return
}
pipeline(response, createWriteStream(targetPath)).then(resolve).catch(reject)
}
)
request.setTimeout(180_000, () => {
request.destroy(new Error(`download timed out: ${url}`))
})
request.on('error', reject)
})
const readTar = () => {
const betterSqliteDir = getBetterSqliteDir()
const prebuildInstallPackagePath = require.resolve('prebuild-install/package.json', {
paths: [betterSqliteDir]
})
const prebuildInstallDir = path.dirname(prebuildInstallPackagePath)
return require(resolveFrom('tar-fs', [prebuildInstallDir]))
}
const downloadNativeToCache = async (info, options = {}) => {
const betterSqliteVersion = readPackage('better-sqlite3').version
const assetName = `better-sqlite3-v${betterSqliteVersion}-${info.runtime}-v${info.abi}-${info.platform}-${info.arch}.tar.gz`
const url = `https://github.com/WiseLibs/better-sqlite3/releases/download/v${betterSqliteVersion}/${assetName}`
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'ncurator-better-sqlite3-'))
const archivePath = path.join(tempDir, assetName)
const extractDir = path.join(tempDir, 'extract')
const extractedBinaryPath = path.join(extractDir, binaryRelativePath)
const targetBinaryPath = cacheBinaryPath(info)
try {
await mkdir(extractDir, { recursive: true })
const log = options.silent ? console.error : console.log
log(
`[native] downloading better-sqlite3 ${info.runtime} abi ${info.abi} (${info.platform}-${info.arch})`
)
await download(url, archivePath)
await pipeline(
createReadStream(archivePath),
createGunzip(),
readTar().extract(extractDir, {
readable: true,
writable: true,
hardlinkAsFilesFallback: true
})
)
ensureBinaryExists(extractedBinaryPath, 'downloaded better-sqlite3 binary')
mkdirSync(path.dirname(targetBinaryPath), { recursive: true })
copyFileSync(extractedBinaryPath, targetBinaryPath)
} finally {
await rm(tempDir, { recursive: true, force: true })
}
}
const writeManifest = (infos) => {
mkdirSync(cacheRoot, { recursive: true })
writeFileSync(
manifestPath,
`${JSON.stringify(
{
generatedAt: new Date().toISOString(),
targets: infos.map((info) => ({
...info,
cacheDir: path.relative(root, cacheDir(info)),
cacheBinary: path.relative(root, cacheBinaryPath(info))
}))
},
null,
2
)}\n`
)
}
const ensureCache = async (target, options = {}) => {
const info = targetInfo(target)
if (!existsSync(cacheBinaryPath(info))) {
await downloadNativeToCache(info, options)
}
ensureBinaryExists(cacheBinaryPath(info), `${info.runtime} cached better-sqlite3 binary`)
writeManifest([info])
if (!options.silent) {
console.log(
`[native] ready ${info.runtime} abi ${info.abi}: ${path.relative(root, cacheBinaryPath(info))}`
)
}
return info
}
const printPath = async (target) => {
const info = await ensureCache(target, { silent: true })
process.stdout.write(`${cacheBinaryPath(info)}\n`)
}
const exportElectronResource = async () => {
const info = await ensureCache('electron')
mkdirSync(resourceDir(info), { recursive: true })
copyFileSync(cacheBinaryPath(info), resourceBinaryPath(info))
console.log(`[native] exported ${path.relative(root, resourceBinaryPath(info))}`)
}
const status = () => {
if (existsSync(manifestPath)) {
console.log(readFileSync(manifestPath, 'utf8').trim())
} else {
console.log('[native] no manifest')
}
for (const target of ['node', 'electron']) {
const info = targetInfo(target)
console.log(
`[native] cache ${target}: ${existsSync(cacheBinaryPath(info)) ? 'ready' : 'missing'} ${path.relative(
root,
cacheBinaryPath(info)
)}`
)
console.log(
`[native] resource ${target}: ${existsSync(resourceBinaryPath(info)) ? 'ready' : 'missing'} ${path.relative(
root,
resourceBinaryPath(info)
)}`
)
}
}
const clean = () => {
rmSync(cacheRoot, { recursive: true, force: true })
rmSync(resourceRoot, { recursive: true, force: true })
console.log('[native] cache and resources removed')
}
const main = async () => {
const command = process.argv[2] || 'status'
const target = process.argv[3]
if (command === 'ensure') {
await ensureCache(target || 'node')
return
}
if (command === 'path') {
await printPath(target || 'node')
return
}
if (command === 'prepare') {
await ensureCache('node')
await ensureCache('electron')
return
}
if (command === 'export-electron-resource') {
await exportElectronResource()
return
}
if (command === 'status') {
status()
return
}
if (command === 'clean') {
clean()
return
}
throw new Error(`unknown command: ${command}`)
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error))
process.exit(1)
})