next.js/scripts/native-cache.js
native-cache.js162 lines4.4 KB
#!/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)
})
Quest for Codev2.0.0
/
SIGN IN