@chikhamx/voidx 3.1.2 → 3.2.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/bin/postinstall.js +431 -431
- package/bin/voidx.js +393 -391
- package/package.json +33 -33
package/bin/voidx.js
CHANGED
|
@@ -1,391 +1,393 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const { spawn, spawnSync } = require("child_process");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const os = require("os");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
|
|
9
|
-
const pkg = require("../package.json");
|
|
10
|
-
|
|
11
|
-
// ── Configuration ──────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
const PBS_TAG = "20260602";
|
|
14
|
-
const PBS_CPYTHON = "3.12.13";
|
|
15
|
-
const PBS_PYTHON_MAJOR = "3.12";
|
|
16
|
-
|
|
17
|
-
// ── Main ───────────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
function main(argv = process.argv.slice(2), env = process.env) {
|
|
20
|
-
try {
|
|
21
|
-
const python = selectPython(env);
|
|
22
|
-
const venvDir = resolveVenvDir(env);
|
|
23
|
-
ensureVenv(python, venvDir, env);
|
|
24
|
-
const executable = resolveVoidxExecutable(venvDir);
|
|
25
|
-
const childEnv = {
|
|
26
|
-
...env,
|
|
27
|
-
VOIDX_LAUNCHED_BY_NPM: "1",
|
|
28
|
-
VOIDX_NPM_PACKAGE_VERSION: pkg.version,
|
|
29
|
-
};
|
|
30
|
-
const child = spawn(executable, argv, { stdio: "inherit", env: childEnv });
|
|
31
|
-
child.on("exit", (code, signal) => {
|
|
32
|
-
if (signal) {
|
|
33
|
-
process.kill(process.pid, signal);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
process.exit(code === null ? 1 : code);
|
|
37
|
-
});
|
|
38
|
-
child.on("error", (error) => {
|
|
39
|
-
fail(`Failed to start voidx: ${error.message}`);
|
|
40
|
-
});
|
|
41
|
-
} catch (error) {
|
|
42
|
-
fail(error.message);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── Python selection ───────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
function selectPython(env) {
|
|
49
|
-
// 1. Explicit override (for advanced users / debugging)
|
|
50
|
-
const explicit = env.VOIDX_PYTHON;
|
|
51
|
-
if (explicit) {
|
|
52
|
-
const candidate = { command: explicit, args: [], label: explicit };
|
|
53
|
-
const probe = probePython(candidate);
|
|
54
|
-
if (!probe.ok) {
|
|
55
|
-
throw new Error(probe.reason || `Unable to run Python at ${explicit}.`);
|
|
56
|
-
}
|
|
57
|
-
if (!isCompatible(probe.version)) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`voidx requires Python 3.11+. Found Python ${probe.versionText} at ${explicit}.`
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
return candidate;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 2. Bundled Python only — voidx runs in its own isolated environment
|
|
66
|
-
const bundledBin = resolveBundledPythonBin(env);
|
|
67
|
-
if (bundledBin && fs.existsSync(bundledBin)) {
|
|
68
|
-
const candidate = { command: bundledBin, args: [], label: "bundled" };
|
|
69
|
-
const probe = probePython(candidate);
|
|
70
|
-
if (probe.ok && isCompatible(probe.version)) {
|
|
71
|
-
return candidate;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 3. Bundled Python not found — try to bootstrap it (postinstall may have failed)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return "";
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn, spawnSync } = require("child_process");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
const pkg = require("../package.json");
|
|
10
|
+
|
|
11
|
+
// ── Configuration ──────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const PBS_TAG = "20260602";
|
|
14
|
+
const PBS_CPYTHON = "3.12.13";
|
|
15
|
+
const PBS_PYTHON_MAJOR = "3.12";
|
|
16
|
+
|
|
17
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function main(argv = process.argv.slice(2), env = process.env) {
|
|
20
|
+
try {
|
|
21
|
+
const python = selectPython(env);
|
|
22
|
+
const venvDir = resolveVenvDir(env);
|
|
23
|
+
ensureVenv(python, venvDir, env);
|
|
24
|
+
const executable = resolveVoidxExecutable(venvDir);
|
|
25
|
+
const childEnv = {
|
|
26
|
+
...env,
|
|
27
|
+
VOIDX_LAUNCHED_BY_NPM: "1",
|
|
28
|
+
VOIDX_NPM_PACKAGE_VERSION: pkg.version,
|
|
29
|
+
};
|
|
30
|
+
const child = spawn(executable, argv, { stdio: "inherit", env: childEnv });
|
|
31
|
+
child.on("exit", (code, signal) => {
|
|
32
|
+
if (signal) {
|
|
33
|
+
process.kill(process.pid, signal);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
process.exit(code === null ? 1 : code);
|
|
37
|
+
});
|
|
38
|
+
child.on("error", (error) => {
|
|
39
|
+
fail(`Failed to start voidx: ${error.message}`);
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
fail(error.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Python selection ───────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function selectPython(env) {
|
|
49
|
+
// 1. Explicit override (for advanced users / debugging)
|
|
50
|
+
const explicit = env.VOIDX_PYTHON;
|
|
51
|
+
if (explicit) {
|
|
52
|
+
const candidate = { command: explicit, args: [], label: explicit };
|
|
53
|
+
const probe = probePython(candidate);
|
|
54
|
+
if (!probe.ok) {
|
|
55
|
+
throw new Error(probe.reason || `Unable to run Python at ${explicit}.`);
|
|
56
|
+
}
|
|
57
|
+
if (!isCompatible(probe.version)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`voidx requires Python 3.11+. Found Python ${probe.versionText} at ${explicit}.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Bundled Python only — voidx runs in its own isolated environment
|
|
66
|
+
const bundledBin = resolveBundledPythonBin(env);
|
|
67
|
+
if (bundledBin && fs.existsSync(bundledBin)) {
|
|
68
|
+
const candidate = { command: bundledBin, args: [], label: "bundled" };
|
|
69
|
+
const probe = probePython(candidate);
|
|
70
|
+
if (probe.ok && isCompatible(probe.version)) {
|
|
71
|
+
return candidate;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. Bundled Python not found — try to bootstrap it (postinstall may have failed)
|
|
76
|
+
if (env.VOIDX_NPM_SKIP_BOOTSTRAP !== "1") {
|
|
77
|
+
console.error("\n⚙️ Bundled Python not found, running setup…\n");
|
|
78
|
+
const postinstallScript = path.join(path.dirname(__filename), "postinstall.js");
|
|
79
|
+
if (fs.existsSync(postinstallScript)) {
|
|
80
|
+
const result = spawnSync(process.execPath, [postinstallScript], {
|
|
81
|
+
stdio: "inherit",
|
|
82
|
+
windowsHide: true,
|
|
83
|
+
env: { ...env },
|
|
84
|
+
});
|
|
85
|
+
if (result.status !== 0) {
|
|
86
|
+
console.error(" Setup failed. Try reinstalling:");
|
|
87
|
+
console.error(" npm install -g @chikhamx/voidx");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Retry after bootstrap
|
|
93
|
+
if (bundledBin && fs.existsSync(bundledBin)) {
|
|
94
|
+
const candidate = { command: bundledBin, args: [], label: "bundled" };
|
|
95
|
+
const probe = probePython(candidate);
|
|
96
|
+
if (probe.ok && isCompatible(probe.version)) {
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(
|
|
102
|
+
"voidx bundled Python not found. Reinstall to set up the isolated runtime:\n" +
|
|
103
|
+
" npm install -g @chikhamx/voidx\n" +
|
|
104
|
+
" or: curl -fsSL https://raw.githubusercontent.com/chikhamx/voidx/master/scripts/install.sh | bash"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function probePython(candidate) {
|
|
109
|
+
const code = [
|
|
110
|
+
"import sys",
|
|
111
|
+
"print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')",
|
|
112
|
+
].join("; ");
|
|
113
|
+
const result = spawnSync(candidate.command, [...candidate.args, "-c", code], {
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
windowsHide: true,
|
|
116
|
+
});
|
|
117
|
+
if (result.error) {
|
|
118
|
+
return { ok: false, reason: result.error.message };
|
|
119
|
+
}
|
|
120
|
+
if (result.status !== 0) {
|
|
121
|
+
return { ok: false, reason: (result.stderr || "").trim() };
|
|
122
|
+
}
|
|
123
|
+
const versionText = (result.stdout || "").trim();
|
|
124
|
+
const version = parseVersion(versionText);
|
|
125
|
+
if (!version) {
|
|
126
|
+
return { ok: false, reason: `Unable to parse Python version: ${versionText}` };
|
|
127
|
+
}
|
|
128
|
+
return { ok: true, version, versionText };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseVersion(value) {
|
|
132
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value);
|
|
133
|
+
if (!match) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return match.slice(1).map((part) => Number.parseInt(part, 10));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isCompatible(version) {
|
|
140
|
+
return version[0] > 3 || (version[0] === 3 && version[1] >= 11);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
function resolveDataHome(env) {
|
|
146
|
+
if (env.VOIDX_NPM_HOME) {
|
|
147
|
+
return path.resolve(env.VOIDX_NPM_HOME);
|
|
148
|
+
}
|
|
149
|
+
if (process.platform === "win32") {
|
|
150
|
+
return env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
151
|
+
}
|
|
152
|
+
return env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolvePythonDir(env) {
|
|
156
|
+
if (env.VOIDX_NPM_PYTHON_DIR) {
|
|
157
|
+
return path.resolve(env.VOIDX_NPM_PYTHON_DIR);
|
|
158
|
+
}
|
|
159
|
+
return path.join(resolveDataHome(env), "voidx", "python");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveBundledPythonBin(env) {
|
|
163
|
+
const pythonDir = resolvePythonDir(env);
|
|
164
|
+
return process.platform === "win32"
|
|
165
|
+
? path.join(pythonDir, "python", "python.exe")
|
|
166
|
+
: path.join(pythonDir, "python", "bin", "python3");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveVenvDir(env) {
|
|
170
|
+
if (env.VOIDX_NPM_VENV) {
|
|
171
|
+
return path.resolve(env.VOIDX_NPM_VENV);
|
|
172
|
+
}
|
|
173
|
+
// Use the same venv directory as install.sh — single environment, no duplicates
|
|
174
|
+
return path.join(resolveDataHome(env), "voidx", "venv");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveVenvPython(venvDir) {
|
|
178
|
+
return process.platform === "win32"
|
|
179
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
180
|
+
: path.join(venvDir, "bin", "python");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveVoidxExecutable(venvDir) {
|
|
184
|
+
return process.platform === "win32"
|
|
185
|
+
? path.join(venvDir, "Scripts", "voidx.exe")
|
|
186
|
+
: path.join(venvDir, "bin", "voidx");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Venv setup ─────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
function ensureVenv(python, venvDir, env) {
|
|
192
|
+
const executable = resolveVoidxExecutable(venvDir);
|
|
193
|
+
if (env.VOIDX_NPM_SKIP_BOOTSTRAP === "1") {
|
|
194
|
+
if (!fs.existsSync(executable)) {
|
|
195
|
+
throw new Error(`voidx executable not found in ${venvDir}.`);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const markerPath = path.join(venvDir, ".voidx-install-version");
|
|
201
|
+
const marker = `${pkg.version}\n${PBS_TAG}\n${PBS_CPYTHON}\n`;
|
|
202
|
+
if (fs.existsSync(executable) && readFile(markerPath) === marker) {
|
|
203
|
+
debug(env, `Using cached environment at ${venvDir}`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// v2 → v3 migration: clean up legacy data before first v3 run
|
|
208
|
+
runV2CleanupIfNeeded(venvDir, env);
|
|
209
|
+
|
|
210
|
+
fs.mkdirSync(path.dirname(venvDir), { recursive: true });
|
|
211
|
+
const venvPython = resolveVenvPython(venvDir);
|
|
212
|
+
|
|
213
|
+
// If venv exists but is corrupted (python binary missing), nuke and rebuild
|
|
214
|
+
if (fs.existsSync(venvDir) && !fs.existsSync(venvPython)) {
|
|
215
|
+
console.error(" Existing venv is corrupted, rebuilding…");
|
|
216
|
+
try {
|
|
217
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error(` Failed to remove corrupted venv: ${err.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const isFresh = !fs.existsSync(venvPython);
|
|
224
|
+
if (isFresh) {
|
|
225
|
+
console.error(
|
|
226
|
+
"\n⚙️ Setting up voidx environment (this only happens once)...\n"
|
|
227
|
+
);
|
|
228
|
+
const venvResult = spawnSync(
|
|
229
|
+
python.command,
|
|
230
|
+
[...python.args, "-m", "venv", venvDir],
|
|
231
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true }
|
|
232
|
+
);
|
|
233
|
+
if (venvResult.error) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Failed to create the Python virtual environment: ${venvResult.error.message}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (venvResult.status !== 0) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"Failed to create the Python virtual environment. See errors above."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!fs.existsSync(executable) || readFile(markerPath) !== marker) {
|
|
246
|
+
const packageSpec = env.VOIDX_NPM_PACKAGE_SPEC || `voidx==${pkg.version}`;
|
|
247
|
+
console.error(
|
|
248
|
+
`\n📦 Downloading ${packageSpec} and dependencies… ` +
|
|
249
|
+
"(1–2 minutes on first run)\n"
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Upgrade pip first to avoid resolver bugs
|
|
253
|
+
const pipUpgradeEnv = Object.assign({}, env, {
|
|
254
|
+
PIP_NO_INPUT: "1",
|
|
255
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
256
|
+
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
257
|
+
});
|
|
258
|
+
const pipUpgradeResult = spawnSync(
|
|
259
|
+
venvPython,
|
|
260
|
+
["-m", "pip", "install", "--upgrade", "pip", "--no-cache-dir"],
|
|
261
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipUpgradeEnv }
|
|
262
|
+
);
|
|
263
|
+
if (pipUpgradeResult.error || pipUpgradeResult.status !== 0) {
|
|
264
|
+
console.error(" ⚠️ Failed to upgrade pip, continuing with current version…");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const pipEnv = Object.assign({}, env, {
|
|
268
|
+
PIP_NO_INPUT: "1",
|
|
269
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
270
|
+
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const pipArgs = ["-m", "pip", "install", "--upgrade", "--no-cache-dir", "--progress-bar", "on"];
|
|
274
|
+
|
|
275
|
+
// Support custom PyPI index
|
|
276
|
+
const pipIndex = env.VOIDX_NPM_PIP_INDEX;
|
|
277
|
+
if (pipIndex) {
|
|
278
|
+
pipArgs.push("-i", pipIndex);
|
|
279
|
+
try {
|
|
280
|
+
const indexUrl = new URL(pipIndex);
|
|
281
|
+
pipArgs.push("--trusted-host", indexUrl.hostname);
|
|
282
|
+
} catch {}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
pipArgs.push(packageSpec);
|
|
286
|
+
|
|
287
|
+
const result = spawnSync(
|
|
288
|
+
venvPython,
|
|
289
|
+
pipArgs,
|
|
290
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipEnv }
|
|
291
|
+
);
|
|
292
|
+
if (result.error) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`Failed to install ${packageSpec}: ${result.error.message}`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (result.status !== 0) {
|
|
298
|
+
throw new Error(`Failed to install ${packageSpec}. See errors above.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fs.writeFileSync(markerPath, marker);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── v2 → v3 migration ─────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
function runV2CleanupIfNeeded(venvDir, env) {
|
|
308
|
+
const markerPath = path.join(venvDir, ".voidx-install-version");
|
|
309
|
+
const oldMarker = readFile(markerPath).trim();
|
|
310
|
+
if (!oldMarker) return; // fresh install, nothing to migrate
|
|
311
|
+
|
|
312
|
+
const oldVersion = parseVersion(oldMarker.split("\n")[0]);
|
|
313
|
+
if (!oldVersion) return;
|
|
314
|
+
|
|
315
|
+
// Only run cleanup when upgrading from < 3.0.0
|
|
316
|
+
const v3 = [3, 0, 0];
|
|
317
|
+
if (
|
|
318
|
+
oldVersion[0] > v3[0] ||
|
|
319
|
+
(oldVersion[0] === v3[0] && oldVersion[1] > v3[1]) ||
|
|
320
|
+
(oldVersion[0] === v3[0] && oldVersion[1] === v3[1] && oldVersion[2] >= v3[2])
|
|
321
|
+
) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.error("\n🔄 Upgrading from v2 — cleaning up legacy data…\n");
|
|
326
|
+
|
|
327
|
+
const venvPython = resolveVenvPython(venvDir);
|
|
328
|
+
if (!fs.existsSync(venvPython)) return; // venv not built yet, will clean after install
|
|
329
|
+
|
|
330
|
+
// Download and run the cleanup script from GitHub
|
|
331
|
+
const scriptUrl =
|
|
332
|
+
"https://raw.githubusercontent.com/chikhamx/voidx/master/scripts/clean_v2_data.py";
|
|
333
|
+
const tmpScript = path.join(os.tmpdir(), "voidx-clean-v2-data.py");
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const curlResult = spawnSync(
|
|
337
|
+
process.platform === "win32" ? "curl.exe" : "curl",
|
|
338
|
+
["-fsSL", scriptUrl, "-o", tmpScript],
|
|
339
|
+
{ encoding: "utf8", windowsHide: true, timeout: 15000 }
|
|
340
|
+
);
|
|
341
|
+
if (curlResult.error || curlResult.status !== 0) {
|
|
342
|
+
console.error(" ⚠️ Could not download v2 cleanup script, skipping.");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const pyResult = spawnSync(
|
|
347
|
+
venvPython,
|
|
348
|
+
[tmpScript],
|
|
349
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, timeout: 30000 }
|
|
350
|
+
);
|
|
351
|
+
if (pyResult.error || pyResult.status !== 0) {
|
|
352
|
+
console.error(" ⚠️ v2 cleanup script failed, continuing anyway.");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try { fs.unlinkSync(tmpScript); } catch {}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(` ⚠️ v2 cleanup error: ${err.message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
function readFile(filePath) {
|
|
364
|
+
try {
|
|
365
|
+
return fs.readFileSync(filePath, "utf8");
|
|
366
|
+
} catch {
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function debug(env, message) {
|
|
372
|
+
if (env.VOIDX_NPM_DEBUG === "1") {
|
|
373
|
+
console.error(`[voidx npm] ${message}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function fail(message) {
|
|
378
|
+
console.error(message);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (require.main === module) {
|
|
383
|
+
main();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = {
|
|
387
|
+
isCompatible,
|
|
388
|
+
parseVersion,
|
|
389
|
+
resolveDataHome,
|
|
390
|
+
resolveVenvDir,
|
|
391
|
+
resolveBundledPythonBin,
|
|
392
|
+
selectPython,
|
|
393
|
+
};
|