@chikhamx/voidx 1.1.1 → 2.0.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 +122 -90
- package/bin/voidx.js +51 -31
- package/package.json +1 -1
package/bin/postinstall.js
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
// Post-install: download a standalone Python, create venv, pip install voidx.
|
|
5
|
-
//
|
|
5
|
+
// Uses only the bundled Python — never falls back to the system Python.
|
|
6
6
|
|
|
7
|
-
const { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
7
|
+
const { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, rmSync } = require("fs");
|
|
8
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
9
|
const { join, dirname } = require("path");
|
|
14
10
|
const os = require("os");
|
|
15
11
|
const fs = require("fs");
|
|
16
12
|
const path = require("path");
|
|
17
|
-
const { execSync } = require("child_process");
|
|
18
13
|
|
|
19
14
|
const pkg = require("../package.json");
|
|
20
15
|
|
|
@@ -92,7 +87,32 @@ function resolveBundledPython(pythonDir, platform) {
|
|
|
92
87
|
return path.join(installDir, "bin", "python3");
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
// ── Download
|
|
90
|
+
// ── Download with retry ────────────────────────────────────────────────────
|
|
91
|
+
const MAX_DOWNLOAD_RETRIES = 3;
|
|
92
|
+
|
|
93
|
+
function sleep(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function downloadFileWithRetry(url, dest, retries = MAX_DOWNLOAD_RETRIES) {
|
|
98
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
99
|
+
try {
|
|
100
|
+
return await downloadFile(url, dest);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// Clean up partial download
|
|
103
|
+
try { unlinkSync(dest); } catch {}
|
|
104
|
+
if (attempt < retries) {
|
|
105
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
106
|
+
console.error(` Download attempt ${attempt}/${retries} failed: ${err.message}`);
|
|
107
|
+
console.error(` Retrying in ${delay / 1000}s…`);
|
|
108
|
+
await sleep(delay);
|
|
109
|
+
} else {
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
96
116
|
function downloadFile(url, dest) {
|
|
97
117
|
return new Promise((resolve, reject) => {
|
|
98
118
|
const doRequest = (currentUrl, redirects = 0) => {
|
|
@@ -100,7 +120,7 @@ function downloadFile(url, dest) {
|
|
|
100
120
|
return reject(new Error(`Too many redirects downloading ${url}`));
|
|
101
121
|
}
|
|
102
122
|
const mod = currentUrl.startsWith("https") ? require("https") : require("http");
|
|
103
|
-
mod.get(currentUrl, { timeout:
|
|
123
|
+
mod.get(currentUrl, { timeout: 30000 }, (res) => {
|
|
104
124
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
105
125
|
return doRequest(res.headers.location, redirects + 1);
|
|
106
126
|
}
|
|
@@ -163,14 +183,45 @@ function writeMarker(markerPath, content) {
|
|
|
163
183
|
|
|
164
184
|
// ── pip install ────────────────────────────────────────────────────────────
|
|
165
185
|
function pipInstall(venvPython, packageSpec, env) {
|
|
186
|
+
// Upgrade pip first to avoid resolver bugs in old versions
|
|
187
|
+
const pipUpgradeEnv = Object.assign({}, env, {
|
|
188
|
+
PIP_NO_INPUT: "1",
|
|
189
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
190
|
+
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
191
|
+
});
|
|
192
|
+
const pipUpgradeResult = spawnSync(
|
|
193
|
+
venvPython,
|
|
194
|
+
["-m", "pip", "install", "--upgrade", "pip", "--no-cache-dir"],
|
|
195
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipUpgradeEnv }
|
|
196
|
+
);
|
|
197
|
+
if (pipUpgradeResult.error || pipUpgradeResult.status !== 0) {
|
|
198
|
+
console.error(" ⚠️ Failed to upgrade pip, continuing with current version…");
|
|
199
|
+
}
|
|
200
|
+
|
|
166
201
|
const pipEnv = Object.assign({}, env, {
|
|
167
202
|
PIP_NO_INPUT: "1",
|
|
168
203
|
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
169
204
|
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
170
205
|
});
|
|
206
|
+
|
|
207
|
+
const pipArgs = ["-m", "pip", "install", "--upgrade", "--no-cache-dir", "--progress-bar", "on"];
|
|
208
|
+
|
|
209
|
+
// Support custom PyPI index for users behind firewalls or in regions with slow PyPI access
|
|
210
|
+
const pipIndex = env.VOIDX_NPM_PIP_INDEX;
|
|
211
|
+
if (pipIndex) {
|
|
212
|
+
pipArgs.push("-i", pipIndex);
|
|
213
|
+
// Extract host for --trusted-host when using a custom index
|
|
214
|
+
try {
|
|
215
|
+
const indexUrl = new URL(pipIndex);
|
|
216
|
+
pipArgs.push("--trusted-host", indexUrl.hostname);
|
|
217
|
+
} catch {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pipArgs.push(packageSpec);
|
|
221
|
+
|
|
171
222
|
const result = spawnSync(
|
|
172
223
|
venvPython,
|
|
173
|
-
|
|
224
|
+
pipArgs,
|
|
174
225
|
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipEnv }
|
|
175
226
|
);
|
|
176
227
|
if (result.error) {
|
|
@@ -181,37 +232,6 @@ function pipInstall(venvPython, packageSpec, env) {
|
|
|
181
232
|
}
|
|
182
233
|
}
|
|
183
234
|
|
|
184
|
-
// ── System Python fallback ─────────────────────────────────────────────────
|
|
185
|
-
function probeSystemPython() {
|
|
186
|
-
const candidates = [
|
|
187
|
-
{ command: "python3", args: [] },
|
|
188
|
-
{ command: "python", args: [] },
|
|
189
|
-
{ command: "python3.12", args: [] },
|
|
190
|
-
{ command: "python3.11", args: [] },
|
|
191
|
-
];
|
|
192
|
-
if (process.platform === "win32") {
|
|
193
|
-
candidates.push({ command: "py", args: ["-3"] });
|
|
194
|
-
}
|
|
195
|
-
|
|
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
|
-
);
|
|
202
|
-
if (result.error || result.status !== 0) continue;
|
|
203
|
-
const ver = (result.stdout || "").trim();
|
|
204
|
-
const match = /^(\d+)\.(\d+)/.exec(ver);
|
|
205
|
-
if (!match) continue;
|
|
206
|
-
const major = parseInt(match[1], 10);
|
|
207
|
-
const minor = parseInt(match[2], 10);
|
|
208
|
-
if (major > 3 || (major === 3 && minor >= 11)) {
|
|
209
|
-
return { command: candidate.command, args: candidate.args };
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
235
|
// ── Main ───────────────────────────────────────────────────────────────────
|
|
216
236
|
async function main() {
|
|
217
237
|
const env = process.env;
|
|
@@ -228,70 +248,71 @@ async function main() {
|
|
|
228
248
|
return;
|
|
229
249
|
}
|
|
230
250
|
|
|
231
|
-
// Step 1:
|
|
232
|
-
let pythonForVenv;
|
|
251
|
+
// Step 1: Download bundled Python (required — no system Python fallback)
|
|
233
252
|
const platformInfo = getPlatformInfo();
|
|
253
|
+
if (!platformInfo) {
|
|
254
|
+
console.error(`\n ❌ Unsupported platform: ${os.platform()}-${os.arch()}\n`);
|
|
255
|
+
console.error(" voidx npm package supports: macOS (x64/arm64), Linux (x64/arm64), Windows (x64/arm64)\n");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
234
258
|
|
|
235
|
-
|
|
236
|
-
|
|
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 });
|
|
259
|
+
console.error(`\n🐍 Setting up voidx ${pkg.version}…\n`);
|
|
260
|
+
console.error(" [1/3] Downloading Python runtime…");
|
|
248
261
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
const pythonDir = resolvePythonDir(env);
|
|
263
|
+
const pbsFilename = getPbsFilename(platformInfo.target);
|
|
264
|
+
const pbsUrlBase = env.VOIDX_NPM_PYTHON_MIRROR || PBS_RELEASE_BASE;
|
|
265
|
+
const pbsUrl = `${pbsUrlBase}/${PBS_TAG}/${pbsFilename}`;
|
|
266
|
+
const archivePath = path.join(pythonDir, pbsFilename);
|
|
267
|
+
const bundledPython = resolveBundledPython(pythonDir, platformInfo.platform);
|
|
254
268
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
269
|
+
if (!existsSync(bundledPython)) {
|
|
270
|
+
try {
|
|
271
|
+
mkdirSync(pythonDir, { recursive: true });
|
|
258
272
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
console.error(
|
|
263
|
-
console.error(" Falling back to system Python…\n");
|
|
273
|
+
if (!existsSync(archivePath)) {
|
|
274
|
+
console.error(` Downloading ${pbsFilename}…`);
|
|
275
|
+
await downloadFileWithRetry(pbsUrl, archivePath);
|
|
276
|
+
console.error(" Download complete.");
|
|
264
277
|
}
|
|
265
|
-
} else {
|
|
266
|
-
console.error(" Using cached Python runtime.");
|
|
267
|
-
}
|
|
268
278
|
|
|
269
|
-
|
|
270
|
-
|
|
279
|
+
console.error(" Extracting Python runtime…");
|
|
280
|
+
extractTarGz(archivePath, pythonDir);
|
|
281
|
+
console.error(" Extraction complete.");
|
|
282
|
+
|
|
283
|
+
// Clean up archive to save disk
|
|
284
|
+
try { fs.unlinkSync(archivePath); } catch {}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
// Clean up partial archive on failure
|
|
287
|
+
try { unlinkSync(archivePath); } catch {}
|
|
288
|
+
console.error(`\n ❌ Failed to download Python runtime: ${err.message}\n`);
|
|
289
|
+
console.error(" This is usually a network issue. Try:");
|
|
290
|
+
console.error(" 1. Use a mirror: VOIDX_NPM_PYTHON_MIRROR=https://npmmirror.com/mirrors/python-standalone");
|
|
291
|
+
console.error(" 2. Retry: npm install -g @chikhamx/voidx");
|
|
292
|
+
console.error(" 3. Debug: VOIDX_NPM_DEBUG=1 npm install -g @chikhamx/voidx\n");
|
|
293
|
+
process.exit(1);
|
|
271
294
|
}
|
|
295
|
+
} else {
|
|
296
|
+
console.error(" Using cached Python runtime.");
|
|
272
297
|
}
|
|
273
298
|
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
console.error(
|
|
284
|
-
console.error(" Windows: https://python.org/downloads\n");
|
|
285
|
-
process.exit(1);
|
|
299
|
+
// Step 2: Create venv (rebuild if corrupted)
|
|
300
|
+
console.error(" [2/3] Creating virtual environment…");
|
|
301
|
+
|
|
302
|
+
// If venv exists but is corrupted (python binary missing), nuke and rebuild
|
|
303
|
+
if (existsSync(venvDir) && !existsSync(venvPython)) {
|
|
304
|
+
console.error(" Existing venv is corrupted, rebuilding…");
|
|
305
|
+
try {
|
|
306
|
+
rmSync(venvDir, { recursive: true, force: true });
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error(` Failed to remove corrupted venv: ${err.message}`);
|
|
286
309
|
}
|
|
287
310
|
}
|
|
288
311
|
|
|
289
|
-
// Step 2: Create venv
|
|
290
|
-
console.error(" [2/3] Creating virtual environment…");
|
|
291
312
|
if (!existsSync(venvPython)) {
|
|
292
313
|
const venvResult = spawnSync(
|
|
293
|
-
|
|
294
|
-
[
|
|
314
|
+
bundledPython,
|
|
315
|
+
["-m", "venv", venvDir],
|
|
295
316
|
{ encoding: "utf8", stdio: "inherit", windowsHide: true }
|
|
296
317
|
);
|
|
297
318
|
if (venvResult.error) {
|
|
@@ -313,7 +334,18 @@ async function main() {
|
|
|
313
334
|
console.error(`\n✅ voidx ${pkg.version} installed! Run: voidx\n`);
|
|
314
335
|
}
|
|
315
336
|
|
|
337
|
+
// ── URL builder (for mirror support) ──────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
function buildPythonDownloadUrl(mirrorBase, tag, filename) {
|
|
340
|
+
return `${mirrorBase}/${tag}/${filename}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
316
343
|
main().catch((err) => {
|
|
317
344
|
console.error(`\n❌ Setup failed: ${err.message}\n`);
|
|
318
345
|
process.exit(1);
|
|
319
346
|
});
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
buildPythonDownloadUrl,
|
|
350
|
+
downloadFileWithRetry,
|
|
351
|
+
};
|
package/bin/voidx.js
CHANGED
|
@@ -40,7 +40,7 @@ function main(argv = process.argv.slice(2), env = process.env) {
|
|
|
40
40
|
// ── Python selection ───────────────────────────────────────────────────────
|
|
41
41
|
|
|
42
42
|
function selectPython(env) {
|
|
43
|
-
// 1. Explicit override
|
|
43
|
+
// 1. Explicit override (for advanced users / debugging)
|
|
44
44
|
const explicit = env.VOIDX_PYTHON;
|
|
45
45
|
if (explicit) {
|
|
46
46
|
const candidate = { command: explicit, args: [], label: explicit };
|
|
@@ -56,7 +56,7 @@ function selectPython(env) {
|
|
|
56
56
|
return candidate;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// 2. Bundled Python (
|
|
59
|
+
// 2. Bundled Python (preferred)
|
|
60
60
|
const bundledBin = resolveBundledPythonBin(env);
|
|
61
61
|
if (bundledBin && fs.existsSync(bundledBin)) {
|
|
62
62
|
const candidate = { command: bundledBin, args: [], label: "bundled" };
|
|
@@ -66,7 +66,7 @@ function selectPython(env) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// 3. System Python
|
|
69
|
+
// 3. System Python fallback (for upgrades from v1.x where bundled Python was not used)
|
|
70
70
|
const candidates = [
|
|
71
71
|
{ command: "python3", args: [], label: "python3" },
|
|
72
72
|
{ command: "python", args: [], label: "python" },
|
|
@@ -90,14 +90,17 @@ function selectPython(env) {
|
|
|
90
90
|
oldVersions.push(`${probe.versionText} at ${candidate.label}`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const hint = pythonHint();
|
|
94
93
|
if (oldVersions.length > 0) {
|
|
95
94
|
throw new Error(
|
|
96
|
-
`voidx requires Python 3.11+. Found ${oldVersions.join(", ")}.\n
|
|
95
|
+
`voidx requires Python 3.11+. Found ${oldVersions.join(", ")}.\n` +
|
|
96
|
+
"Install Python 3.11+ or reinstall the npm package to get the bundled runtime:\n" +
|
|
97
|
+
" npm install -g @chikhamx/voidx"
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
throw new Error(
|
|
100
|
-
|
|
101
|
+
"voidx requires Python 3.11+. No Python found.\n" +
|
|
102
|
+
"Install Python 3.11+ or reinstall the npm package to get the bundled runtime:\n" +
|
|
103
|
+
" npm install -g @chikhamx/voidx"
|
|
101
104
|
);
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -136,22 +139,6 @@ function isCompatible(version) {
|
|
|
136
139
|
return version[0] > 3 || (version[0] === 3 && version[1] >= 11);
|
|
137
140
|
}
|
|
138
141
|
|
|
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
142
|
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
156
143
|
|
|
157
144
|
function resolveDataHome(env) {
|
|
@@ -220,6 +207,16 @@ function ensureVenv(python, venvDir, env) {
|
|
|
220
207
|
fs.mkdirSync(path.dirname(venvDir), { recursive: true });
|
|
221
208
|
const venvPython = resolveVenvPython(venvDir);
|
|
222
209
|
|
|
210
|
+
// If venv exists but is corrupted (python binary missing), nuke and rebuild
|
|
211
|
+
if (fs.existsSync(venvDir) && !fs.existsSync(venvPython)) {
|
|
212
|
+
console.error(" Existing venv is corrupted, rebuilding…");
|
|
213
|
+
try {
|
|
214
|
+
fs.rmSync(venvDir, { recursive: true, force: true });
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(` Failed to remove corrupted venv: ${err.message}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
223
220
|
const isFresh = !fs.existsSync(venvPython);
|
|
224
221
|
if (isFresh) {
|
|
225
222
|
console.error(
|
|
@@ -247,22 +244,45 @@ function ensureVenv(python, venvDir, env) {
|
|
|
247
244
|
`\n📦 Downloading ${packageSpec} and dependencies… ` +
|
|
248
245
|
"(1–2 minutes on first run)\n"
|
|
249
246
|
);
|
|
247
|
+
|
|
248
|
+
// Upgrade pip first to avoid resolver bugs
|
|
249
|
+
const pipUpgradeEnv = Object.assign({}, env, {
|
|
250
|
+
PIP_NO_INPUT: "1",
|
|
251
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
252
|
+
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
253
|
+
});
|
|
254
|
+
const pipUpgradeResult = spawnSync(
|
|
255
|
+
venvPython,
|
|
256
|
+
["-m", "pip", "install", "--upgrade", "pip", "--no-cache-dir"],
|
|
257
|
+
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipUpgradeEnv }
|
|
258
|
+
);
|
|
259
|
+
if (pipUpgradeResult.error || pipUpgradeResult.status !== 0) {
|
|
260
|
+
console.error(" ⚠️ Failed to upgrade pip, continuing with current version…");
|
|
261
|
+
}
|
|
262
|
+
|
|
250
263
|
const pipEnv = Object.assign({}, env, {
|
|
251
264
|
PIP_NO_INPUT: "1",
|
|
252
265
|
PIP_DISABLE_PIP_VERSION_CHECK: "1",
|
|
253
266
|
PYTHON_KEYRING_BACKEND: "keyring.backends.null.Keyring",
|
|
254
267
|
});
|
|
268
|
+
|
|
269
|
+
const pipArgs = ["-m", "pip", "install", "--upgrade", "--no-cache-dir", "--progress-bar", "on"];
|
|
270
|
+
|
|
271
|
+
// Support custom PyPI index
|
|
272
|
+
const pipIndex = env.VOIDX_NPM_PIP_INDEX;
|
|
273
|
+
if (pipIndex) {
|
|
274
|
+
pipArgs.push("-i", pipIndex);
|
|
275
|
+
try {
|
|
276
|
+
const indexUrl = new URL(pipIndex);
|
|
277
|
+
pipArgs.push("--trusted-host", indexUrl.hostname);
|
|
278
|
+
} catch {}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
pipArgs.push(packageSpec);
|
|
282
|
+
|
|
255
283
|
const result = spawnSync(
|
|
256
284
|
venvPython,
|
|
257
|
-
|
|
258
|
-
"-m",
|
|
259
|
-
"pip",
|
|
260
|
-
"install",
|
|
261
|
-
"--upgrade",
|
|
262
|
-
"--progress-bar",
|
|
263
|
-
"on",
|
|
264
|
-
packageSpec,
|
|
265
|
-
],
|
|
285
|
+
pipArgs,
|
|
266
286
|
{ encoding: "utf8", stdio: "inherit", windowsHide: true, env: pipEnv }
|
|
267
287
|
);
|
|
268
288
|
if (result.error) {
|