Skip to content

Commit a8849ea

Browse files
committed
feat(node-sea-builder): add hash-based caching for SEA binaries
Add intelligent caching to skip SEA builds when CLI bundle unchanged: - Hash CLI bundle and build script (the inputs) - Store hashes in centralized build/.cache/ directory - Skip build if CLI unchanged (per platform/arch) - Clear logging when using cached builds Benefits: - Saves time downloading Node.js binary and injecting blob - Each platform/arch binary cached separately - Works reliably on CI and local development - SHA256 content hashing for accurate cache validation Hash file naming: {binary-name}.hash (e.g., socket-sea-darwin-arm64.hash)
1 parent 9e952ed commit a8849ea

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

packages/node-sea-builder/scripts/build.mjs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ import { normalizePath } from '@socketsecurity/lib/path'
3737
import { spawn } from '@socketsecurity/lib/spawn'
3838
import colors from 'yoctocolors-cjs'
3939

40+
import {
41+
generateHashComment,
42+
shouldExtract,
43+
} from '@socketsecurity/build-infra/lib/extraction-cache'
44+
4045
import constants from './constants.mjs'
4146

4247
// Inline NODE_SEA_FUSE constant (not exported from constants.mjs).
@@ -480,6 +485,38 @@ async function buildTarget(target, options) {
480485
// Ensure output directory exists.
481486
await fs.mkdir(outputDir, { recursive: true })
482487

488+
// Generate output path.
489+
const outputPath = normalizePath(path.join(outputDir, target.outputName))
490+
491+
// Check if we can use cached SEA build.
492+
// Hash the CLI bundle and build script since those are the inputs.
493+
const sourcePaths = [cliPath, url.fileURLToPath(import.meta.url)]
494+
495+
// Store hash in centralized build/.cache/ directory.
496+
const cacheDir = normalizePath(path.join(__dirname, '../build/.cache'))
497+
await fs.mkdir(cacheDir, { recursive: true })
498+
const hashFilePath = normalizePath(path.join(cacheDir, `${target.outputName}.hash`))
499+
500+
const needsExtraction = await shouldExtract({
501+
sourcePaths,
502+
outputPath: hashFilePath,
503+
validateOutput: () => {
504+
// Verify both SEA binary and hash file exist.
505+
return existsSync(outputPath) && existsSync(hashFilePath)
506+
},
507+
})
508+
509+
if (!needsExtraction) {
510+
// Cache hit! SEA binary is up to date.
511+
logger.log('')
512+
logger.log(`${colors.green('✓')} Using cached SEA binary`)
513+
logger.log('CLI bundle unchanged since last build.')
514+
logger.log('')
515+
logger.log(`Binary: ${outputPath}`)
516+
logger.log('')
517+
return
518+
}
519+
483520
// Create a modified copy of the CLI with SEA compatibility fixes.
484521
const modifiedCliPath = normalizePath(path.join(outputDir, 'cli-modified.js'))
485522
let cliContent = await fs.readFile(cliPath, 'utf8')
@@ -552,10 +589,6 @@ if (typeof require !== 'undefined' && (!require.resolve || !require.resolve.path
552589
target.arch,
553590
)
554591

555-
// Generate output path.
556-
const outputPath = normalizePath(path.join(outputDir, target.outputName))
557-
await fs.mkdir(outputDir, { recursive: true })
558-
559592
// Generate SEA configuration.
560593
const configPath = await generateSeaConfig(entryPoint, outputPath)
561594

@@ -573,6 +606,10 @@ if (typeof require !== 'undefined' && (!require.resolve || !require.resolve.path
573606

574607
logger.log(`${colors.green('✓')} Built ${target.outputName}`)
575608

609+
// Write source hash to cache file for future builds.
610+
const sourceHashComment = await generateHashComment(sourcePaths)
611+
await fs.writeFile(hashFilePath, sourceHashComment, 'utf-8')
612+
576613
// Clean up temporary files using trash.
577614
const filesToClean = [
578615
blobPath,

0 commit comments

Comments
 (0)