quickjs-zig 1.0.0
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/.gitmodules +3 -0
- package/README.md +147 -0
- package/build.mjs +175 -0
- package/index.mjs +60 -0
- package/package.json +34 -0
- package/postinstall.mjs +116 -0
- package/src/exec.c +183 -0
package/.gitmodules
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# QuickJS-Zig ⚡
|
|
2
|
+
|
|
3
|
+
A high-performance build system for **QuickJS**, powered by the **Zig** compiler. This project allows you to compile JavaScript applications into standalone native binaries for Windows, Linux, and macOS with zero system dependencies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
* **Zero Config Cross-Compilation**: Build for Windows, Linux, and macOS from any host (Intel or Apple Silicon).
|
|
8
|
+
* **Zig-Powered**: Uses **Zig 0.15.2** as a C compiler for modern, safe, and highly optimized binaries.
|
|
9
|
+
* **Custom C Modules**: Easily inject and register your own C modules into the QuickJS engine.
|
|
10
|
+
* **Native Windows Support**: Includes a custom `exec` implementation for Windows, bypassing typical QuickJS POSIX limitations.
|
|
11
|
+
* **Clean Source Management**: Automatically patches and restores QuickJS source files to keep the core library pristine.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Add `quickjs-zig` as a development dependency in your project:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev quickjs-zig
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
> **Note**: During installation, the `postinstall` script will automatically download the appropriate Zig 0.15.2 binary for your platform and initialize the QuickJS submodules.
|
|
25
|
+
|
|
26
|
+
### Development / Local setup
|
|
27
|
+
|
|
28
|
+
If you want to contribute or test the latest version from source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/eid-app/quickjs-zig.git
|
|
32
|
+
cd quickjs-zig
|
|
33
|
+
npm install
|
|
34
|
+
npm link
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
Configure your entry point and custom C modules in your project's `package.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"name": "my-app",
|
|
47
|
+
"quickJs": {
|
|
48
|
+
"input": "app/index.mjs",
|
|
49
|
+
"modules": {
|
|
50
|
+
"my_module": "src/my_module.c"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
* **`input`**: The entry point of your JavaScript application (defaults to `app/index.mjs`).
|
|
58
|
+
* **`modules`**: A key-value map of custom C modules (Module Name -> C Source Path).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Example Usage
|
|
63
|
+
|
|
64
|
+
### 1. Create a C Module (`src/my_module.c`)
|
|
65
|
+
|
|
66
|
+
This module will be automatically compiled and linked into your final binary.
|
|
67
|
+
|
|
68
|
+
```c
|
|
69
|
+
#include "quickjs-libc.h"
|
|
70
|
+
|
|
71
|
+
// A simple native function
|
|
72
|
+
static JSValue js_hello(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
|
73
|
+
return JS_NewString(ctx, "Hello from the native side!");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Module initialization
|
|
77
|
+
JSModuleDef *js_init_module_my_module(JSContext *ctx, const char *module_name) {
|
|
78
|
+
JSModuleDef *m = JS_NewCModule(ctx, module_name, NULL);
|
|
79
|
+
if (!m) return NULL;
|
|
80
|
+
JS_AddModuleExport(ctx, m, "hello");
|
|
81
|
+
JS_SetModuleExport(ctx, m, "hello", JS_NewCFunction(ctx, js_hello, "hello", 0));
|
|
82
|
+
return m;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Create your JavaScript (`app/index.mjs`)
|
|
88
|
+
|
|
89
|
+
Import your custom module just like any other ES module.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import { hello } from 'my_module';
|
|
93
|
+
import * as os from 'os';
|
|
94
|
+
|
|
95
|
+
// Use the native function
|
|
96
|
+
const message = hello();
|
|
97
|
+
console.log(message);
|
|
98
|
+
|
|
99
|
+
// Standard QuickJS modules are also fully supported
|
|
100
|
+
console.log(`Platform: ${os.platform}`);
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Build your binaries
|
|
105
|
+
|
|
106
|
+
Run the build command to generate tools and standalone binaries for all targets:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx quickjs-zig build
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Supported Targets
|
|
116
|
+
|
|
117
|
+
The build system generates binaries for the following platforms in the `dist/` folder:
|
|
118
|
+
|
|
119
|
+
| Platform | Architecture | Target ID | Binary Name |
|
|
120
|
+
| --- | --- | --- | --- |
|
|
121
|
+
| **Windows** | x64 / x86 | `x86_64-windows-gnu` / `x86-windows-gnu` | `app_win64.exe` / `app_win32.exe` |
|
|
122
|
+
| **Linux** | x64 / x86 / ARM64 | `x86_64-linux-gnu` / `x86-linux-gnu` / `aarch64-linux-gnu` | `app_linux64` / `app_linux32` / `app_linux_arm64` |
|
|
123
|
+
| **macOS** | Apple / Intel | `aarch64-macos` / `x86_64-macos` | `app_mac_arm` / `app_mac_intel` |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Technical Details
|
|
128
|
+
|
|
129
|
+
### Why Zig?
|
|
130
|
+
|
|
131
|
+
Zig is not just a language; it's a powerful C/C++ toolchain. It allows `quickjs-zig` to:
|
|
132
|
+
|
|
133
|
+
* Cross-compile to Windows (MinGW) from macOS/Linux without installing complex toolchains.
|
|
134
|
+
* Provide a consistent `libc` environment across different platforms.
|
|
135
|
+
* Produce small, fast, and statically linked binaries.
|
|
136
|
+
|
|
137
|
+
### Windows Patching
|
|
138
|
+
|
|
139
|
+
QuickJS is designed for POSIX systems. This tool automatically patches `quickjs-libc.c` during the build process to provide a working `os.exec` on Windows using a native `_spawnvp` implementation.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT - Created by **eid-app**.
|
|
146
|
+
|
|
147
|
+
---
|
package/build.mjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync, unlinkSync, existsSync, readdirSync, cpSync, readFileSync, renameSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
// --- PATH CONFIGURATION ---
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const USER_CWD = process.cwd();
|
|
10
|
+
|
|
11
|
+
// Load the user project's package.json
|
|
12
|
+
const USER_PKG_PATH = path.join(USER_CWD, 'package.json');
|
|
13
|
+
if (!existsSync(USER_PKG_PATH)) {
|
|
14
|
+
console.error("❌ Error: package.json not found in current directory.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const userPackageJson = JSON.parse(readFileSync(USER_PKG_PATH, 'utf8'));
|
|
19
|
+
const customModules = userPackageJson.quickJs?.modules || {};
|
|
20
|
+
const APP_NAME = userPackageJson.name || 'app';
|
|
21
|
+
|
|
22
|
+
// Input file from package.json or default to app/index.mjs
|
|
23
|
+
const INPUT_FILE_RELATIVE = userPackageJson.quickJs?.input || 'app/index.mjs';
|
|
24
|
+
const INPUT_FILE_ABS = path.resolve(USER_CWD, INPUT_FILE_RELATIVE);
|
|
25
|
+
|
|
26
|
+
if (!existsSync(INPUT_FILE_ABS)) {
|
|
27
|
+
console.error(`❌ Error: Input file not found at ${INPUT_FILE_ABS}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const QUICKJS_DIR = path.resolve(__dirname, 'quickjs');
|
|
32
|
+
const ZIG_PATH = path.resolve(__dirname, 'bin', 'zig', os.platform() === 'win32' ? 'zig.exe' : 'zig');
|
|
33
|
+
const VERSION = '2024-01-13';
|
|
34
|
+
const BIN_DIR = path.join(USER_CWD, 'bin');
|
|
35
|
+
const DIST_DIR = path.join(USER_CWD, 'dist');
|
|
36
|
+
|
|
37
|
+
const LIBC_PATH = path.join(QUICKJS_DIR, 'quickjs-libc.c');
|
|
38
|
+
const LIBC_BAK = path.join(QUICKJS_DIR, 'quickjs-libc.c.bak');
|
|
39
|
+
const QJS_C_PATH = path.join(QUICKJS_DIR, 'qjs.c');
|
|
40
|
+
const QJS_C_BAK = path.join(QUICKJS_DIR, 'qjs.c.bak');
|
|
41
|
+
const QJSC_C_PATH = path.join(QUICKJS_DIR, 'qjsc.c');
|
|
42
|
+
const QJSC_C_BAK = path.join(QUICKJS_DIR, 'qjsc.c.bak');
|
|
43
|
+
const EXEC_SRC_PATH = path.resolve(__dirname, 'src', 'exec.c');
|
|
44
|
+
|
|
45
|
+
if (!existsSync(BIN_DIR)) mkdirSync(BIN_DIR, { recursive: true });
|
|
46
|
+
if (!existsSync(DIST_DIR)) mkdirSync(DIST_DIR, { recursive: true });
|
|
47
|
+
|
|
48
|
+
// Detect the host architecture to find the correct qjsc binary
|
|
49
|
+
const platform = os.platform();
|
|
50
|
+
const arch = os.arch();
|
|
51
|
+
let hostQjscName = '';
|
|
52
|
+
|
|
53
|
+
if (platform === 'darwin') hostQjscName = (arch === 'arm64') ? 'qjsc_mac_arm' : 'qjsc_mac_intel';
|
|
54
|
+
else if (platform === 'linux') hostQjscName = (arch === 'arm64' || arch === 'aarch64') ? 'qjsc_linux_arm' : 'qjsc_linux64';
|
|
55
|
+
else if (platform === 'win32') hostQjscName = 'qjsc_win64.exe';
|
|
56
|
+
|
|
57
|
+
const hostQjscPath = path.join(BIN_DIR, hostQjscName);
|
|
58
|
+
|
|
59
|
+
// ==========================================================
|
|
60
|
+
// STAGE 0: PATCHING
|
|
61
|
+
// ==========================================================
|
|
62
|
+
console.log("=== STAGE 0: PATCHING QUICKJS SOURCES ===");
|
|
63
|
+
|
|
64
|
+
if (existsSync(LIBC_PATH)) {
|
|
65
|
+
if (!existsSync(LIBC_BAK)) cpSync(LIBC_PATH, LIBC_BAK);
|
|
66
|
+
let content = readFileSync(LIBC_PATH, 'utf8');
|
|
67
|
+
|
|
68
|
+
if (!content.includes('#include <process.h>')) {
|
|
69
|
+
content = '#if defined(_WIN32)\n#include <process.h>\n#endif\n' + content;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const name of Object.keys(customModules)) {
|
|
73
|
+
const decl = `JSModuleDef *js_init_module_${name}(JSContext *ctx, const char *module_name);`;
|
|
74
|
+
if (!content.includes(decl)) content = content.replace('#include "quickjs-libc.h"', `#include "quickjs-libc.h"\n\n${decl}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (existsSync(EXEC_SRC_PATH)) {
|
|
78
|
+
const execImpl = readFileSync(EXEC_SRC_PATH, 'utf8');
|
|
79
|
+
if (!content.includes('js_os_exec_win32')) {
|
|
80
|
+
content = content.replace('static JSClassDef js_worker_class = {', execImpl + '\nstatic JSClassDef js_worker_class = {');
|
|
81
|
+
}
|
|
82
|
+
const oldEntry = ' JS_CFUNC_DEF("exec", 1, js_os_exec ),';
|
|
83
|
+
const newEntry = `#if defined(_WIN32)\n JS_CFUNC_DEF("exec", 1, js_os_exec_win32 ),\n#else\n JS_CFUNC_DEF("exec", 1, js_os_exec ),\n#endif`;
|
|
84
|
+
if (content.includes(oldEntry)) content = content.replace(oldEntry, newEntry);
|
|
85
|
+
}
|
|
86
|
+
writeFileSync(LIBC_PATH, content);
|
|
87
|
+
console.log("✅ quickjs-libc.c patched.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[ [QJS_C_PATH, QJS_C_BAK], [QJSC_C_PATH, QJSC_C_BAK] ].forEach(([p, b]) => {
|
|
91
|
+
if (!existsSync(p)) return;
|
|
92
|
+
if (!existsSync(b)) cpSync(p, b);
|
|
93
|
+
let content = readFileSync(p, 'utf8');
|
|
94
|
+
const isQjsc = p.includes('qjsc.c');
|
|
95
|
+
|
|
96
|
+
for (const name of Object.keys(customModules)) {
|
|
97
|
+
if (isQjsc) {
|
|
98
|
+
const entry = `namelist_add(&cmodule_list, "${name}", "${name}", 0);`;
|
|
99
|
+
if (!content.includes(entry)) content = content.replace('namelist_add(&cmodule_list, "os", "os", 0);', `namelist_add(&cmodule_list, "os", "os", 0);\n ${entry}`);
|
|
100
|
+
} else {
|
|
101
|
+
const decl = `JSModuleDef *js_init_module_${name}(JSContext *ctx, const char *module_name);`;
|
|
102
|
+
const call = `js_init_module_${name}(ctx, "${name}");`;
|
|
103
|
+
if (!content.includes(decl)) content = content.replace('#include "quickjs-libc.h"', `#include "quickjs-libc.h"\n\n${decl}`);
|
|
104
|
+
if (!content.includes(call)) content = content.replace('js_init_module_os(ctx, "os");', `js_init_module_os(ctx, "os");\n ${call}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
writeFileSync(p, content);
|
|
108
|
+
console.log(`✅ ${path.basename(p)} patched.`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const baseSources = ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'quickjs-libc.c', 'dtoa.c']
|
|
112
|
+
.map(f => path.join(QUICKJS_DIR, f))
|
|
113
|
+
.concat(Object.values(customModules).map(f => path.resolve(USER_CWD, f)))
|
|
114
|
+
.join(' ');
|
|
115
|
+
|
|
116
|
+
const targets = [
|
|
117
|
+
{ id: 'x86_64-windows-gnu', qjs: 'qjs_win64.exe', qjsc: 'qjsc_win64.exe', app: `${APP_NAME}_win64.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE' },
|
|
118
|
+
{ id: 'x86-windows-gnu', qjs: 'qjs_win32.exe', qjsc: 'qjsc_win32.exe', app: `${APP_NAME}_win32.exe`, libs: '-lm', cflags: '-D_GNU_SOURCE' },
|
|
119
|
+
{ id: 'x86_64-linux-gnu', qjs: 'qjs_linux64', qjsc: 'qjsc_linux64', app: `${APP_NAME}_linux64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
|
|
120
|
+
{ id: 'x86-linux-gnu', qjs: 'qjs_linux32', qjsc: 'qjsc_linux32', app: `${APP_NAME}_linux32`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
|
|
121
|
+
{ id: 'aarch64-linux-gnu', qjs: 'qjs_linux_arm64', qjsc: 'qjsc_linux_arm64', app: `${APP_NAME}_linux_arm64`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
|
|
122
|
+
{ id: 'aarch64-macos', qjs: 'qjs_mac_arm', qjsc: 'qjsc_mac_arm', app: `${APP_NAME}_mac_arm`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' },
|
|
123
|
+
{ id: 'x86_64-macos', qjs: 'qjs_mac_intel', qjsc: 'qjsc_mac_intel', app: `${APP_NAME}_mac_intel`, libs: '-lm -lpthread -ldl', cflags: '-D_GNU_SOURCE -DCONFIG_PTHREAD' }
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// ==========================================================
|
|
127
|
+
// STAGE 1 & 2: TOOLS AND APPLICATION COMPILATION
|
|
128
|
+
// ==========================================================
|
|
129
|
+
const stubPath = path.join(QUICKJS_DIR, 'repl_stub.c');
|
|
130
|
+
writeFileSync(stubPath, `const unsigned char qjsc_repl[] = {0}; const unsigned int qjsc_repl_size = 0;`);
|
|
131
|
+
|
|
132
|
+
targets.forEach(t => {
|
|
133
|
+
console.log(`\n--- Compiling for: ${t.id} ---`);
|
|
134
|
+
const cmdBase = `${ZIG_PATH} cc -target ${t.id} -I${QUICKJS_DIR} -O2 ${t.cflags} -Wno-ignored-attributes -DCONFIG_VERSION=\\"${VERSION}\\" ${t.libs} -s`;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
execSync(`${cmdBase} -o "${path.join(BIN_DIR, t.qjs)}" ${baseSources} ${stubPath} "${path.join(QUICKJS_DIR, 'qjs.c')}"`);
|
|
138
|
+
execSync(`${cmdBase} -o "${path.join(BIN_DIR, t.qjsc)}" ${baseSources} "${path.join(QUICKJS_DIR, 'qjsc.c')}"`);
|
|
139
|
+
console.log(`✅ Build tools generated.`);
|
|
140
|
+
|
|
141
|
+
const tempC = path.join(BIN_DIR, `${t.id}_app.c`);
|
|
142
|
+
const relativeInput = path.relative(USER_CWD, INPUT_FILE_ABS);
|
|
143
|
+
|
|
144
|
+
execSync(`"${hostQjscPath}" -e -o "${tempC}" "${relativeInput}"`, { cwd: USER_CWD });
|
|
145
|
+
|
|
146
|
+
execSync(`${cmdBase} -o "${path.join(DIST_DIR, t.app)}" "${tempC}" ${baseSources} -I${QUICKJS_DIR}`);
|
|
147
|
+
console.log(`✅ Binary built: ${t.app}`);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error(`❌ Compilation failed for ${t.id}`);
|
|
150
|
+
console.error(e.stderr?.toString() || e.message);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ==========================================================
|
|
155
|
+
// STAGE 3: CLEANUP
|
|
156
|
+
// ==========================================================
|
|
157
|
+
console.log("\n=== STAGE 3: CLEANUP ===");
|
|
158
|
+
|
|
159
|
+
// Remove all generated .c files in the bin directory
|
|
160
|
+
const binFiles = readdirSync(BIN_DIR);
|
|
161
|
+
binFiles.forEach(file => {
|
|
162
|
+
if (file.endsWith('_app.c')) {
|
|
163
|
+
unlinkSync(path.join(BIN_DIR, file));
|
|
164
|
+
console.log(`🗑️ Removed temporary source: ${file}`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
[ [LIBC_BAK, LIBC_PATH], [QJS_C_BAK, QJS_C_PATH], [QJSC_C_BAK, QJSC_C_PATH] ].forEach(([b, s]) => {
|
|
169
|
+
if (existsSync(b)) {
|
|
170
|
+
renameSync(b, s);
|
|
171
|
+
console.log(`🔄 Restored original: ${path.basename(s)}`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
if (existsSync(stubPath)) unlinkSync(stubPath);
|
|
175
|
+
console.log("🚀 Build process complete.");
|
package/index.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* quickjs-zig CLI Dispatcher
|
|
5
|
+
* This script is the entry point when running 'npx quickjs-zig'
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const command = args[0];
|
|
15
|
+
|
|
16
|
+
async function run() {
|
|
17
|
+
switch (command) {
|
|
18
|
+
case 'build':
|
|
19
|
+
console.log("🛠️ quickjs-zig: Starting build process...");
|
|
20
|
+
|
|
21
|
+
// On appelle build.mjs en lui passant le reste des arguments
|
|
22
|
+
const buildScript = path.join(__dirname, 'build.mjs');
|
|
23
|
+
const child = spawn('node', [buildScript, ...args.slice(1)], {
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
shell: true
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
child.on('exit', (code) => {
|
|
29
|
+
if (code === 0) {
|
|
30
|
+
console.log("✅ Build completed successfully.");
|
|
31
|
+
}
|
|
32
|
+
process.exit(code);
|
|
33
|
+
});
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'help':
|
|
37
|
+
case undefined:
|
|
38
|
+
console.log(`
|
|
39
|
+
quickjs-zig - Native Binary Compiler for QuickJS
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
npx quickjs-zig <command> [options]
|
|
43
|
+
|
|
44
|
+
Commands:
|
|
45
|
+
build Compiles your project into native binaries based on package.json
|
|
46
|
+
help Shows this help message
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
npx quickjs-zig build
|
|
50
|
+
`);
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
default:
|
|
54
|
+
console.log(`❌ Unknown command: "${command}"`);
|
|
55
|
+
console.log("Type 'npx quickjs-zig help' for usage.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run();
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quickjs-zig",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A generic build engine to compile QuickJS scripts into standalone native binaries using Zig for multi-platform cross-compilation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"quickjs-zig": "index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "node postinstall.mjs"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"quickjs",
|
|
18
|
+
"zig",
|
|
19
|
+
"compiler",
|
|
20
|
+
"native-binaries",
|
|
21
|
+
"cross-compilation",
|
|
22
|
+
"c-modules"
|
|
23
|
+
],
|
|
24
|
+
"author": "eid-app",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/eid-app/quickjs-zig.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/eid-app/quickjs-zig/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/eid-app/quickjs-zig#readme"
|
|
34
|
+
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { mkdirSync, existsSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import https from 'https';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
// --- CONFIGURATION ---
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const ZIG_VERSION = '0.15.2';
|
|
11
|
+
const BIN_DIR = path.resolve(__dirname, 'bin', 'zig');
|
|
12
|
+
|
|
13
|
+
// Mapping for Zig download URLs
|
|
14
|
+
const platformMap = {
|
|
15
|
+
win32: 'windows',
|
|
16
|
+
darwin: 'macos',
|
|
17
|
+
linux: 'linux'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const archMap = {
|
|
21
|
+
x64: 'x86_64',
|
|
22
|
+
arm64: 'aarch64'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const platform = platformMap[os.platform()];
|
|
26
|
+
const arch = archMap[os.arch()];
|
|
27
|
+
|
|
28
|
+
if (!platform || !arch) {
|
|
29
|
+
console.error(`❌ Unsupported platform/arch: ${os.platform()} ${os.arch()}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fixed: Correct Zig URL order is arch-platform (e.g., x86_64-macos)
|
|
34
|
+
const zigTarget = `${arch}-${platform}`;
|
|
35
|
+
const zigFileName = `zig-${zigTarget}-${ZIG_VERSION}.${platform === 'windows' ? 'zip' : 'tar.xz'}`;
|
|
36
|
+
const zigUrl = `https://ziglang.org/download/${ZIG_VERSION}/${zigFileName}`;
|
|
37
|
+
|
|
38
|
+
const ZIG_BIN_PATH = path.join(BIN_DIR, platform === 'windows' ? 'zig.exe' : 'zig');
|
|
39
|
+
|
|
40
|
+
// --- DOWNLOAD HELPER ---
|
|
41
|
+
const downloadFile = (url, dest) => {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
https.get(url, (res) => {
|
|
44
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
45
|
+
return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
|
|
46
|
+
}
|
|
47
|
+
if (res.statusCode !== 200) {
|
|
48
|
+
reject(new Error(`Failed to download: ${res.statusCode}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const chunks = [];
|
|
52
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
53
|
+
res.on('end', () => {
|
|
54
|
+
writeFileSync(dest, Buffer.concat(chunks));
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
}).on('error', reject);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// --- MAIN EXECUTION ---
|
|
62
|
+
async function install() {
|
|
63
|
+
console.log("=== POSTINSTALL: INITIALIZING PROJECT DEPENDENCIES ===");
|
|
64
|
+
|
|
65
|
+
// 1. Initialize Git Submodules (QuickJS source)
|
|
66
|
+
try {
|
|
67
|
+
console.log("📂 Initializing git submodules...");
|
|
68
|
+
execSync('git submodule update --init --recursive', { stdio: 'inherit' });
|
|
69
|
+
console.log("✅ Submodules initialized.");
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn("⚠️ Git submodule update failed. Ensure you are in a git repository or have QuickJS sources.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Install Zig Compiler
|
|
75
|
+
if (existsSync(ZIG_BIN_PATH)) {
|
|
76
|
+
console.log(`✅ Zig ${ZIG_VERSION} already installed.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!existsSync(BIN_DIR)) mkdirSync(BIN_DIR, { recursive: true });
|
|
81
|
+
|
|
82
|
+
console.log(`📥 Downloading Zig ${ZIG_VERSION} for ${zigTarget}...`);
|
|
83
|
+
const archivePath = path.join(BIN_DIR, zigFileName);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await downloadFile(zigUrl, archivePath);
|
|
87
|
+
console.log(`📦 Extracting ${zigFileName}...`);
|
|
88
|
+
|
|
89
|
+
if (platform === 'windows') {
|
|
90
|
+
// Extraction using PowerShell for Windows
|
|
91
|
+
execSync(`powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${BIN_DIR}' -Force"`);
|
|
92
|
+
// Zig extracts into a subfolder named 'zig-windows-x86_64-0.15.2', let's flatten it
|
|
93
|
+
const subDir = path.join(BIN_DIR, `zig-${zigTarget}-${ZIG_VERSION}`);
|
|
94
|
+
if (existsSync(subDir)) {
|
|
95
|
+
execSync(`powershell -command "Move-Item -Path '${subDir}\\*' -Destination '${BIN_DIR}' -Force"`);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Extraction for Unix with strip-components to avoid nested folder
|
|
99
|
+
execSync(`tar -xJf "${archivePath}" -C "${BIN_DIR}" --strip-components=1`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Cleanup
|
|
103
|
+
if (existsSync(archivePath)) unlinkSync(archivePath);
|
|
104
|
+
|
|
105
|
+
if (platform !== 'windows') {
|
|
106
|
+
chmodSync(ZIG_BIN_PATH, 0o755);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`🚀 Zig ${ZIG_VERSION} installed successfully at ${ZIG_BIN_PATH}`);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`❌ Installation failed: ${err.message}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
install();
|
package/src/exec.c
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#if defined(_WIN32)
|
|
2
|
+
#include <fcntl.h>
|
|
3
|
+
#include <io.h>
|
|
4
|
+
|
|
5
|
+
/* Helper to build the environment block for Windows */
|
|
6
|
+
static char **build_envp(JSContext *ctx, JSValueConst obj)
|
|
7
|
+
{
|
|
8
|
+
uint32_t len, i;
|
|
9
|
+
JSPropertyEnum *tab;
|
|
10
|
+
char **envp, *pair;
|
|
11
|
+
const char *key, *str;
|
|
12
|
+
JSValue val;
|
|
13
|
+
size_t key_len, str_len;
|
|
14
|
+
|
|
15
|
+
if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj,
|
|
16
|
+
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0)
|
|
17
|
+
return NULL;
|
|
18
|
+
|
|
19
|
+
envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1));
|
|
20
|
+
if (!envp)
|
|
21
|
+
goto fail;
|
|
22
|
+
|
|
23
|
+
for(i = 0; i < len; i++) {
|
|
24
|
+
val = JS_GetProperty(ctx, obj, tab[i].atom);
|
|
25
|
+
if (JS_IsException(val))
|
|
26
|
+
goto fail;
|
|
27
|
+
str = JS_ToCString(ctx, val);
|
|
28
|
+
JS_FreeValue(ctx, val); // OK: 2 arguments
|
|
29
|
+
if (!str)
|
|
30
|
+
goto fail;
|
|
31
|
+
|
|
32
|
+
key = JS_AtomToCString(ctx, tab[i].atom);
|
|
33
|
+
if (!key) {
|
|
34
|
+
JS_FreeCString(ctx, str);
|
|
35
|
+
goto fail;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
key_len = strlen(key);
|
|
39
|
+
str_len = strlen(str);
|
|
40
|
+
pair = js_malloc(ctx, key_len + str_len + 2);
|
|
41
|
+
if (!pair) {
|
|
42
|
+
JS_FreeCString(ctx, key);
|
|
43
|
+
JS_FreeCString(ctx, str);
|
|
44
|
+
goto fail;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
memcpy(pair, key, key_len);
|
|
48
|
+
pair[key_len] = '=';
|
|
49
|
+
memcpy(pair + key_len + 1, str, str_len);
|
|
50
|
+
pair[key_len + 1 + str_len] = '\0';
|
|
51
|
+
envp[i] = pair;
|
|
52
|
+
|
|
53
|
+
JS_FreeCString(ctx, key);
|
|
54
|
+
JS_FreeCString(ctx, str);
|
|
55
|
+
}
|
|
56
|
+
envp[len] = NULL;
|
|
57
|
+
|
|
58
|
+
done:
|
|
59
|
+
JS_FreePropertyEnum(ctx, tab, len);
|
|
60
|
+
return envp;
|
|
61
|
+
fail:
|
|
62
|
+
if (envp) {
|
|
63
|
+
for(i = 0; i < len; i++) {
|
|
64
|
+
if (envp[i]) js_free(ctx, envp[i]);
|
|
65
|
+
}
|
|
66
|
+
js_free(ctx, envp);
|
|
67
|
+
envp = NULL;
|
|
68
|
+
}
|
|
69
|
+
goto done;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Main exec implementation for Windows */
|
|
73
|
+
static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val,
|
|
74
|
+
int argc, JSValueConst *argv)
|
|
75
|
+
{
|
|
76
|
+
JSValueConst options, args = argv[0];
|
|
77
|
+
JSValue val, ret_val = JS_UNDEFINED;
|
|
78
|
+
const char **exec_argv = NULL;
|
|
79
|
+
char **envp = NULL;
|
|
80
|
+
const char *file = NULL;
|
|
81
|
+
uint32_t exec_argc = 0, i;
|
|
82
|
+
int ret, mode;
|
|
83
|
+
BOOL block_flag = TRUE, use_path = TRUE;
|
|
84
|
+
|
|
85
|
+
int custom_stdout_fd = -1;
|
|
86
|
+
int old_stdout = -1;
|
|
87
|
+
|
|
88
|
+
// 1. Arguments length
|
|
89
|
+
val = JS_GetPropertyStr(ctx, args, "length");
|
|
90
|
+
if (JS_IsException(val)) return JS_EXCEPTION;
|
|
91
|
+
ret = JS_ToUint32(ctx, &exec_argc, val);
|
|
92
|
+
JS_FreeValue(ctx, val); // OK: 2 arguments
|
|
93
|
+
if (ret < 0) return JS_EXCEPTION;
|
|
94
|
+
|
|
95
|
+
exec_argv = js_mallocz(ctx, sizeof(char *) * (exec_argc + 1));
|
|
96
|
+
if (!exec_argv) return JS_EXCEPTION;
|
|
97
|
+
|
|
98
|
+
for(i = 0; i < exec_argc; i++) {
|
|
99
|
+
val = JS_GetPropertyUint32(ctx, args, i);
|
|
100
|
+
if (JS_IsException(val)) goto exception;
|
|
101
|
+
exec_argv[i] = JS_ToCString(ctx, val);
|
|
102
|
+
JS_FreeValue(ctx, val); // OK: 2 arguments
|
|
103
|
+
if (!exec_argv[i]) goto exception;
|
|
104
|
+
}
|
|
105
|
+
exec_argv[exec_argc] = NULL;
|
|
106
|
+
|
|
107
|
+
// 2. Options & stdout fileno
|
|
108
|
+
if (argc >= 2) {
|
|
109
|
+
options = argv[1];
|
|
110
|
+
get_bool_option(ctx, &block_flag, options, "block");
|
|
111
|
+
get_bool_option(ctx, &use_path, options, "usePath");
|
|
112
|
+
|
|
113
|
+
val = JS_GetPropertyStr(ctx, options, "stdout");
|
|
114
|
+
if (!JS_IsUndefined(val) && !JS_IsNull(val)) {
|
|
115
|
+
JS_ToInt32(ctx, &custom_stdout_fd, val);
|
|
116
|
+
}
|
|
117
|
+
JS_FreeValue(ctx, val); // Était trop peu d'arguments ici !
|
|
118
|
+
|
|
119
|
+
val = JS_GetPropertyStr(ctx, options, "file");
|
|
120
|
+
if (!JS_IsUndefined(val) && !JS_IsNull(val)) {
|
|
121
|
+
file = JS_ToCString(ctx, val);
|
|
122
|
+
}
|
|
123
|
+
JS_FreeValue(ctx, val); // Était trop peu d'arguments ici !
|
|
124
|
+
|
|
125
|
+
val = JS_GetPropertyStr(ctx, options, "env");
|
|
126
|
+
if (!JS_IsUndefined(val) && !JS_IsNull(val)) {
|
|
127
|
+
envp = build_envp(ctx, val);
|
|
128
|
+
}
|
|
129
|
+
JS_FreeValue(ctx, val); // OK: 2 arguments
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (envp == NULL) envp = (char **)_environ;
|
|
133
|
+
const char *spawn_file = file ? file : exec_argv[0];
|
|
134
|
+
|
|
135
|
+
// 3. Redirection logic
|
|
136
|
+
if (custom_stdout_fd != -1) {
|
|
137
|
+
_flushall();
|
|
138
|
+
old_stdout = _dup(_fileno(stdout));
|
|
139
|
+
if (_dup2(custom_stdout_fd, _fileno(stdout)) == -1) {
|
|
140
|
+
_close(old_stdout);
|
|
141
|
+
old_stdout = -1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
mode = block_flag ? _P_WAIT : _P_NOWAIT;
|
|
146
|
+
_flushall();
|
|
147
|
+
|
|
148
|
+
if (use_path)
|
|
149
|
+
ret = _spawnvpe(mode, spawn_file, (const char * const *)exec_argv, (const char * const *)envp);
|
|
150
|
+
else
|
|
151
|
+
ret = _spawnve(mode, spawn_file, (const char * const *)exec_argv, (const char * const *)envp);
|
|
152
|
+
|
|
153
|
+
if (old_stdout != -1) {
|
|
154
|
+
_flushall();
|
|
155
|
+
_dup2(old_stdout, _fileno(stdout));
|
|
156
|
+
_close(old_stdout);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (ret == -1) {
|
|
160
|
+
JS_ThrowTypeError(ctx, "exec error (spawn failed)");
|
|
161
|
+
goto exception;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
ret_val = JS_NewInt32(ctx, ret);
|
|
165
|
+
|
|
166
|
+
done:
|
|
167
|
+
if (file) JS_FreeCString(ctx, file);
|
|
168
|
+
if (exec_argv) {
|
|
169
|
+
for(i = 0; i < exec_argc; i++) if (exec_argv[i]) JS_FreeCString(ctx, exec_argv[i]);
|
|
170
|
+
js_free(ctx, exec_argv);
|
|
171
|
+
}
|
|
172
|
+
if (envp && envp != (char **)_environ) {
|
|
173
|
+
char **p = envp;
|
|
174
|
+
while (*p != NULL) { js_free(ctx, *p); p++; }
|
|
175
|
+
js_free(ctx, envp);
|
|
176
|
+
}
|
|
177
|
+
return ret_val;
|
|
178
|
+
|
|
179
|
+
exception:
|
|
180
|
+
ret_val = JS_EXCEPTION;
|
|
181
|
+
goto done;
|
|
182
|
+
}
|
|
183
|
+
#endif
|