robot-resources 1.9.2 → 1.9.4
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/lib/python-bridge.js +17 -7
- package/lib/wizard.js +38 -17
- package/package.json +1 -1
package/lib/python-bridge.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findPython,
|
|
3
|
+
findOrInstallPython,
|
|
3
4
|
ensureVenv,
|
|
4
5
|
installRouter,
|
|
5
6
|
isRouterInstalled,
|
|
@@ -7,22 +8,31 @@ import {
|
|
|
7
8
|
} from '@robot-resources/cli-core/python-bridge.mjs';
|
|
8
9
|
|
|
9
10
|
// Re-export shared primitives used by wizard.js and other CLI code.
|
|
10
|
-
export { ensureVenv, isRouterInstalled, getVenvPythonPath };
|
|
11
|
+
export { findPython, ensureVenv, isRouterInstalled, getVenvPythonPath };
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Full setup: find Python, create venv, install router.
|
|
14
|
-
*
|
|
14
|
+
* Full setup: find Python (or bootstrap one via uv), create venv, install router.
|
|
15
|
+
*
|
|
16
|
+
* The uv-fallback path is what unblocks users without system Python. It
|
|
17
|
+
* downloads the uv binary to ~/.robot-resources/bin/ and uses it to
|
|
18
|
+
* install a standalone Python — nothing touches the user's system.
|
|
19
|
+
*
|
|
20
|
+
* Returns { venvPython, pythonVersion, pythonSource } or throws.
|
|
15
21
|
*/
|
|
16
22
|
export async function setupRouter() {
|
|
17
|
-
const python =
|
|
23
|
+
const python = await findOrInstallPython();
|
|
18
24
|
if (!python) {
|
|
19
25
|
throw new Error(
|
|
20
|
-
'Python 3.10+ not found
|
|
21
|
-
'
|
|
26
|
+
'Python 3.10+ not found and uv bootstrap failed. ' +
|
|
27
|
+
'Install Python from https://python.org and try again.'
|
|
22
28
|
);
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
const venvPython = ensureVenv(python.bin);
|
|
26
32
|
await installRouter();
|
|
27
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
venvPython,
|
|
35
|
+
pythonVersion: python.version,
|
|
36
|
+
pythonSource: python.source, // 'system' | 'uv'
|
|
37
|
+
};
|
|
28
38
|
}
|
package/lib/wizard.js
CHANGED
|
@@ -19,12 +19,25 @@ import { header, step, success, warn, error, info, blank, summary } from './ui.j
|
|
|
19
19
|
*/
|
|
20
20
|
function classifyRouterError(err) {
|
|
21
21
|
const msg = (err?.message || String(err)).toLowerCase();
|
|
22
|
+
const stderr = (err?.stderr || '').toString().toLowerCase();
|
|
23
|
+
const combined = msg + '\n' + stderr;
|
|
22
24
|
let reason = 'unknown';
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
// Order matters — check specific patterns before generic ones.
|
|
27
|
+
// python_venv_missing is specific (Debian/Ubuntu ships python3 without
|
|
28
|
+
// the venv module) — previously showed up as 'unknown' in telemetry.
|
|
29
|
+
if (/python venv module|ensurepip.*not (installed|available)|python\d*-venv/.test(combined)) {
|
|
30
|
+
reason = 'python_venv_missing';
|
|
31
|
+
} else if (msg.includes('python 3.10+') || msg.includes('python is required')) {
|
|
25
32
|
reason = 'python_not_found';
|
|
26
33
|
} else if (err?.code === 'ENOENT' || msg.includes('enoent')) {
|
|
27
34
|
reason = 'spawn_enoent';
|
|
35
|
+
} else if (/failed building wheel|metadata-generation-failed|cargo|rust compiler|subprocess-exited-with-error/.test(combined)) {
|
|
36
|
+
// Wheel-build failures: pip tried to compile a native dep from source
|
|
37
|
+
// because no binary wheel was available for the user's platform. This
|
|
38
|
+
// was silently categorized as 'pip_install_failed' before — surfacing
|
|
39
|
+
// it separately lets us see affected packages in aggregate.
|
|
40
|
+
reason = 'wheel_build_failed';
|
|
28
41
|
} else if (msg.includes('timeout') || msg.includes('timed out') || err?.code === 'ETIMEDOUT') {
|
|
29
42
|
reason = 'timeout';
|
|
30
43
|
} else if (msg.includes('exited with code') || msg.includes('pip install')) {
|
|
@@ -158,24 +171,26 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
158
171
|
results.router = true;
|
|
159
172
|
} else {
|
|
160
173
|
const python = findPython();
|
|
161
|
-
if (
|
|
162
|
-
warn('Python 3.10+ not found — skipping Router installation');
|
|
163
|
-
info('Install Python from https://python.org and re-run this wizard');
|
|
164
|
-
info('Scraper works without Python');
|
|
165
|
-
// Record the reason so install_complete tells us why router=false.
|
|
166
|
-
results.routerError = { reason: 'python_not_found', detail: 'Python 3.10+ not detected on PATH' };
|
|
167
|
-
} else {
|
|
174
|
+
if (python) {
|
|
168
175
|
info(`Found Python ${python.version} (${python.bin})`);
|
|
169
|
-
|
|
176
|
+
} else {
|
|
177
|
+
// No system Python — setupRouter() will bootstrap uv + install a
|
|
178
|
+
// standalone Python into ~/.robot-resources/. This used to be a hard
|
|
179
|
+
// fail; now it's the dominant auto-heal for the python_not_found
|
|
180
|
+
// cohort (2/3 of failures in recent telemetry).
|
|
181
|
+
warn('No system Python detected — bootstrapping one via uv.');
|
|
182
|
+
info('This downloads the uv binary (~15MB) and a managed Python to ~/.robot-resources/');
|
|
183
|
+
}
|
|
184
|
+
step('Installing Router (this may take a moment)...');
|
|
170
185
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
try {
|
|
187
|
+
const { pythonSource } = await setupRouter();
|
|
188
|
+
success(`Router installed${pythonSource === 'uv' ? ' (uv-managed Python)' : ''}`);
|
|
189
|
+
results.router = true;
|
|
190
|
+
results.pythonSource = pythonSource;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
error(`Router installation failed: ${err.message}`);
|
|
193
|
+
results.routerError = classifyRouterError(err);
|
|
179
194
|
}
|
|
180
195
|
}
|
|
181
196
|
|
|
@@ -340,6 +355,12 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
340
355
|
scraper: results.scraper || false,
|
|
341
356
|
source: 'wizard',
|
|
342
357
|
};
|
|
358
|
+
if (results.pythonSource) {
|
|
359
|
+
// 'system' when the user had Python installed, 'uv' when we
|
|
360
|
+
// auto-bootstrapped one. Lets us measure how many installs were
|
|
361
|
+
// rescued by the uv fallback.
|
|
362
|
+
installPayload.pythonSource = results.pythonSource;
|
|
363
|
+
}
|
|
343
364
|
if (results.routerError && typeof results.routerError === 'object') {
|
|
344
365
|
installPayload.routerError = results.routerError.reason;
|
|
345
366
|
installPayload.routerErrorDetail = results.routerError.detail;
|