Skip to content

Commit f495dd3

Browse files
committed
refactor(build): improve build infrastructure and E2E testing
Enhance build-infra APIs with better signatures and return types. Unify E2E test scripts with single configuration approach. Build Infrastructure: - Improve exec() to support both exec(cmd, opts) and exec(cmd, args, opts) - Return structured data from checkDiskSpace() and checkCompiler() - Use whichBinSync() for reliable binary detection E2E Testing: - Add unified scripts/e2e.mjs for all binary types (js/sea/smol/all) - Simplify to single .env.e2e with dynamic TEST_*_BINARY flags - Remove redundant .env.e2e.{sea,smol,all} files - Improve build feedback with timing and manual build instructions Builder Scripts: - Update node-smol-builder to use improved build-infra APIs - Update node-sea-builder to use improved build-infra APIs - Make Socket bootstrap optional with clear skip message
1 parent 49bc1a8 commit f495dd3

File tree

12 files changed

+357
-133
lines changed

12 files changed

+357
-133
lines changed

packages/build-infra/lib/build-exec.mjs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,31 @@
88
import { spawn } from '@socketsecurity/lib/spawn'
99

1010
/**
11-
* Execute command and inherit stdio.
11+
* Execute command with arguments.
1212
*
1313
* @param {string} command - Command to execute
14-
* @param {object} options - Spawn options
15-
* @returns {Promise<void>}
14+
* @param {string[]|object} argsOrOptions - Arguments array or options object
15+
* @param {object} [options] - Spawn options (if args provided)
16+
* @returns {Promise<object>}
1617
*/
17-
export async function exec(command, options = {}) {
18-
const result = await spawn(command, [], {
19-
shell: true,
20-
stdio: options.stdio || 'pipe',
18+
export async function exec(command, argsOrOptions = [], options = {}) {
19+
// Handle both signatures: exec(cmd, opts) and exec(cmd, args, opts).
20+
const args = Array.isArray(argsOrOptions) ? argsOrOptions : []
21+
const opts = Array.isArray(argsOrOptions) ? options : argsOrOptions
22+
23+
const result = await spawn(command, args, {
24+
stdio: opts.stdio || 'inherit',
2125
stdioString: true,
2226
stripAnsi: false,
23-
...options,
27+
...opts,
2428
})
2529

2630
// Treat undefined or null status as success (0).
2731
const exitCode = result.status ?? 0
2832

2933
if (exitCode !== 0) {
30-
const error = new Error(`Command failed with exit code ${exitCode}: ${command}`)
34+
const cmdString = args.length ? `${command} ${args.join(' ')}` : command
35+
const error = new Error(`Command failed with exit code ${exitCode}: ${cmdString}`)
3136
error.stdout = result.stdout
3237
error.stderr = result.stderr
3338
error.code = exitCode
@@ -38,9 +43,10 @@ export async function exec(command, options = {}) {
3843
}
3944

4045
/**
41-
* Execute command and capture output.
46+
* Execute shell command and capture output.
47+
* Always uses shell for proper command execution with redirections and pipes.
4248
*
43-
* @param {string} command - Command to execute
49+
* @param {string} command - Shell command to execute
4450
* @param {object} options - Spawn options
4551
* @returns {Promise<{stdout: string, stderr: string, code: number}>}
4652
*/

packages/build-infra/lib/build-helpers.mjs

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,72 +8,78 @@
88
import { promises as fs } from 'node:fs'
99
import path from 'node:path'
1010

11+
import { whichBinSync } from '@socketsecurity/lib/bin'
12+
1113
import { exec, execCapture } from './build-exec.mjs'
1214
import { printError, printStep, printWarning } from './build-output.mjs'
1315

1416
/**
1517
* Check available disk space.
1618
*
1719
* @param {string} dir - Directory to check
18-
* @param {number} requiredBytes - Required bytes
19-
* @returns {Promise<boolean>}
20+
* @param {number} [requiredGB=5] - Required GB (defaults to 5GB)
21+
* @returns {Promise<{availableGB: number|null, sufficient: boolean}>}
2022
*/
21-
export async function checkDiskSpace(dir, requiredBytes) {
23+
export async function checkDiskSpace(dir, requiredGB = 5) {
2224
printStep('Checking disk space')
2325

2426
try {
2527
const { stdout } = await execCapture(`df -k "${dir}"`)
2628
const lines = stdout.trim().split('\n')
2729
if (lines.length < 2) {
2830
printWarning('Could not determine disk space')
29-
return true
31+
return { availableGB: null, sufficient: true }
3032
}
3133

3234
const stats = lines[1].split(/\s+/)
3335
const availableKB = Number.parseInt(stats[3], 10)
3436
const availableBytes = availableKB * 1024
37+
const availableGBValue = Number((availableBytes / (1024 * 1024 * 1024)).toFixed(2))
38+
const sufficient = availableGBValue >= requiredGB
3539

36-
if (availableBytes < requiredBytes) {
37-
const requiredGB = (requiredBytes / (1024 * 1024 * 1024)).toFixed(2)
38-
const availableGB = (availableBytes / (1024 * 1024 * 1024)).toFixed(2)
39-
printError(
40-
`Insufficient disk space. Required: ${requiredGB} GB, Available: ${availableGB} GB`
41-
)
42-
return false
40+
return {
41+
availableGB: availableGBValue,
42+
sufficient,
4343
}
44-
45-
return true
4644
} catch {
4745
printWarning('Could not check disk space')
48-
return true
46+
return { availableGB: null, sufficient: true }
4947
}
5048
}
5149

5250
/**
5351
* Check if compiler is available.
52+
* Tries multiple compilers if none specified.
5453
*
55-
* @param {string} compiler - Compiler command (e.g., 'clang++', 'gcc')
56-
* @returns {Promise<boolean>}
54+
* @param {string|string[]} [compilers] - Compiler command(s) to check (e.g., 'clang++', ['clang++', 'g++', 'c++'])
55+
* @returns {Promise<{available: boolean, compiler: string|undefined}>}
5756
*/
58-
export async function checkCompiler(compiler) {
59-
printStep(`Checking for ${compiler}`)
60-
61-
try {
62-
await execCapture(`which ${compiler}`)
63-
return true
64-
} catch {
65-
printError(`${compiler} not found. Please install ${compiler}.`)
66-
return false
57+
export async function checkCompiler(compilers) {
58+
const compilerList = Array.isArray(compilers)
59+
? compilers
60+
: compilers
61+
? [compilers]
62+
: ['clang++', 'g++', 'c++']
63+
64+
for (const compiler of compilerList) {
65+
printStep(`Checking for ${compiler}`)
66+
67+
const binPath = whichBinSync(compiler, { nothrow: true })
68+
if (binPath) {
69+
return { available: true, compiler }
70+
}
6771
}
72+
73+
return { available: false, compiler: undefined }
6874
}
6975

7076
/**
7177
* Check Python version.
7278
*
73-
* @param {string} minVersion - Minimum required version (e.g., '3.8')
74-
* @returns {Promise<boolean>}
79+
* @param {string} [minVersion='3.6'] - Minimum required version (e.g., '3.8')
80+
* @returns {Promise<{available: boolean, sufficient: boolean, version: string|null}>}
7581
*/
76-
export async function checkPythonVersion(minVersion) {
82+
export async function checkPythonVersion(minVersion = '3.6') {
7783
printStep('Checking Python version')
7884

7985
try {
@@ -84,17 +90,19 @@ export async function checkPythonVersion(minVersion) {
8490
const [major, minor] = version.split('.').map(Number)
8591
const [minMajor, minMinor] = minVersion.split('.').map(Number)
8692

87-
if (major < minMajor || (major === minMajor && minor < minMinor)) {
88-
printError(
89-
`Python ${minVersion}+ required, but found ${version}`
90-
)
91-
return false
92-
}
93+
const sufficient = major > minMajor || (major === minMajor && minor >= minMinor)
9394

94-
return true
95+
return {
96+
available: true,
97+
sufficient,
98+
version,
99+
}
95100
} catch {
96-
printError('Python 3 not found. Please install Python 3.')
97-
return false
101+
return {
102+
available: false,
103+
sufficient: false,
104+
version: null,
105+
}
98106
}
99107
}
100108

@@ -295,18 +303,11 @@ export async function cleanCheckpoint(buildDir) {
295303
export async function checkNetworkConnectivity() {
296304
try {
297305
// Try to reach GitHub (where we clone from).
298-
const result = await execCapture('curl', [
299-
'-s',
300-
'-o',
301-
'/dev/null',
302-
'-w',
303-
'%{http_code}',
304-
'--connect-timeout',
305-
'5',
306-
'https://github.com',
307-
])
308-
309-
const statusCode = result.stdout
306+
const result = await execCapture(
307+
'curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://github.com'
308+
)
309+
310+
const statusCode = result.stdout.trim()
310311
return {
311312
connected:
312313
statusCode === '200' || statusCode === '301' || statusCode === '302',
@@ -325,12 +326,9 @@ export async function checkNetworkConnectivity() {
325326
*/
326327
export async function verifyGitTag(version) {
327328
try {
328-
const result = await execCapture('git', [
329-
'ls-remote',
330-
'--tags',
331-
'https://github.com/nodejs/node.git',
332-
version,
333-
])
329+
const result = await execCapture(
330+
`git ls-remote --tags https://github.com/nodejs/node.git ${version}`
331+
)
334332

335333
return {
336334
exists: result.stdout.includes(version),

packages/cli/.env.e2e

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# E2E Test Environment Configuration
2-
# Extends .env.test with E2E-specific settings
2+
# Used by all e2e tests (js, sea, smol, all)
3+
# The e2e.mjs script sets TEST_*_BINARY flags dynamically
34

45
NODE_COMPILE_CACHE="./.cache"
56
NODE_OPTIONS="--max-old-space-size=2048 --unhandled-rejections=warn"
@@ -10,3 +11,5 @@ SOCKET_CLI_JS_PATH="./dist/cli.js"
1011

1112
# Enable E2E tests (requires Socket API token)
1213
RUN_E2E_TESTS=1
14+
15+
# Note: TEST_SEA_BINARY and TEST_SMOL_BINARY are set by scripts/e2e.mjs

packages/cli/.env.e2e.all

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/cli/.env.e2e.sea

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/cli/.env.e2e.smol

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/cli/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@
5656
"bs": "dotenvx -q run -f .env.local -- pnpm run build; pnpm exec socket --",
5757
"s": "dotenvx -q run -f .env.local -- pnpm exec socket --",
5858
"e2e-tests": "dotenvx -q run -f .env.test -- vitest run --config vitest.e2e.config.mts",
59-
"e2e:js": "dotenvx -q run -f .env.e2e -- vitest run test/e2e/binary-test-suite.e2e.test.mts --config vitest.e2e.config.mts",
60-
"e2e:smol": "dotenvx -q run -f .env.e2e.smol -- vitest run test/e2e/binary-test-suite.e2e.test.mts --config vitest.e2e.config.mts",
61-
"e2e:sea": "dotenvx -q run -f .env.e2e.sea -- vitest run test/e2e/binary-test-suite.e2e.test.mts --config vitest.e2e.config.mts",
62-
"e2e:all": "dotenvx -q run -f .env.e2e.all -- vitest run test/e2e/binary-test-suite.e2e.test.mts --config vitest.e2e.config.mts",
59+
"e2e:js": "node scripts/e2e.mjs --js",
60+
"e2e:smol": "node scripts/e2e.mjs --smol",
61+
"e2e:sea": "node scripts/e2e.mjs --sea",
62+
"e2e:all": "node scripts/e2e.mjs --all",
6363
"test": "run-s check test:*",
6464
"test:prepare": "dotenvx -q run -f .env.test -- pnpm build && del-cli 'test/**/node_modules'",
6565
"test:unit": "dotenvx -q run -f .env.test -- vitest run",

0 commit comments

Comments
 (0)