@chikhamx/voidx 1.0.1 → 1.1.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/bin/postinstall.js +292 -37
- package/bin/voidx.js +91 -48
- package/package.json +10 -1
package/bin/postinstall.js
CHANGED
|
@@ -1,64 +1,319 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Post-install: download a standalone Python, create venv, pip install voidx.
|
|
5
|
+
// Falls back to system Python if download fails.
|
|
6
6
|
|
|
7
|
+
const { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
7
8
|
const { spawnSync } = require("child_process");
|
|
9
|
+
const { get, request } = require("https");
|
|
10
|
+
const { createGunzip } = require("zlib");
|
|
11
|
+
const { pipeline } = require("stream/promises");
|
|
12
|
+
const { createUnzip } = require("zlib");
|
|
13
|
+
const { join, dirname } = require("path");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const { execSync } = require("child_process");
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
const pkg = require("../package.json");
|
|
20
|
+
|
|
21
|
+
// ── Python build metadata ──────────────────────────────────────────────────
|
|
22
|
+
// Pin to a specific python-build-standalone release tag and CPython version.
|
|
23
|
+
// Update these when upgrading the bundled Python.
|
|
24
|
+
const PBS_TAG = "20260602";
|
|
25
|
+
const PBS_CPYTHON = "3.12.13";
|
|
26
|
+
const PBS_RELEASE_BASE = `https://github.com/astral-sh/python-build-standalone/releases/download/${PBS_TAG}`;
|
|
27
|
+
|
|
28
|
+
// ── Platform mapping ───────────────────────────────────────────────────────
|
|
29
|
+
function getPlatformInfo() {
|
|
30
|
+
const platform = os.platform();
|
|
31
|
+
const arch = os.arch();
|
|
32
|
+
|
|
33
|
+
const map = {
|
|
34
|
+
"darwin-arm64": { target: "aarch64-apple-darwin" },
|
|
35
|
+
"darwin-x64": { target: "x86_64-apple-darwin" },
|
|
36
|
+
"linux-arm64": { target: "aarch64-unknown-linux-gnu" },
|
|
37
|
+
"linux-x64": { target: "x86_64-unknown-linux-gnu" },
|
|
38
|
+
"win32-arm64": { target: "aarch64-pc-windows-msvc" },
|
|
39
|
+
"win32-x64": { target: "x86_64-pc-windows-msvc" },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const key = `${platform}-${arch}`;
|
|
43
|
+
const entry = map[key];
|
|
44
|
+
if (!entry) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return { platform, arch, target: entry.target };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getPbsFilename(target) {
|
|
51
|
+
return `cpython-${PBS_CPYTHON}+${PBS_TAG}-${target}-install_only_stripped.tar.gz`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
55
|
+
function resolveDataHome(env) {
|
|
56
|
+
if (env.VOIDX_NPM_HOME) return path.resolve(env.VOIDX_NPM_HOME);
|
|
57
|
+
if (process.platform === "win32") {
|
|
58
|
+
return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
59
|
+
}
|
|
60
|
+
return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolvePythonDir(env) {
|
|
64
|
+
return path.join(resolveDataHome(env), "voidx", "python");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveVenvDir(env) {
|
|
68
|
+
if (env.VOIDX_NPM_VENV) return path.resolve(env.VOIDX_NPM_VENV);
|
|
69
|
+
return path.join(resolveDataHome(env), "voidx", "npm-venv");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveVenvPython(venvDir) {
|
|
73
|
+
return process.platform === "win32"
|
|
74
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
75
|
+
: path.join(venvDir, "bin", "python");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveVoidxExecutable(venvDir) {
|
|
79
|
+
return process.platform === "win32"
|
|
80
|
+
? path.join(venvDir, "Scripts", "voidx.exe")
|
|
81
|
+
: path.join(venvDir, "bin", "voidx");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolveBundledPython(pythonDir, platform) {
|
|
85
|
+
// install_only_stripped layout:
|
|
86
|
+
// unix: python/python/bin/python3
|
|
87
|
+
// win32: python/python/python.exe
|
|
88
|
+
const installDir = path.join(pythonDir, "python");
|
|
89
|
+
if (platform === "win32") {
|
|
90
|
+
return path.join(installDir, "python.exe");
|
|
91
|
+
}
|
|
92
|
+
return path.join(installDir, "bin", "python3");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Download ───────────────────────────────────────────────────────────────
|
|
96
|
+
function downloadFile(url, dest) {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const doRequest = (currentUrl, redirects = 0) => {
|
|
99
|
+
if (redirects > 5) {
|
|
100
|
+
return reject(new Error(`Too many redirects downloading ${url}`));
|
|
101
|
+
}
|
|
102
|
+
const mod = currentUrl.startsWith("https") ? require("https") : require("http");
|
|
103
|
+
mod.get(currentUrl, { timeout: 60000 }, (res) => {
|
|
104
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
105
|
+
return doRequest(res.headers.location, redirects + 1);
|
|
106
|
+
}
|
|
107
|
+
if (res.statusCode !== 200) {
|
|
108
|
+
return reject(new Error(`HTTP ${res.statusCode} downloading ${currentUrl}`));
|
|
109
|
+
}
|
|
110
|
+
const total = parseInt(res.headers["content-length"] || "0", 10);
|
|
111
|
+
let downloaded = 0;
|
|
112
|
+
let lastPercent = -1;
|
|
113
|
+
|
|
114
|
+
res.on("data", (chunk) => {
|
|
115
|
+
downloaded += chunk.length;
|
|
116
|
+
if (total > 0) {
|
|
117
|
+
const pct = Math.floor((downloaded / total) * 100);
|
|
118
|
+
if (pct !== lastPercent && pct % 10 === 0) {
|
|
119
|
+
lastPercent = pct;
|
|
120
|
+
process.stderr.write(` ${pct}% (${Math.round(downloaded / 1024 / 1024)}/${Math.round(total / 1024 / 1024)} MB)\n`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const stream = createWriteStream(dest);
|
|
126
|
+
res.pipe(stream);
|
|
127
|
+
stream.on("finish", () => {
|
|
128
|
+
stream.close();
|
|
129
|
+
resolve(dest);
|
|
130
|
+
});
|
|
131
|
+
stream.on("error", reject);
|
|
132
|
+
res.on("error", reject);
|
|
133
|
+
}).on("error", reject);
|
|
134
|
+
};
|
|
135
|
+
doRequest(url);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Extract ────────────────────────────────────────────────────────────────
|
|
140
|
+
function extractTarGz(archive, destDir) {
|
|
141
|
+
// Use the system tar command — available on macOS, Linux, and modern Windows (10+)
|
|
142
|
+
const result = spawnSync("tar", ["-xzf", archive, "-C", destDir], {
|
|
143
|
+
encoding: "utf8",
|
|
144
|
+
windowsHide: true,
|
|
145
|
+
});
|
|
146
|
+
if (result.error) {
|
|
147
|
+
throw new Error(`Failed to extract Python: ${result.error.message}`);
|
|
14
148
|
}
|
|
149
|
+
if (result.status !== 0) {
|
|
150
|
+
throw new Error(`Failed to extract Python (tar exited ${result.status}).`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Version marker ─────────────────────────────────────────────────────────
|
|
155
|
+
function readMarker(markerPath) {
|
|
156
|
+
try { return readFileSync(markerPath, "utf8"); } catch { return ""; }
|
|
157
|
+
}
|
|
15
158
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
159
|
+
function writeMarker(markerPath, content) {
|
|
160
|
+
mkdirSync(dirname(markerPath), { recursive: true });
|
|
161
|
+
writeFileSync(markerPath, content);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── pip install ────────────────────────────────────────────────────────────
|
|
165
|
+
function pipInstall(venvPython, packageSpec, env) {
|
|
166
|
+
const pipEnv = Object.assign({}, env, {
|
|
167
|
+
PIP_NO_INPUT: "1",
|
|
168
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
169
|
+
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
170
|
+
});
|
|
171
|
+
const result = spawnSync(
|
|
172
|
+
venvPython,
|
|
173
|
+
["-m", "pip", "install", "--upgrade", "--progress-bar", "on", packageSpec],
|
|
174
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipEnv }
|
|
175
|
+
);
|
|
176
|
+
if (result.error) {
|
|
177
|
+
throw new Error(`pip install failed: ${result.error.message}`);
|
|
178
|
+
}
|
|
179
|
+
if (result.status !== 0) {
|
|
180
|
+
throw new Error("pip install failed. See errors above.");
|
|
181
|
+
}
|
|
19
182
|
}
|
|
20
183
|
|
|
21
|
-
|
|
184
|
+
// ── System Python fallback ─────────────────────────────────────────────────
|
|
185
|
+
function probeSystemPython() {
|
|
22
186
|
const candidates = [
|
|
23
|
-
"python3",
|
|
24
|
-
|
|
187
|
+
{ command: "python3", args: [] },
|
|
188
|
+
{ command: "python", args: [] },
|
|
189
|
+
{ command: "python3.12", args: [] },
|
|
190
|
+
{ command: "python3.11", args: [] },
|
|
25
191
|
];
|
|
26
192
|
if (process.platform === "win32") {
|
|
27
|
-
candidates.push("py");
|
|
193
|
+
candidates.push({ command: "py", args: ["-3"] });
|
|
28
194
|
}
|
|
29
195
|
|
|
30
|
-
for (const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
windowsHide: true
|
|
35
|
-
|
|
196
|
+
for (const candidate of candidates) {
|
|
197
|
+
const result = spawnSync(
|
|
198
|
+
candidate.command,
|
|
199
|
+
[...candidate.args, "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
|
|
200
|
+
{ encoding: "utf8", windowsHide: true }
|
|
201
|
+
);
|
|
36
202
|
if (result.error || result.status !== 0) continue;
|
|
37
|
-
const
|
|
38
|
-
const match = /^(\d+)\.(\d+)
|
|
203
|
+
const ver = (result.stdout || "").trim();
|
|
204
|
+
const match = /^(\d+)\.(\d+)/.exec(ver);
|
|
39
205
|
if (!match) continue;
|
|
40
|
-
const major =
|
|
41
|
-
const minor =
|
|
206
|
+
const major = parseInt(match[1], 10);
|
|
207
|
+
const minor = parseInt(match[2], 10);
|
|
42
208
|
if (major > 3 || (major === 3 && minor >= 11)) {
|
|
43
|
-
return {
|
|
209
|
+
return { command: candidate.command, args: candidate.args };
|
|
44
210
|
}
|
|
45
211
|
}
|
|
46
|
-
return
|
|
212
|
+
return null;
|
|
47
213
|
}
|
|
48
214
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
215
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
216
|
+
async function main() {
|
|
217
|
+
const env = process.env;
|
|
218
|
+
const venvDir = resolveVenvDir(env);
|
|
219
|
+
const venvPython = resolveVenvPython(venvDir);
|
|
220
|
+
const executable = resolveVoidxExecutable(venvDir);
|
|
221
|
+
const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
|
|
222
|
+
const markerPath = path.join(venvDir, ".voidx-npm-version");
|
|
223
|
+
const marker = `${pkg.version}\n${packageSpec}\n${PBS_TAG}\n${PBS_CPYTHON}\n`;
|
|
224
|
+
|
|
225
|
+
// Already set up and up-to-date?
|
|
226
|
+
if (existsSync(executable) && readMarker(markerPath) === marker) {
|
|
227
|
+
console.error(`\n✅ voidx ${pkg.version} ready (cached)\n`);
|
|
228
|
+
return;
|
|
52
229
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
230
|
+
|
|
231
|
+
// Step 1: Get a Python interpreter (bundled or system)
|
|
232
|
+
let pythonForVenv;
|
|
233
|
+
const platformInfo = getPlatformInfo();
|
|
234
|
+
|
|
235
|
+
if (platformInfo && env.VOIDX_NPM_SKIP_BUNDLED_PYTHON !== "1") {
|
|
236
|
+
console.error(`\n🐍 Setting up voidx ${pkg.version}…\n`);
|
|
237
|
+
console.error(" [1/3] Downloading Python runtime…");
|
|
238
|
+
|
|
239
|
+
const pythonDir = resolvePythonDir(env);
|
|
240
|
+
const pbsFilename = getPbsFilename(platformInfo.target);
|
|
241
|
+
const pbsUrl = `${PBS_RELEASE_BASE}/${pbsFilename}`;
|
|
242
|
+
const archivePath = path.join(pythonDir, pbsFilename);
|
|
243
|
+
const bundledPython = resolveBundledPython(pythonDir, platformInfo.platform);
|
|
244
|
+
|
|
245
|
+
if (!existsSync(bundledPython)) {
|
|
246
|
+
try {
|
|
247
|
+
mkdirSync(pythonDir, { recursive: true });
|
|
248
|
+
|
|
249
|
+
if (!existsSync(archivePath)) {
|
|
250
|
+
console.error(` Downloading ${pbsFilename}…`);
|
|
251
|
+
await downloadFile(pbsUrl, archivePath);
|
|
252
|
+
console.error(" Download complete.");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.error(" Extracting Python runtime…");
|
|
256
|
+
extractTarGz(archivePath, pythonDir);
|
|
257
|
+
console.error(" Extraction complete.");
|
|
258
|
+
|
|
259
|
+
// Clean up archive to save disk
|
|
260
|
+
try { fs.unlinkSync(archivePath); } catch {}
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.error(`\n ⚠️ Bundled Python download failed: ${err.message}`);
|
|
263
|
+
console.error(" Falling back to system Python…\n");
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
console.error(" Using cached Python runtime.");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (existsSync(bundledPython)) {
|
|
270
|
+
pythonForVenv = { command: bundledPython, args: [], label: "bundled" };
|
|
271
|
+
}
|
|
57
272
|
}
|
|
58
|
-
|
|
59
|
-
|
|
273
|
+
|
|
274
|
+
// Fallback to system Python
|
|
275
|
+
if (!pythonForVenv) {
|
|
276
|
+
const sysPython = probeSystemPython();
|
|
277
|
+
if (sysPython) {
|
|
278
|
+
pythonForVenv = sysPython;
|
|
279
|
+
console.error("\n Using system Python.\n");
|
|
280
|
+
} else {
|
|
281
|
+
console.error("\n ❌ No Python 3.11+ found. Please install Python 3.11+ and run voidx again.\n");
|
|
282
|
+
console.error(" macOS: brew install python@3.12");
|
|
283
|
+
console.error(" Linux: sudo apt install python3.12");
|
|
284
|
+
console.error(" Windows: https://python.org/downloads\n");
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
60
287
|
}
|
|
61
|
-
|
|
288
|
+
|
|
289
|
+
// Step 2: Create venv
|
|
290
|
+
console.error(" [2/3] Creating virtual environment…");
|
|
291
|
+
if (!existsSync(venvPython)) {
|
|
292
|
+
const venvResult = spawnSync(
|
|
293
|
+
pythonForVenv.command,
|
|
294
|
+
[...pythonForVenv.args, "-m", "venv", venvDir],
|
|
295
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true }
|
|
296
|
+
);
|
|
297
|
+
if (venvResult.error) {
|
|
298
|
+
console.error(`\n ❌ Failed to create venv: ${venvResult.error.message}\n`);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
if (venvResult.status !== 0) {
|
|
302
|
+
console.error("\n ❌ Failed to create venv. See errors above.\n");
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Step 3: pip install
|
|
308
|
+
console.error(" [3/3] Installing voidx and dependencies…");
|
|
309
|
+
pipInstall(venvPython, packageSpec, env);
|
|
310
|
+
|
|
311
|
+
// Done
|
|
312
|
+
writeMarker(markerPath, marker);
|
|
313
|
+
console.error(`\n✅ voidx ${pkg.version} installed! Run: voidx\n`);
|
|
62
314
|
}
|
|
63
315
|
|
|
64
|
-
main()
|
|
316
|
+
main().catch((err) => {
|
|
317
|
+
console.error(`\n❌ Setup failed: ${err.message}\n`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
});
|
package/bin/voidx.js
CHANGED
|
@@ -8,6 +8,13 @@ const path = require("path");
|
|
|
8
8
|
|
|
9
9
|
const pkg = require("../package.json");
|
|
10
10
|
|
|
11
|
+
// ── Configuration ──────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const PBS_TAG = "20260602";
|
|
14
|
+
const PBS_PYTHON_MAJOR = "3.12";
|
|
15
|
+
|
|
16
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
11
18
|
function main(argv = process.argv.slice(2), env = process.env) {
|
|
12
19
|
try {
|
|
13
20
|
const python = selectPython(env);
|
|
@@ -23,14 +30,17 @@ function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
23
30
|
process.exit(code === null ? 1 : code);
|
|
24
31
|
});
|
|
25
32
|
child.on("error", (error) => {
|
|
26
|
-
fail(`Failed to start voidx
|
|
33
|
+
fail(`Failed to start voidx: ${error.message}`);
|
|
27
34
|
});
|
|
28
35
|
} catch (error) {
|
|
29
36
|
fail(error.message);
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
39
|
|
|
40
|
+
// ── Python selection ───────────────────────────────────────────────────────
|
|
41
|
+
|
|
33
42
|
function selectPython(env) {
|
|
43
|
+
// 1. Explicit override
|
|
34
44
|
const explicit = env.VOIDX_PYTHON;
|
|
35
45
|
if (explicit) {
|
|
36
46
|
const candidate = { command: explicit, args: [], label: explicit };
|
|
@@ -46,6 +56,17 @@ function selectPython(env) {
|
|
|
46
56
|
return candidate;
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
// 2. Bundled Python (downloaded by postinstall)
|
|
60
|
+
const bundledBin = resolveBundledPythonBin(env);
|
|
61
|
+
if (bundledBin && fs.existsSync(bundledBin)) {
|
|
62
|
+
const candidate = { command: bundledBin, args: [], label: "bundled" };
|
|
63
|
+
const probe = probePython(candidate);
|
|
64
|
+
if (probe.ok && isCompatible(probe.version)) {
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3. System Python
|
|
49
70
|
const candidates = [
|
|
50
71
|
{ command: "python3", args: [], label: "python3" },
|
|
51
72
|
{ command: "python", args: [], label: "python" },
|
|
@@ -54,7 +75,7 @@ function selectPython(env) {
|
|
|
54
75
|
{ command: "python3.11", args: [], label: "python3.11" },
|
|
55
76
|
];
|
|
56
77
|
if (process.platform === "win32") {
|
|
57
|
-
candidates.push({ command: "py", args: ["-3
|
|
78
|
+
candidates.push({ command: "py", args: ["-3"], label: "py -3" });
|
|
58
79
|
}
|
|
59
80
|
|
|
60
81
|
const oldVersions = [];
|
|
@@ -76,26 +97,10 @@ function selectPython(env) {
|
|
|
76
97
|
);
|
|
77
98
|
}
|
|
78
99
|
throw new Error(
|
|
79
|
-
`voidx
|
|
100
|
+
`voidx requires Python 3.11+. No Python found.\n${hint}`
|
|
80
101
|
);
|
|
81
102
|
}
|
|
82
103
|
|
|
83
|
-
function pythonHint() {
|
|
84
|
-
if (process.platform === "darwin") {
|
|
85
|
-
return "Install Python 3.11+ via: brew install python@3.12\n" +
|
|
86
|
-
"Or point to an existing install: VOIDX_PYTHON=/path/to/python3 voidx";
|
|
87
|
-
}
|
|
88
|
-
if (process.platform === "linux") {
|
|
89
|
-
return "Install Python 3.11+ via your package manager (apt/dnf).\n" +
|
|
90
|
-
"Or point to an existing install: VOIDX_PYTHON=/path/to/python3 voidx";
|
|
91
|
-
}
|
|
92
|
-
if (process.platform === "win32") {
|
|
93
|
-
return "Install Python 3.11+ from https://python.org/downloads\n" +
|
|
94
|
-
"Or: VOIDX_PYTHON=C:\\Python312\\python.exe voidx";
|
|
95
|
-
}
|
|
96
|
-
return "Install Python 3.11+ or set VOIDX_PYTHON.";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
104
|
function probePython(candidate) {
|
|
100
105
|
const code = [
|
|
101
106
|
"import sys",
|
|
@@ -131,6 +136,69 @@ function isCompatible(version) {
|
|
|
131
136
|
return version[0] > 3 || (version[0] === 3 && version[1] >= 11);
|
|
132
137
|
}
|
|
133
138
|
|
|
139
|
+
function pythonHint() {
|
|
140
|
+
if (process.platform === "darwin") {
|
|
141
|
+
return "Install Python 3.11+ via: brew install python@3.12\n" +
|
|
142
|
+
"Or reinstall voidx to get the bundled Python.";
|
|
143
|
+
}
|
|
144
|
+
if (process.platform === "linux") {
|
|
145
|
+
return "Install Python 3.11+ via your package manager (apt/dnf).\n" +
|
|
146
|
+
"Or reinstall voidx to get the bundled Python.";
|
|
147
|
+
}
|
|
148
|
+
if (process.platform === "win32") {
|
|
149
|
+
return "Install Python 3.11+ from https://python.org/downloads\n" +
|
|
150
|
+
"Or reinstall voidx to get the bundled Python.";
|
|
151
|
+
}
|
|
152
|
+
return "Install Python 3.11+ or reinstall voidx.";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
function resolveDataHome(env) {
|
|
158
|
+
if (env.VOIDX_NPM_HOME) {
|
|
159
|
+
return path.resolve(env.VOIDX_NPM_HOME);
|
|
160
|
+
}
|
|
161
|
+
if (process.platform === "win32") {
|
|
162
|
+
return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
163
|
+
}
|
|
164
|
+
return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function resolvePythonDir(env) {
|
|
168
|
+
if (env.VOIDX_NPM_PYTHON_DIR) {
|
|
169
|
+
return path.resolve(env.VOIDX_NPM_PYTHON_DIR);
|
|
170
|
+
}
|
|
171
|
+
return path.join(resolveDataHome(env), "voidx", "python");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function resolveBundledPythonBin(env) {
|
|
175
|
+
const pythonDir = resolvePythonDir(env);
|
|
176
|
+
return process.platform === "win32"
|
|
177
|
+
? path.join(pythonDir, "python", "python.exe")
|
|
178
|
+
: path.join(pythonDir, "python", "bin", `python${PBS_PYTHON_MAJOR}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveVenvDir(env) {
|
|
182
|
+
if (env.VOIDX_NPM_VENV) {
|
|
183
|
+
return path.resolve(env.VOIDX_NPM_VENV);
|
|
184
|
+
}
|
|
185
|
+
return path.join(resolveDataHome(env), "voidx", "npm-venv");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resolveVenvPython(venvDir) {
|
|
189
|
+
return process.platform === "win32"
|
|
190
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
191
|
+
: path.join(venvDir, "bin", "python");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveVoidxExecutable(venvDir) {
|
|
195
|
+
return process.platform === "win32"
|
|
196
|
+
? path.join(venvDir, "Scripts", "voidx.exe")
|
|
197
|
+
: path.join(venvDir, "bin", "voidx");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Venv setup ─────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
134
202
|
function ensureVenv(python, venvDir, env) {
|
|
135
203
|
const executable = resolveVoidxExecutable(venvDir);
|
|
136
204
|
if (env.VOIDX_NPM_SKIP_BOOTSTRAP === "1") {
|
|
@@ -142,7 +210,8 @@ function ensureVenv(python, venvDir, env) {
|
|
|
142
210
|
|
|
143
211
|
const markerPath = path.join(venvDir, ".voidx-npm-version");
|
|
144
212
|
const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
|
|
145
|
-
|
|
213
|
+
// Marker must match postinstall.js format (includes PBS_TAG + PBS_CPYTHON)
|
|
214
|
+
const marker = `${pkg.version}\n${packageSpec}\n${PBS_TAG}\n3.12.13\n`;
|
|
146
215
|
if (fs.existsSync(executable) && readFile(markerPath) === marker) {
|
|
147
216
|
debug(env, `Using cached npm-managed environment at ${venvDir}`);
|
|
148
217
|
return;
|
|
@@ -209,34 +278,7 @@ function ensureVenv(python, venvDir, env) {
|
|
|
209
278
|
fs.writeFileSync(markerPath, marker);
|
|
210
279
|
}
|
|
211
280
|
|
|
212
|
-
|
|
213
|
-
if (env.VOIDX_NPM_VENV) {
|
|
214
|
-
return path.resolve(env.VOIDX_NPM_VENV);
|
|
215
|
-
}
|
|
216
|
-
return path.join(resolveDataHome(env), "voidx", "npm-venv");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function resolveDataHome(env) {
|
|
220
|
-
if (env.VOIDX_NPM_HOME) {
|
|
221
|
-
return path.resolve(env.VOIDX_NPM_HOME);
|
|
222
|
-
}
|
|
223
|
-
if (process.platform === "win32") {
|
|
224
|
-
return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
225
|
-
}
|
|
226
|
-
return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function resolveVenvPython(venvDir) {
|
|
230
|
-
return process.platform === "win32"
|
|
231
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
232
|
-
: path.join(venvDir, "bin", "python");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function resolveVoidxExecutable(venvDir) {
|
|
236
|
-
return process.platform === "win32"
|
|
237
|
-
? path.join(venvDir, "Scripts", "voidx.exe")
|
|
238
|
-
: path.join(venvDir, "bin", "voidx");
|
|
239
|
-
}
|
|
281
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
240
282
|
|
|
241
283
|
function readFile(filePath) {
|
|
242
284
|
try {
|
|
@@ -266,5 +308,6 @@ module.exports = {
|
|
|
266
308
|
parseVersion,
|
|
267
309
|
resolveDataHome,
|
|
268
310
|
resolveVenvDir,
|
|
311
|
+
resolveBundledPythonBin,
|
|
269
312
|
selectPython,
|
|
270
313
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chikhamx/voidx",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "npm launcher for voidx, a terminal AI coding agent.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"voidx": "bin/voidx.js"
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=16"
|
|
13
13
|
},
|
|
14
|
+
"os": [
|
|
15
|
+
"darwin",
|
|
16
|
+
"linux",
|
|
17
|
+
"win32"
|
|
18
|
+
],
|
|
19
|
+
"cpu": [
|
|
20
|
+
"x64",
|
|
21
|
+
"arm64"
|
|
22
|
+
],
|
|
14
23
|
"keywords": [
|
|
15
24
|
"ai",
|
|
16
25
|
"agent",
|