#!/usr/bin/env node
//
// Cache the compiled next-swc .node binary in the turbo remote cache.
//
// Uses the turbo-computed rust fingerprint (target/.rust-fingerprint) as
// the cache key, combined with the target triple. Run `turbo run
// rust-fingerprint` first to compute it.
//
// Usage:
// node scripts/native-cache.js --restore --target x86_64-unknown-linux-gnu
// node scripts/native-cache.js --save --target x86_64-unknown-linux-gnu
const { execSync } = require('child_process')
const { createHash } = require('crypto')
const path = require('path')
const fs = require('fs')
const os = require('os')
const { parseArgs } = require('node:util')
const { values: flags } = parseArgs({
args: process.argv.slice(2),
options: {
restore: { type: 'boolean', default: false },
save: { type: 'boolean', default: false },
target: { type: 'string', default: '' },
},
strict: false,
})
const REPO_ROOT = path.resolve(__dirname, '..')
const NATIVE_DIR = path.join(REPO_ROOT, 'packages/next-swc/native')
const FINGERPRINT = path.join(REPO_ROOT, 'target/.rust-fingerprint')
function computeCacheKey() {
if (!fs.existsSync(FINGERPRINT)) {
console.error(
'target/.rust-fingerprint not found — run `turbo run rust-fingerprint` first'
)
process.exit(1)
}
const turboHash = fs.readFileSync(FINGERPRINT, 'utf-8').trim()
// Combine turbo's input hash with the target triple for a unique key.
// Must be hex-only (turbo cache API requirement).
const hash = createHash('sha256')
hash.update(`native-cache-v1\0`)
hash.update(`turbo=${turboHash}\0`)
hash.update(`target=${flags.target}\0`)
return hash.digest('hex')
}
function tmpFile(name) {
return path.join(process.env.RUNNER_TEMP || os.tmpdir(), name)
}
function sh(cmd) {
execSync(cmd, { stdio: 'inherit', shell: true })
}
async function restore() {
const cache = await import('./turbo-cache.mjs')
const key = computeCacheKey()
console.log(`Native cache key: ${key.slice(0, 16)}...`)
console.log(` target: ${flags.target}`)
if (!process.env.TURBO_TOKEN) {
console.log('No TURBO_TOKEN — skipping cache restore')
return false
}
const hit = await cache.exists(key)
if (!hit) {
console.log('Native cache MISS')
return false
}
console.log('Native cache HIT — downloading...')
const tarFile = tmpFile('native-cache.tar.zst')
const result = await cache.getToFile(key, tarFile)
if (!result.ok) {
console.log('Download failed')
return false
}
if (result.stats) {
console.log(`Downloaded (${cache.formatStats(result.stats)})`)
} else {
const size = fs.statSync(tarFile).size
console.log(`Downloaded ${(size / 1024 / 1024).toFixed(0)} MB`)
}
fs.mkdirSync(NATIVE_DIR, { recursive: true })
sh(`zstd -d -c "${tarFile}" | tar xf - -C "${NATIVE_DIR}"`)
fs.unlinkSync(tarFile)
// Verify
const nodes = fs.readdirSync(NATIVE_DIR).filter((f) => f.endsWith('.node'))
if (nodes.length > 0) {
console.log(`Restored: ${nodes.join(', ')}`)
return true
}
console.log('WARNING: tar extracted but no .node files found')
return false
}
async function save() {
const cache = await import('./turbo-cache.mjs')
const key = computeCacheKey()
console.log(`Native cache key: ${key.slice(0, 16)}...`)
if (!process.env.TURBO_TOKEN) {
console.log('No TURBO_TOKEN — skipping cache save')
return
}
const nodes = fs.existsSync(NATIVE_DIR)
? fs.readdirSync(NATIVE_DIR).filter((f) => f.endsWith('.node'))
: []
if (nodes.length === 0) {
console.log('No .node files to cache')
return
}
if (await cache.exists(key)) {
console.log('Already cached — skipping')
return
}
const tarFile = tmpFile('native-cache.tar.zst')
const fileList = nodes.join(' ')
sh(`tar cf - -C "${NATIVE_DIR}" ${fileList} | zstd -3 -T0 -o "${tarFile}"`)
const size = fs.statSync(tarFile).size
console.log(
`Compressed: ${(size / 1024 / 1024).toFixed(0)} MB — uploading...`
)
try {
await cache.put(key, tarFile)
console.log('Native cache saved')
} catch (e) {
console.log(`WARNING: Failed to save: ${e.message}`)
}
fs.unlinkSync(tarFile)
}
async function main() {
if (flags.restore) {
const ok = await restore()
process.exit(ok ? 0 : 1)
} else if (flags.save) {
await save()
} else {
console.error('Usage: --restore or --save (with --target)')
process.exit(1)
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})