diff --git a/.gitignore b/.gitignore index e1cbd51..01e5e63 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ logs/ print_spool/ certs/ output/ +dist/ diff --git a/package.json b/package.json index bb9194e..66d8b0e 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "docs:api": "node scripts/generateApiDocs.js", "api-graph": "node generate-api-graph.js", "review:functions": "node scripts/extractFunctions.js", + "build:sea": "node scripts/buildSea.js", + "sea": "npm run build:sea", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { diff --git a/scripts/buildSea.js b/scripts/buildSea.js new file mode 100644 index 0000000..abc1bfa --- /dev/null +++ b/scripts/buildSea.js @@ -0,0 +1,44 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, '..'); +const configPath = path.join(rootDir, 'sea-config.json'); + +if (!fs.existsSync(configPath)) { + console.error(`Error: Configuration file not found at ${configPath}`); + process.exit(1); +} + +const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); +const outputPath = path.resolve(rootDir, config.output || 'dist/dashback'); +const outputDir = path.dirname(outputPath); + +// Ensure the target output directory exists +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +console.log('Building Node.js Single Executable Application (SEA)...'); +console.log(`Node version: ${process.version}`); +console.log(`Config: ${path.relative(rootDir, configPath)}`); +console.log(`Target: ${path.relative(rootDir, outputPath)}`); + +try { + execFileSync(process.execPath, ['--build-sea', configPath], { + cwd: rootDir, + stdio: 'inherit', + }); + + // Ensure executable permissions on POSIX systems + if (process.platform !== 'win32' && fs.existsSync(outputPath)) { + fs.chmodSync(outputPath, 0o755); + } + + console.log(`\nSuccessfully built SEA executable: ${path.relative(rootDir, outputPath)}`); +} catch (err) { + console.error('\nFailed to build SEA:', err.message); + process.exit(1); +} diff --git a/sea-config.json b/sea-config.json new file mode 100644 index 0000000..c3230ac --- /dev/null +++ b/sea-config.json @@ -0,0 +1,5 @@ +{ + "main": "src/sea-entry.cjs", + "output": "dist/dashback", + "disableExperimentalSEAWarning": true +} diff --git a/src/sea-entry.cjs b/src/sea-entry.cjs new file mode 100644 index 0000000..6789003 --- /dev/null +++ b/src/sea-entry.cjs @@ -0,0 +1,43 @@ +/** + * SEA Entry point for dashback. + * Injected into the Node.js single executable binary via node --build-sea. + * + * When launched as a SEA binary, it resolves the project root, + * configures module resolution, and loads src/index.js. + */ +const { createRequire } = require('node:module'); +const path = require('node:path'); +const fs = require('node:fs'); + +function resolveProjectRoot() { + if (process.env.DASHBACK_ROOT && fs.existsSync(path.join(process.env.DASHBACK_ROOT, 'src/index.js'))) { + return path.resolve(process.env.DASHBACK_ROOT); + } + const cwdEntry = path.join(process.cwd(), 'src/index.js'); + if (fs.existsSync(cwdEntry)) { + return process.cwd(); + } + const parentEntry = path.join(__dirname, '..', 'src/index.js'); + if (fs.existsSync(parentEntry)) { + return path.resolve(__dirname, '..'); + } + const dirEntry = path.join(__dirname, 'src/index.js'); + if (fs.existsSync(dirEntry)) { + return __dirname; + } + return process.cwd(); +} + +const projectRoot = resolveProjectRoot(); +const entryFile = path.join(projectRoot, 'src/index.js'); + +if (!fs.existsSync(entryFile)) { + console.error(`[SEA] Could not find dashback entry point at ${entryFile}.`); + console.error(`[SEA] Please ensure you are running from the dashback repository root or set DASHBACK_ROOT.`); + process.exit(1); +} + +process.argv[1] = entryFile; + +const req = createRequire(entryFile); +req(entryFile);