seabox 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.json +6 -0
- package/LICENSE.MD +21 -0
- package/README.md +209 -0
- package/bin/seabox-rebuild.js +395 -0
- package/bin/seabox.js +81 -0
- package/lib/bindings.js +31 -0
- package/lib/blob.js +109 -0
- package/lib/bootstrap.js +655 -0
- package/lib/build.js +283 -0
- package/lib/config.js +117 -0
- package/lib/crypto-assets.js +160 -0
- package/lib/fetch-node.js +177 -0
- package/lib/index.js +27 -0
- package/lib/inject.js +81 -0
- package/lib/manifest.js +98 -0
- package/lib/obfuscate.js +73 -0
- package/lib/scanner.js +153 -0
- package/lib/unsign.js +184 -0
- package/package.json +47 -0
package/.mocharc.json
ADDED
package/LICENSE.MD
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Meirion Hughes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# seabox
|
|
2
|
+
|
|
3
|
+
A reusable tool for building Node.js Single Executable Applications (SEA) with native-module support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Bundle Node.js applications into standalone executables
|
|
8
|
+
- Automatic native module (.node, .dll, .so, .dylib) extraction and loading
|
|
9
|
+
- Asset encryption with obfuscated keys embedded in V8 snapshots
|
|
10
|
+
- Multi-platform targeting (Windows, Linux, macOS)
|
|
11
|
+
- V8 snapshot support for faster startup
|
|
12
|
+
- Integrity checking for extracted binaries
|
|
13
|
+
- Automatic code signature removal before injection
|
|
14
|
+
- Simple configuration via package.json
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install --save-dev seabox
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
Add a `sea` configuration to your `package.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"sea": {
|
|
29
|
+
"entry": "./out/server.js",
|
|
30
|
+
"assets": [
|
|
31
|
+
"./out/client/**/*",
|
|
32
|
+
"./out/lib/**/*",
|
|
33
|
+
"./out/native/**/*",
|
|
34
|
+
"!**/*.md",
|
|
35
|
+
"!**/test/**"
|
|
36
|
+
],
|
|
37
|
+
"binaries": [
|
|
38
|
+
"*.node",
|
|
39
|
+
"*.dll"
|
|
40
|
+
],
|
|
41
|
+
"targets": [
|
|
42
|
+
"node24.11.0-win32-x64"
|
|
43
|
+
],
|
|
44
|
+
"output": "myapp.exe",
|
|
45
|
+
"outputPath": "dist",
|
|
46
|
+
"disableExperimentalSEAWarning": true,
|
|
47
|
+
"useSnapshot": true,
|
|
48
|
+
"useCodeCache": false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration Options
|
|
54
|
+
|
|
55
|
+
- **entry**: Path to your bundled application entry point
|
|
56
|
+
- **assets**: Array of glob patterns for files to include (supports `!` prefix for exclusions, e.g., `"!**/*.md"`)
|
|
57
|
+
- **binaries**: Patterns to identify binary files that need extraction (e.g., `.node`, `.dll`)
|
|
58
|
+
- **targets**: Array of target platforms (format: `nodeX.Y.Z-platform-arch`)
|
|
59
|
+
- **output**: Name of the output executable
|
|
60
|
+
- **outputPath**: Directory for build output
|
|
61
|
+
- **disableExperimentalSEAWarning**: Suppress Node.js SEA experimental warnings (default: true)
|
|
62
|
+
- **useSnapshot**: Enable V8 snapshot for faster startup (default: false)
|
|
63
|
+
- **useCodeCache**: Enable V8 code cache (default: false)
|
|
64
|
+
- **encryptAssets**: Enable encryption for assets (default: false)
|
|
65
|
+
- **encryptExclude**: Patterns to exclude from encryption (e.g., `['*.txt']`)
|
|
66
|
+
- **exclude**: *(Deprecated)* Use `!` prefix in assets array instead
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
After installing `seabox` as a dev dependency and configuring your `package.json`, build your SEA executable:
|
|
71
|
+
|
|
72
|
+
### npm script (recommended)
|
|
73
|
+
|
|
74
|
+
Add a build script to your `package.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"scripts": {
|
|
79
|
+
"build:exe": "seabox",
|
|
80
|
+
"build:exe:verbose": "seabox --verbose"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then run:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run build:exe
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### CLI
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Build using package.json configuration
|
|
95
|
+
npx seabox
|
|
96
|
+
|
|
97
|
+
# Verbose output
|
|
98
|
+
npx seabox --verbose
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Programmatic API
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
const { build } = require('seabox');
|
|
105
|
+
|
|
106
|
+
await build({
|
|
107
|
+
projectRoot: process.cwd(),
|
|
108
|
+
verbose: true
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## How It Works
|
|
113
|
+
|
|
114
|
+
1. **Asset Scanning**: Scans and resolves all files matching your asset patterns
|
|
115
|
+
2. **Manifest Generation**: Creates a runtime manifest with metadata for binary extraction
|
|
116
|
+
3. **Bootstrap Injection**: Prepends bootstrap code to handle native module extraction
|
|
117
|
+
4. **Blob Creation**: Uses Node.js SEA tooling to create the application blob
|
|
118
|
+
5. **Binary Fetching**: Downloads the target Node.js binary and removes its signature
|
|
119
|
+
6. **Injection**: Uses postject to inject the blob into the Node binary
|
|
120
|
+
7. **Output**: Produces a standalone executable ready for signing and distribution
|
|
121
|
+
|
|
122
|
+
## Binary Extraction
|
|
123
|
+
|
|
124
|
+
Native modules (`.node`, `.dll`, `.so`, `.dylib`) are automatically:
|
|
125
|
+
- Extracted to a cache directory on first run
|
|
126
|
+
- Integrity-checked using SHA-256 hashes
|
|
127
|
+
- Loaded via custom module resolution
|
|
128
|
+
|
|
129
|
+
Cache location: `%LOCALAPPDATA%\.sea-cache\<appname>\<version>-<platform>-<arch>`
|
|
130
|
+
|
|
131
|
+
## Native Module Rebuilding
|
|
132
|
+
|
|
133
|
+
If your project has native modules (e.g., `.node` bindings), you may need to rebuild them for the target Node.js version:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Rebuild for a specific target
|
|
137
|
+
npx seabox-rebuild --target node24.11.0-win32-x64
|
|
138
|
+
|
|
139
|
+
# Rebuild with separate options
|
|
140
|
+
npx seabox-rebuild --node-version 24.11.0 --platform linux --arch x64
|
|
141
|
+
|
|
142
|
+
# Rebuild in a specific directory
|
|
143
|
+
npx seabox-rebuild /path/to/project --target node24.11.0-linux-x64
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The rebuilder will:
|
|
147
|
+
- Scan all dependencies for native modules (those with `binding.gyp` or `gypfile: true`)
|
|
148
|
+
- Rebuild each one using `node-gyp` for the target platform and Node.js version
|
|
149
|
+
- Download necessary headers for cross-compilation
|
|
150
|
+
|
|
151
|
+
**Note**: Cross-compilation may require additional platform-specific build tools installed.
|
|
152
|
+
|
|
153
|
+
## Asset Encryption
|
|
154
|
+
|
|
155
|
+
seabox supports optional AES-256-GCM encryption of embedded assets to protect your application code and data:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"sea": {
|
|
160
|
+
"encryptAssets": true,
|
|
161
|
+
"encryptExclude": ["*.txt", "public/*"],
|
|
162
|
+
"useSnapshot": true
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### How Encryption Works
|
|
168
|
+
|
|
169
|
+
1. **Build Time**: A random 256-bit encryption key is generated
|
|
170
|
+
2. **Asset Encryption**: Non-binary assets are encrypted using AES-256-GCM
|
|
171
|
+
3. **Key Embedding**: The encryption key is obfuscated and embedded in the bootstrap code
|
|
172
|
+
4. **Snapshot Obfuscation**: When `useSnapshot: true`, the key becomes part of V8 bytecode
|
|
173
|
+
5. **Runtime Decryption**: Assets are transparently decrypted when accessed
|
|
174
|
+
|
|
175
|
+
### Security Considerations
|
|
176
|
+
|
|
177
|
+
- **Binary files** (`.node`, `.dll`, `.so`, `.dylib`) are **never encrypted** as they must be extracted as-is
|
|
178
|
+
- The manifest (`sea-manifest.json`) is **not encrypted** to allow bootstrap initialization
|
|
179
|
+
- Encryption provides **obfuscation**, not cryptographic security against determined attackers
|
|
180
|
+
- When combined with V8 snapshots, the encryption key is compiled into bytecode, making extraction significantly harder
|
|
181
|
+
- Use `encryptExclude` to skip encryption for non-sensitive assets (e.g., public files, documentation)
|
|
182
|
+
|
|
183
|
+
### Best Practices
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"sea": {
|
|
188
|
+
"encryptAssets": true,
|
|
189
|
+
"useSnapshot": true, // Strongly recommended with encryption
|
|
190
|
+
"assets": [
|
|
191
|
+
"./out/**/*",
|
|
192
|
+
"!**/*.md", // Exclude documentation
|
|
193
|
+
"!**/public/**", // Exclude public assets
|
|
194
|
+
"!**/LICENSE" // Exclude license files
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Platform Support
|
|
201
|
+
|
|
202
|
+
- **Windows**: `win32-x64`, `win32-arm64`
|
|
203
|
+
- **Linux**: `linux-x64`, `linux-arm64`
|
|
204
|
+
- **macOS**: `darwin-x64`, `darwin-arm64`
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
MIT
|
|
209
|
+
Copyright Meirion Hughes 2025
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native Module Rebuilder for SEA Builder
|
|
5
|
+
*
|
|
6
|
+
* Scans package dependencies to identify native modules (those with binding.gyp
|
|
7
|
+
* or gypfile: true in package.json) and rebuilds them using node-gyp for a
|
|
8
|
+
* target Node.js version and platform.
|
|
9
|
+
*
|
|
10
|
+
* Supports cross-compilation for different platforms and architectures.
|
|
11
|
+
* Inspired by @electron/rebuild's module identification strategy.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
class NativeModuleRebuilder {
|
|
19
|
+
constructor(buildPath, options = {}) {
|
|
20
|
+
this.buildPath = buildPath || process.cwd();
|
|
21
|
+
this.nativeModules = new Map();
|
|
22
|
+
this.visitedPaths = new Set();
|
|
23
|
+
|
|
24
|
+
// Target configuration
|
|
25
|
+
this.targetNodeVersion = options.nodeVersion || process.version.replace('v', '');
|
|
26
|
+
this.targetPlatform = options.platform || process.platform;
|
|
27
|
+
this.targetArch = options.arch || process.arch;
|
|
28
|
+
|
|
29
|
+
// Parse from target string if provided (e.g., "node24.11.0-win32-x64")
|
|
30
|
+
if (options.target) {
|
|
31
|
+
const match = options.target.match(/^node(\d+\.\d+\.\d+)-(\w+)-(\w+)$/);
|
|
32
|
+
if (match) {
|
|
33
|
+
this.targetNodeVersion = match[1];
|
|
34
|
+
this.targetPlatform = match[2];
|
|
35
|
+
this.targetArch = match[3];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read and parse package.json from a directory
|
|
42
|
+
*/
|
|
43
|
+
readPackageJson(modulePath) {
|
|
44
|
+
const packageJsonPath = path.join(modulePath, 'package.json');
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
47
|
+
const content = fs.readFileSync(packageJsonPath, 'utf8');
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(`Warning: Could not read package.json at ${packageJsonPath}:`, error.message);
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if a module is a native module
|
|
58
|
+
* A module is considered native if it has:
|
|
59
|
+
* - A binding.gyp file, OR
|
|
60
|
+
* - gypfile: true in package.json
|
|
61
|
+
*/
|
|
62
|
+
isNativeModule(modulePath) {
|
|
63
|
+
// Check for binding.gyp
|
|
64
|
+
const bindingGypPath = path.join(modulePath, 'binding.gyp');
|
|
65
|
+
if (fs.existsSync(bindingGypPath)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check for gypfile: true in package.json
|
|
70
|
+
const packageJson = this.readPackageJson(modulePath);
|
|
71
|
+
if (packageJson && packageJson.gypfile === true) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the actual path of a dependency
|
|
80
|
+
*/
|
|
81
|
+
resolveDependencyPath(fromPath, depName) {
|
|
82
|
+
const possiblePaths = [];
|
|
83
|
+
|
|
84
|
+
// Check in local node_modules
|
|
85
|
+
let currentPath = fromPath;
|
|
86
|
+
while (currentPath !== path.parse(currentPath).root) {
|
|
87
|
+
const nodeModulesPath = path.join(currentPath, 'node_modules', depName);
|
|
88
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
89
|
+
possiblePaths.push(nodeModulesPath);
|
|
90
|
+
}
|
|
91
|
+
currentPath = path.dirname(currentPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Return the first valid path, resolving symlinks
|
|
95
|
+
for (const testPath of possiblePaths) {
|
|
96
|
+
try {
|
|
97
|
+
return fs.realpathSync(testPath);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Skip if symlink is broken
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Scan a directory's dependencies recursively
|
|
109
|
+
*/
|
|
110
|
+
scanDependencies(modulePath, depth = 0) {
|
|
111
|
+
// Resolve symlinks to avoid scanning the same module multiple times
|
|
112
|
+
let realPath;
|
|
113
|
+
try {
|
|
114
|
+
realPath = fs.realpathSync(modulePath);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn(`Warning: Could not resolve path ${modulePath}:`, error.message);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Skip if already visited
|
|
121
|
+
if (this.visitedPaths.has(realPath)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.visitedPaths.add(realPath);
|
|
125
|
+
|
|
126
|
+
// Check if this module is a native module
|
|
127
|
+
if (this.isNativeModule(realPath)) {
|
|
128
|
+
const packageJson = this.readPackageJson(realPath);
|
|
129
|
+
const moduleName = packageJson ? packageJson.name : path.basename(realPath);
|
|
130
|
+
|
|
131
|
+
if (!this.nativeModules.has(realPath)) {
|
|
132
|
+
this.nativeModules.set(realPath, moduleName);
|
|
133
|
+
console.log(`Found native module: ${moduleName} at ${realPath}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Read package.json to find dependencies
|
|
138
|
+
const packageJson = this.readPackageJson(realPath);
|
|
139
|
+
if (!packageJson) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Collect all types of dependencies
|
|
144
|
+
const allDeps = {
|
|
145
|
+
...packageJson.dependencies,
|
|
146
|
+
...packageJson.optionalDependencies,
|
|
147
|
+
...packageJson.devDependencies
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Scan each dependency
|
|
151
|
+
for (const depName of Object.keys(allDeps)) {
|
|
152
|
+
const depPath = this.resolveDependencyPath(realPath, depName);
|
|
153
|
+
if (depPath) {
|
|
154
|
+
this.scanDependencies(depPath, depth + 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Also check nested node_modules
|
|
159
|
+
const nodeModulesPath = path.join(realPath, 'node_modules');
|
|
160
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
161
|
+
try {
|
|
162
|
+
const modules = fs.readdirSync(nodeModulesPath);
|
|
163
|
+
for (const moduleName of modules) {
|
|
164
|
+
if (moduleName === '.bin') continue;
|
|
165
|
+
|
|
166
|
+
const modulePath = path.join(nodeModulesPath, moduleName);
|
|
167
|
+
const stat = fs.lstatSync(modulePath);
|
|
168
|
+
|
|
169
|
+
if (stat.isDirectory()) {
|
|
170
|
+
// Handle scoped packages (e.g., @robgeoltd/lib-m2)
|
|
171
|
+
if (moduleName.startsWith('@')) {
|
|
172
|
+
const scopedModules = fs.readdirSync(modulePath);
|
|
173
|
+
for (const scopedModule of scopedModules) {
|
|
174
|
+
const scopedPath = path.join(modulePath, scopedModule);
|
|
175
|
+
this.scanDependencies(scopedPath, depth + 1);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
this.scanDependencies(modulePath, depth + 1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.warn(`Warning: Could not read node_modules at ${nodeModulesPath}:`, error.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get platform-specific configuration for cross-compilation
|
|
190
|
+
*/
|
|
191
|
+
getCrossCompileEnv() {
|
|
192
|
+
const env = { ...process.env };
|
|
193
|
+
|
|
194
|
+
// Set target platform and architecture
|
|
195
|
+
if (this.targetPlatform !== process.platform) {
|
|
196
|
+
console.log(` Cross-compiling for platform: ${this.targetPlatform}`);
|
|
197
|
+
}
|
|
198
|
+
if (this.targetArch !== process.arch) {
|
|
199
|
+
console.log(` Cross-compiling for architecture: ${this.targetArch}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Map common arch names to node-gyp format
|
|
203
|
+
const archMap = {
|
|
204
|
+
'x64': 'x64',
|
|
205
|
+
'ia32': 'ia32',
|
|
206
|
+
'arm': 'arm',
|
|
207
|
+
'arm64': 'arm64'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
env.npm_config_target = this.targetNodeVersion;
|
|
211
|
+
env.npm_config_arch = archMap[this.targetArch] || this.targetArch;
|
|
212
|
+
env.npm_config_target_arch = archMap[this.targetArch] || this.targetArch;
|
|
213
|
+
env.npm_config_disturl = 'https://nodejs.org/dist';
|
|
214
|
+
env.npm_config_runtime = 'node';
|
|
215
|
+
env.npm_config_build_from_source = 'true';
|
|
216
|
+
|
|
217
|
+
return env;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Rebuild a native module using node-gyp for the target platform
|
|
222
|
+
*/
|
|
223
|
+
rebuildModule(modulePath, moduleName) {
|
|
224
|
+
console.log(`\nRebuilding ${moduleName}...`);
|
|
225
|
+
console.log(` Path: ${modulePath}`);
|
|
226
|
+
console.log(` Target: Node.js ${this.targetNodeVersion} (${this.targetPlatform}-${this.targetArch})`);
|
|
227
|
+
|
|
228
|
+
// Determine if we should use npx node-gyp or just node-gyp
|
|
229
|
+
let useNpx = false;
|
|
230
|
+
try {
|
|
231
|
+
execSync('node-gyp --version', { stdio: 'pipe' });
|
|
232
|
+
} catch (error) {
|
|
233
|
+
useNpx = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const nodeGypCmd = useNpx ? 'npx node-gyp' : 'node-gyp';
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const env = this.getCrossCompileEnv();
|
|
240
|
+
|
|
241
|
+
// Clean previous builds
|
|
242
|
+
try {
|
|
243
|
+
execSync(`${nodeGypCmd} clean`, {
|
|
244
|
+
cwd: modulePath,
|
|
245
|
+
stdio: 'pipe',
|
|
246
|
+
env
|
|
247
|
+
});
|
|
248
|
+
} catch (cleanError) {
|
|
249
|
+
// Ignore clean errors
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Configure for target
|
|
253
|
+
const configureCmd = `${nodeGypCmd} configure --target=${this.targetNodeVersion} --arch=${this.targetArch} --dist-url=https://nodejs.org/dist`;
|
|
254
|
+
execSync(configureCmd, {
|
|
255
|
+
cwd: modulePath,
|
|
256
|
+
stdio: 'inherit',
|
|
257
|
+
env
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Build
|
|
261
|
+
execSync(`${nodeGypCmd} build`, {
|
|
262
|
+
cwd: modulePath,
|
|
263
|
+
stdio: 'inherit',
|
|
264
|
+
env
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
console.log(`✓ Successfully rebuilt ${moduleName}`);
|
|
268
|
+
return true;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(`✗ Failed to rebuild ${moduleName}:`, error.message);
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Main rebuild process
|
|
277
|
+
*/
|
|
278
|
+
async rebuild() {
|
|
279
|
+
console.log('Starting native module rebuild process...');
|
|
280
|
+
console.log(`Build path: ${this.buildPath}`);
|
|
281
|
+
console.log(`Target: Node.js ${this.targetNodeVersion} (${this.targetPlatform}-${this.targetArch})\n`);
|
|
282
|
+
|
|
283
|
+
// Verify node-gyp is available
|
|
284
|
+
try {
|
|
285
|
+
execSync('node-gyp --version', { stdio: 'pipe' });
|
|
286
|
+
} catch (error) {
|
|
287
|
+
// Try with npx
|
|
288
|
+
try {
|
|
289
|
+
execSync('npx node-gyp --version', { stdio: 'pipe' });
|
|
290
|
+
} catch (npxError) {
|
|
291
|
+
console.error('Error: node-gyp is not installed or not in PATH');
|
|
292
|
+
console.error('Install it with: npm install -g node-gyp');
|
|
293
|
+
console.error('Or ensure it is in your project dependencies');
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Scan for native modules
|
|
299
|
+
console.log('Scanning dependencies for native modules...\n');
|
|
300
|
+
this.scanDependencies(this.buildPath);
|
|
301
|
+
|
|
302
|
+
if (this.nativeModules.size === 0) {
|
|
303
|
+
console.log('\nNo native modules found.');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(`\nFound ${this.nativeModules.size} native module(s) to rebuild.\n`);
|
|
308
|
+
console.log('='.repeat(60));
|
|
309
|
+
|
|
310
|
+
// Rebuild each native module
|
|
311
|
+
let successCount = 0;
|
|
312
|
+
let failCount = 0;
|
|
313
|
+
|
|
314
|
+
for (const [modulePath, moduleName] of this.nativeModules) {
|
|
315
|
+
const success = this.rebuildModule(modulePath, moduleName);
|
|
316
|
+
if (success) {
|
|
317
|
+
successCount++;
|
|
318
|
+
} else {
|
|
319
|
+
failCount++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Summary
|
|
324
|
+
console.log('\n' + '='.repeat(60));
|
|
325
|
+
console.log('\nRebuild Summary:');
|
|
326
|
+
console.log(` Total modules: ${this.nativeModules.size}`);
|
|
327
|
+
console.log(` Successful: ${successCount}`);
|
|
328
|
+
console.log(` Failed: ${failCount}`);
|
|
329
|
+
|
|
330
|
+
if (failCount > 0) {
|
|
331
|
+
console.log('\nSome modules failed to rebuild. Check the errors above.');
|
|
332
|
+
process.exit(1);
|
|
333
|
+
} else {
|
|
334
|
+
console.log('\n✓ All native modules rebuilt successfully!');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Main execution
|
|
340
|
+
if (require.main === module) {
|
|
341
|
+
const args = process.argv.slice(2);
|
|
342
|
+
|
|
343
|
+
// Parse command line arguments
|
|
344
|
+
const options = {};
|
|
345
|
+
let buildPath = process.cwd();
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < args.length; i++) {
|
|
348
|
+
const arg = args[i];
|
|
349
|
+
|
|
350
|
+
if (arg === '--target' && args[i + 1]) {
|
|
351
|
+
options.target = args[i + 1];
|
|
352
|
+
i++;
|
|
353
|
+
} else if (arg === '--node-version' && args[i + 1]) {
|
|
354
|
+
options.nodeVersion = args[i + 1];
|
|
355
|
+
i++;
|
|
356
|
+
} else if (arg === '--platform' && args[i + 1]) {
|
|
357
|
+
options.platform = args[i + 1];
|
|
358
|
+
i++;
|
|
359
|
+
} else if (arg === '--arch' && args[i + 1]) {
|
|
360
|
+
options.arch = args[i + 1];
|
|
361
|
+
i++;
|
|
362
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
363
|
+
console.log(`
|
|
364
|
+
seabox-rebuild - Rebuild native modules for target Node.js version
|
|
365
|
+
|
|
366
|
+
Usage:
|
|
367
|
+
seabox-rebuild [options] [build-path]
|
|
368
|
+
|
|
369
|
+
Options:
|
|
370
|
+
--target <target> Target in format: nodeX.Y.Z-platform-arch
|
|
371
|
+
Example: node24.11.0-win32-x64
|
|
372
|
+
--node-version <version> Target Node.js version (e.g., 24.11.0)
|
|
373
|
+
--platform <platform> Target platform (win32, linux, darwin)
|
|
374
|
+
--arch <arch> Target architecture (x64, arm64, ia32)
|
|
375
|
+
--help, -h Show this help message
|
|
376
|
+
|
|
377
|
+
Examples:
|
|
378
|
+
seabox-rebuild --target node24.11.0-win32-x64
|
|
379
|
+
seabox-rebuild --node-version 24.11.0 --platform linux --arch x64
|
|
380
|
+
seabox-rebuild /path/to/project
|
|
381
|
+
`);
|
|
382
|
+
process.exit(0);
|
|
383
|
+
} else if (!arg.startsWith('--')) {
|
|
384
|
+
buildPath = arg;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const rebuilder = new NativeModuleRebuilder(buildPath, options);
|
|
389
|
+
rebuilder.rebuild().catch(error => {
|
|
390
|
+
console.error('Fatal error:', error);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
module.exports = { NativeModuleRebuilder };
|
package/bin/seabox.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for SEA builder
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { build } = require('../lib');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
const configPath = args.includes('--config') ? args[args.indexOf('--config') + 1] : undefined;
|
|
15
|
+
const verbose = args.includes('--verbose');
|
|
16
|
+
const debug = args.includes('--debug');
|
|
17
|
+
|
|
18
|
+
let showHelp = args.includes('--help') || args.includes('-h');
|
|
19
|
+
|
|
20
|
+
// Try to load config to check if it exists
|
|
21
|
+
if (!configPath) {
|
|
22
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
23
|
+
if (!fs.existsSync(pkgPath)) {
|
|
24
|
+
showHelp = true;
|
|
25
|
+
}
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
27
|
+
if (!pkg.seabox) {
|
|
28
|
+
showHelp = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Show help
|
|
33
|
+
if (showHelp) {
|
|
34
|
+
console.log(`
|
|
35
|
+
SEA Builder - Node.js Single Executable Application Builder
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
seabox [options]
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--config <path> Path to config file (default: reads from package.json)
|
|
42
|
+
--verbose Enable verbose logging
|
|
43
|
+
--debug Keep temporary build files for debugging
|
|
44
|
+
--help, -h Show this help message
|
|
45
|
+
|
|
46
|
+
Configuration:
|
|
47
|
+
Add a "seabox" field to your package.json:
|
|
48
|
+
{
|
|
49
|
+
"seabox": {
|
|
50
|
+
"entry": "./out/app.js",
|
|
51
|
+
"assets": ["./out/**/*"],
|
|
52
|
+
"binaries": ["*.node", "*.dll"],
|
|
53
|
+
"targets": ["node24.11.0-win32-x64"],
|
|
54
|
+
"output": "myapp.exe",
|
|
55
|
+
"outputPath": "dist",
|
|
56
|
+
"useSnapshot": false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
For more information, see the documentation.
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await build({
|
|
67
|
+
configPath,
|
|
68
|
+
verbose,
|
|
69
|
+
debug,
|
|
70
|
+
projectRoot: process.cwd()
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Build failed:', error.message);
|
|
74
|
+
if (verbose) {
|
|
75
|
+
console.error(error.stack);
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|