Skip to content

Commit fb2593d

Browse files
committed
feat(node-smol-builder): implement patch analysis with build-infra helpers
Use centralized patch analysis and conflict detection from build-infra to provide better visibility into patch characteristics and conflicts. Changes: - Import analyzePatchContent and checkPatchConflicts from build-infra - Analyze each patch to identify V8, SEA, and Brotli modifications - Display patch characteristics during validation (⚠️ for V8, ✓ for SEA) - Implement conflict detection with detailed error reporting - Show "No conflicts detected" confirmation when patches are compatible Benefits: - Better understanding of what each patch does - Early detection of patch conflicts before application - Detailed error messages for conflict resolution - Consistent patch analysis across build packages Output improvements: - "⚠️ Modifies V8 includes" warning for patches touching V8 - "✓ Modifies SEA detection" confirmation for SEA patches - Structured conflict reporting with severity levels - Resolution guidance when conflicts are detected
1 parent 3f2b1ac commit fb2593d

File tree

1 file changed

+89
-44
lines changed

1 file changed

+89
-44
lines changed

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

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ import {
8181
} from '@socketsecurity/build-infra/lib/build-helpers'
8282
import { printError, printHeader, printWarning } from '@socketsecurity/build-infra/lib/build-output'
8383
import {
84+
analyzePatchContent,
85+
checkPatchConflicts,
8486
testPatchApplication,
8587
validatePatch,
8688
} from '@socketsecurity/build-infra/lib/patch-validator'
@@ -161,7 +163,7 @@ async function copyBuildAdditions() {
161163
logger.log(
162164
`✅ Copied ${ADDITIONS_DIR.replace(`${ROOT_DIR}/`, '')}/ → ${NODE_DIR}/`,
163165
)
164-
logger.log()
166+
logger.log('')
165167
}
166168

167169
/**
@@ -194,7 +196,7 @@ async function copySocketSecurityBootstrap() {
194196
logger.log(
195197
` ${(stats.size / 1024).toFixed(1)}KB (will be brotli encoded with lib/ files)`,
196198
)
197-
logger.log()
199+
logger.log('')
198200
}
199201

200202
const CPU_COUNT = cpus().length
@@ -237,7 +239,7 @@ async function resetNodeSource() {
237239
await exec('git', ['reset', '--hard', NODE_VERSION], { cwd: NODE_DIR })
238240
await exec('git', ['clean', '-fdx'], { cwd: NODE_DIR })
239241
logger.log('✅ Node.js source reset to clean state')
240-
logger.log()
242+
logger.log('')
241243
}
242244

243245
/**
@@ -574,7 +576,7 @@ async function _compressNodeJsWithBrotli() {
574576
}
575577

576578
logger.log('Minifying and compressing JavaScript files...')
577-
logger.log()
579+
logger.log('')
578580

579581
const libDir = join(NODE_DIR, 'lib')
580582

@@ -653,7 +655,7 @@ async function _compressNodeJsWithBrotli() {
653655
}
654656
}
655657

656-
logger.log()
658+
logger.log('')
657659
logger.log(`✅ Processed ${filesCompressed} files`)
658660
logger.log(
659661
` Original: ${(totalOriginalSize / 1024 / 1024).toFixed(2)} MB`,
@@ -667,7 +669,7 @@ async function _compressNodeJsWithBrotli() {
667669
logger.log(
668670
` Final savings: ${((totalOriginalSize - totalCompressedSize) / 1024 / 1024).toFixed(2)} MB`,
669671
)
670-
logger.log()
672+
logger.log('')
671673

672674
// Create decompression bootstrap hook.
673675
await createBrotliBootstrapHook()
@@ -787,7 +789,7 @@ async function createBrotliBootstrapHook() {
787789
}
788790

789791
logger.log('✅ Brotli bootstrap hook created')
790-
logger.log()
792+
logger.log('')
791793
}
792794

793795
/**
@@ -837,7 +839,7 @@ async function main() {
837839
throw new Error('Invalid Node.js version')
838840
}
839841
logger.log(`✅ ${NODE_VERSION} exists in Node.js repository`)
840-
logger.log()
842+
logger.log('')
841843

842844
// Clone or reset Node.js repository.
843845
if (!existsSync(NODE_DIR) || CLEAN_BUILD) {
@@ -848,24 +850,24 @@ async function main() {
848850
await rm(NODE_DIR, { recursive: true, force: true })
849851
await cleanCheckpoint(BUILD_DIR)
850852
logger.log('✅ Cleaned build directory')
851-
logger.log()
853+
logger.log('')
852854
}
853855

854856
printHeader('Cloning Node.js Source')
855857
logger.log(`Version: ${NODE_VERSION}`)
856858
logger.log('Repository: https://github.com/nodejs/node.git')
857-
logger.log()
859+
logger.log('')
858860
logger.log('⏱️ This will download ~200-300 MB (shallow clone)...')
859861
logger.log('Retry: Up to 3 attempts if clone fails')
860-
logger.log()
862+
logger.log('')
861863

862864
// Git clone with retry (network can fail during long downloads).
863865
let cloneSuccess = false
864866
for (let attempt = 1; attempt <= 3; attempt++) {
865867
try {
866868
if (attempt > 1) {
867869
logger.log(`Retry attempt ${attempt}/3...`)
868-
logger.log()
870+
logger.log('')
869871
}
870872

871873
await exec(
@@ -912,15 +914,15 @@ async function main() {
912914
// Wait before retry.
913915
const waitTime = 2000 * attempt
914916
logger.log(`⏱️ Waiting ${waitTime}ms before retry...`)
915-
logger.log()
917+
logger.log('')
916918
await new Promise(resolve => setTimeout(resolve, waitTime))
917919
}
918920
}
919921

920922
if (cloneSuccess) {
921923
logger.log('✅ Node.js source cloned successfully')
922924
await createCheckpoint(BUILD_DIR, 'cloned')
923-
logger.log()
925+
logger.log('')
924926
}
925927
} else {
926928
printHeader('Using Existing Node.js Source')
@@ -940,12 +942,12 @@ async function main() {
940942

941943
// Wait 5 seconds before proceeding.
942944
await new Promise(resolve => setTimeout(resolve, 5000))
943-
logger.log()
945+
logger.log('')
944946
} else if (isDirty && AUTO_YES) {
945947
logger.log(
946948
'⚠️ Node.js source has uncommitted changes (auto-resetting with --yes)',
947949
)
948-
logger.log()
950+
logger.log('')
949951
}
950952

951953
await resetNodeSource()
@@ -965,7 +967,7 @@ async function main() {
965967
printHeader('Validating Socket Patches')
966968
logger.log(`Found ${socketPatches.length} patch(es) for ${NODE_VERSION}`)
967969
logger.log('Checking integrity, compatibility, and conflicts...')
968-
logger.log()
970+
logger.log('')
969971

970972
const patchData = []
971973
let allValid = true
@@ -981,19 +983,28 @@ async function main() {
981983
continue
982984
}
983985

986+
const content = await readFile(patchPath, 'utf8')
984987
const metadata = validation.metadata
988+
const analysis = analyzePatchContent(content)
985989

986990
patchData.push({
987991
name: patchFile,
988992
path: patchPath,
989993
metadata,
994+
analysis,
990995
})
991996

992997
if (metadata?.description) {
993998
logger.log(` 📝 ${metadata.description}`)
994999
}
1000+
if (analysis.modifiesV8Includes) {
1001+
logger.log(' ⚠️ Modifies V8 includes')
1002+
}
1003+
if (analysis.modifiesSEA) {
1004+
logger.log(' ✓ Modifies SEA detection')
1005+
}
9951006
logger.log(' ✅ Valid')
996-
logger.log()
1007+
logger.log('')
9971008
}
9981009

9991010
if (!allValid) {
@@ -1011,16 +1022,50 @@ async function main() {
10111022
' 3. Check build/patches/README.md for patch creation guide',
10121023
)
10131024
}
1014-
// Note: Advanced patch conflict detection is not yet implemented.
1015-
// TODO: Implement analyzePatchContent and checkPatchConflicts in build-infra package.
1016-
logger.log('✅ All Socket patches validated successfully')
1017-
logger.log()
1025+
// Check for conflicts between patches.
1026+
const conflicts = checkPatchConflicts(patchData, NODE_VERSION)
1027+
if (conflicts.length > 0) {
1028+
logger.warn('⚠️ Patch Conflicts Detected:')
1029+
logger.warn()
1030+
for (const conflict of conflicts) {
1031+
if (conflict.severity === 'error') {
1032+
logger.error(` ❌ ERROR: ${conflict.message}`)
1033+
allValid = false
1034+
} else {
1035+
logger.warn(` ⚠️ WARNING: ${conflict.message}`)
1036+
}
1037+
}
1038+
logger.warn()
1039+
1040+
if (!allValid) {
1041+
throw new Error(
1042+
'Critical patch conflicts detected.\n\n' +
1043+
`Socket patches have conflicts and cannot be applied to Node.js ${NODE_VERSION}.\n\n` +
1044+
'Conflicts found:\n' +
1045+
conflicts
1046+
.filter(c => c.severity === 'error')
1047+
.map(c => ` - ${c.message}`)
1048+
.join('\n') +
1049+
'\n\n' +
1050+
'To fix:\n' +
1051+
' 1. Remove conflicting patches\n' +
1052+
` 2. Use version-specific patches for ${NODE_VERSION}\n` +
1053+
' 3. Regenerate patches:\n' +
1054+
` node scripts/regenerate-node-patches.mjs --version=${NODE_VERSION}\n` +
1055+
' 4. See build/patches/socket/README.md for guidance',
1056+
)
1057+
}
1058+
} else {
1059+
logger.log('✅ All Socket patches validated successfully')
1060+
logger.log('✅ No conflicts detected')
1061+
logger.log('')
1062+
}
10181063

10191064
// Test Socket patches (dry-run) before applying.
10201065
if (allValid) {
10211066
printHeader('Testing Socket Patch Application')
10221067
logger.log('Running dry-run to ensure patches will apply cleanly...')
1023-
logger.log()
1068+
logger.log('')
10241069

10251070
for (const { name, path: patchPath } of patchData) {
10261071
logger.log(`Testing ${name}...`)
@@ -1035,7 +1080,7 @@ async function main() {
10351080
logger.log(' ✅ Will apply cleanly')
10361081
}
10371082
}
1038-
logger.log()
1083+
logger.log('')
10391084

10401085
if (!allValid) {
10411086
throw new Error(
@@ -1089,7 +1134,7 @@ async function main() {
10891134
}
10901135
}
10911136
logger.log('✅ All Socket patches applied successfully')
1092-
logger.log()
1137+
logger.log('')
10931138
}
10941139
} else {
10951140
throw new Error(
@@ -1125,17 +1170,17 @@ async function main() {
11251170
logger.log(
11261171
' 💾 OPTIMIZATIONS: no-snapshot, no-code-cache, no-object-print, no-SEA, V8 Lite',
11271172
)
1128-
logger.log()
1173+
logger.log('')
11291174
logger.log(
11301175
' ⚠️ V8 LITE MODE: JavaScript runs 5-10x slower (CPU-bound code)',
11311176
)
11321177
logger.log(' ✅ WASM: Full speed (uses Liftoff compiler, unaffected)')
11331178
logger.log(' ✅ I/O: No impact (network, file operations)')
1134-
logger.log()
1179+
logger.log('')
11351180
logger.log(
11361181
'Expected binary size: ~60MB (before stripping), ~23-27MB (after)',
11371182
)
1138-
logger.log()
1183+
logger.log('')
11391184

11401185
const configureFlags = [
11411186
'--with-intl=none', // -6-8 MB: No ICU/Intl support (use polyfill instead)
@@ -1164,7 +1209,7 @@ async function main() {
11641209

11651210
await exec('./configure', configureFlags, { cwd: NODE_DIR })
11661211
logger.log('✅ Configuration complete')
1167-
logger.log()
1212+
logger.log('')
11681213

11691214
// Build Node.js.
11701215
printHeader('Building Node.js')
@@ -1174,16 +1219,16 @@ async function main() {
11741219
`⏱️ Estimated time: ${timeEstimate.estimatedMinutes} minutes (${timeEstimate.minMinutes}-${timeEstimate.maxMinutes} min range)`,
11751220
)
11761221
logger.log(`🚀 Using ${CPU_COUNT} CPU cores for parallel compilation`)
1177-
logger.log()
1222+
logger.log('')
11781223
logger.log('You can:')
11791224
logger.log(' • Grab coffee ☕')
11801225
logger.log(' • Work on other tasks')
11811226
logger.log(' • Watch progress in this terminal')
1182-
logger.log()
1227+
logger.log('')
11831228
logger.log(`Build log: ${getBuildLogPath(BUILD_DIR)}`)
1184-
logger.log()
1229+
logger.log('')
11851230
logger.log('Starting build...')
1186-
logger.log()
1231+
logger.log('')
11871232

11881233
const buildStart = Date.now()
11891234

@@ -1218,17 +1263,17 @@ async function main() {
12181263
const buildDuration = Date.now() - buildStart
12191264
const buildTime = formatDuration(buildDuration)
12201265

1221-
logger.log()
1266+
logger.log('')
12221267
logger.log(`✅ Build completed in ${buildTime}`)
12231268
await createCheckpoint(BUILD_DIR, 'built')
1224-
logger.log()
1269+
logger.log('')
12251270

12261271
// Test the binary.
12271272
printHeader('Testing Binary')
12281273
const nodeBinary = join(NODE_DIR, 'out', 'Release', 'node')
12291274

12301275
logger.log('Running basic functionality tests...')
1231-
logger.log()
1276+
logger.log('')
12321277

12331278
await exec(nodeBinary, ['--version'], {
12341279
env: { ...process.env, PKG_EXECPATH: 'PKG_INVOKE_NODEJS' },
@@ -1242,9 +1287,9 @@ async function main() {
12421287
},
12431288
)
12441289

1245-
logger.log()
1290+
logger.log('')
12461291
logger.log('✅ Binary is functional')
1247-
logger.log()
1292+
logger.log('')
12481293

12491294
// Copy unmodified binary to build/out/Release.
12501295
printHeader('Copying to Build Output (Release)')
@@ -1267,7 +1312,7 @@ async function main() {
12671312
const sizeBeforeStrip = await getFileSize(nodeBinary)
12681313
logger.log(`Size before stripping: ${sizeBeforeStrip}`)
12691314
logger.log('Removing debug symbols and unnecessary sections...')
1270-
logger.log()
1315+
logger.log('')
12711316
await exec('strip', ['--strip-all', nodeBinary])
12721317
const sizeAfterStrip = await getFileSize(nodeBinary)
12731318
logger.log(`Size after stripping: ${sizeAfterStrip}`)
@@ -1302,7 +1347,7 @@ async function main() {
13021347
}
13031348
}
13041349

1305-
logger.log()
1350+
logger.log('')
13061351

13071352
// Smoke test binary after stripping (ensure strip didn't corrupt it).
13081353
logger.log('Testing binary after stripping...')
@@ -1325,7 +1370,7 @@ async function main() {
13251370
}
13261371

13271372
logger.log('✅ Binary functional after stripping')
1328-
logger.log()
1373+
logger.log('')
13291374

13301375
// Copy stripped binary to build/out/Stripped.
13311376
printHeader('Copying to Build Output (Stripped)')
@@ -1443,12 +1488,12 @@ async function main() {
14431488
}
14441489

14451490
logger.log('✅ Binary installed to pkg cache successfully')
1446-
logger.log()
1491+
logger.log('')
14471492

14481493
// Verify the cached binary works.
14491494
printHeader('Verifying Cached Binary')
14501495
logger.log('Testing that pkg can use the installed binary...')
1451-
logger.log()
1496+
logger.log('')
14521497

14531498
const cacheTest = await smokeTestBinary(targetPath, {
14541499
...process.env,
@@ -1471,7 +1516,7 @@ async function main() {
14711516

14721517
logger.log('✅ Cached binary passed smoke test')
14731518
logger.log('✅ pkg can use this binary')
1474-
logger.log()
1519+
logger.log('')
14751520

14761521
// Copy final binary to build/out/Distribution.
14771522
printHeader('Copying Final Binary to build/out/Distribution')

0 commit comments

Comments
 (0)