UNPKG

@simple-software/simplebuild

Version:

A build system for JS/TS that simply builds your project in the fastest way possible, with zero effort from you.

229 lines (189 loc) 6.56 kB
#!/usr/bin/env node const path = require('path'); const { spawn } = require('child_process'); const fs = require('fs'); const os = require('os'); /** * Generate WASI runner script with template substitution * @param {string} wasmPath - Path to the WASM file * @param {string} workingDir - Working directory * @returns {string} Generated script content */ function generateWasiRunner(wasmPath, workingDir) { return `// Node ≥ 20 (host shim for WASM command execution) import { WASI } from 'node:wasi'; import { spawnSync } from 'node:child_process'; import { readFileSync } from 'fs'; import { chdir } from 'node:process'; // Change to the target working directory chdir('${workingDir}'); const wasi = new WASI({ version: 'preview1', args: process.argv.slice(2), env: process.env, preopens: { '.': '.', '/': '/', '${workingDir}': '${workingDir}' } }); // Memory management for WASM<->JS communication let memory; let wasmInstance; let bump = 0x10000; let lastExitCode = 0; const enc = new TextEncoder(); const dec = new TextDecoder(); function readString(ptr, len) { return dec.decode(new Uint8Array(memory.buffer, ptr, len)); } function allocateMemory(size) { const currentMemorySize = memory.buffer.byteLength; if (bump + size > currentMemorySize) { const pagesNeeded = Math.ceil((bump + size - currentMemorySize) / 65536); memory.grow(pagesNeeded); } const ptr = bump; bump += size; bump = (bump + 3) & ~3; // Align to 4-byte boundary return ptr; } function allocString(str) { const bytes = enc.encode(str); const ptr = allocateMemory(bytes.length); new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes); return ptr; } function getExitCode() { return lastExitCode; } function hostExec(cmdPtr, cmdLen, argvPtr, argvLen) { const cmd = readString(cmdPtr, cmdLen); const argvStr = readString(argvPtr, argvLen); const argv = argvStr.split('\0').filter(arg => arg.length > 0); const { status = 255, stdout = '', stderr = '' } = spawnSync(cmd, argv, { encoding: 'utf8', cwd: '${workingDir}' }); lastExitCode = status; const output = stdout + stderr; const outputBytes = enc.encode(output); const structPtr = allocateMemory(4 + outputBytes.length); new DataView(memory.buffer).setUint32(structPtr, status, true); new Uint8Array(memory.buffer, structPtr + 4, outputBytes.length).set(outputBytes); const structLen = 4 + outputBytes.length; return (BigInt(structLen) << 32n) | BigInt(structPtr); } function hostExecWithCwd(cmdPtr, cmdLen, argvPtr, argvLen, cwdPtr, cwdLen) { const cmd = readString(cmdPtr, cmdLen); const argvStr = readString(argvPtr, argvLen); const cwd = readString(cwdPtr, cwdLen); const argv = argvStr.split('\0').filter(arg => arg.length > 0); const { status = 255, stdout = '', stderr = '' } = spawnSync(cmd, argv, { encoding: 'utf8', cwd: cwd }); lastExitCode = status; const output = stdout + stderr; const outputBytes = enc.encode(output); const structPtr = allocateMemory(4 + outputBytes.length); new DataView(memory.buffer).setUint32(structPtr, status, true); new Uint8Array(memory.buffer, structPtr + 4, outputBytes.length).set(outputBytes); const structLen = 4 + outputBytes.length; return (BigInt(structLen) << 32n) | BigInt(structPtr); } const wasm = readFileSync('${wasmPath}'); const wasmModule = new WebAssembly.Module(wasm); wasmInstance = new WebAssembly.Instance(wasmModule, { ...wasi.getImportObject(), host: { allocString, readString, getExitCode, exec: hostExec, execWithCwd: hostExecWithCwd }, }); memory = wasmInstance.exports.memory; wasi.initialize(wasmInstance); `; } /** * Show help message */ function showHelp() { console.log(`Simple Build - A build system for JS/TS that simply builds your project Usage: simplebuild [options] Options: --working-dir <path> Working directory to build (default: current directory) --simplebuild-dir <name> Directory name for build artifacts (default: .simplebuild) --help, -h Show this help message Examples: simplebuild simplebuild --working-dir=/path/to/project simplebuild --working-dir=/path/to/project --simplebuild-dir=build`); } /** * Parse command line arguments * @param {string[]} args - Command line arguments * @returns {{workingDir: string, simpleBuildDir: string}} */ function parseArgs(args) { let workingDir = process.cwd(); let simpleBuildDir = '.simplebuild'; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--working-dir=')) { workingDir = arg.split('=')[1]; } else if (arg === '--working-dir' && i + 1 < args.length) { workingDir = args[i + 1]; i++; } else if (arg.startsWith('--simplebuild-dir=')) { simpleBuildDir = arg.split('=')[1]; } else if (arg === '--simplebuild-dir' && i + 1 < args.length) { simpleBuildDir = args[i + 1]; i++; } else if (arg === '--help' || arg === '-h') { showHelp(); process.exit(0); } } return { workingDir: path.resolve(workingDir), simpleBuildDir }; } /** * Run the WASM binary with proper cleanup * @param {string} wasmPath - Path to the WASM file * @param {string} workingDir - Working directory * @param {string} simpleBuildDir - Simple build directory */ function runWasmBinary(wasmPath, workingDir, simpleBuildDir) { const tempDir = os.tmpdir(); const tempScript = path.join(tempDir, `simplebuild-runner-${Date.now()}.mjs`); const wasiScript = generateWasiRunner(wasmPath, workingDir); fs.writeFileSync(tempScript, wasiScript); const wasmArgs = [`--working-dir=${workingDir}`]; if (simpleBuildDir !== '.simplebuild') { wasmArgs.push(`--simplebuild-dir=${simpleBuildDir}`); } const child = spawn('node', ['--no-warnings', tempScript, ...wasmArgs], { stdio: 'inherit', cwd: workingDir }); child.on('close', (code) => { try { fs.unlinkSync(tempScript); } catch (e) { // Ignore cleanup errors } process.exit(code); }); child.on('error', (error) => { try { fs.unlinkSync(tempScript); } catch (e) { // Ignore cleanup errors } console.error('Error running Simple Build:', error.message); process.exit(1); }); } // Main execution const args = process.argv.slice(2); const { workingDir, simpleBuildDir } = parseArgs(args); const wasmPath = path.join(__dirname, '..', 'wasm', 'Simple-Build-wasm-wasi.wasm'); runWasmBinary(wasmPath, workingDir, simpleBuildDir);