@melaya/runner 1.0.32 → 1.0.34

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.
Files changed (2) hide show
  1. package/dist/pythonEnv.js +39 -7
  2. package/package.json +1 -1
package/dist/pythonEnv.js CHANGED
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { spawn } from "child_process";
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
+ import { createHash } from "crypto";
14
15
  import { join } from "path";
15
16
  import { homedir, platform } from "os";
16
17
  import chalk from "chalk";
@@ -86,6 +87,19 @@ export function venvPython() {
86
87
  ? join(VENV_DIR, "Scripts", "python.exe")
87
88
  : join(VENV_DIR, "bin", "python");
88
89
  }
90
+ // The marker is keyed on sharedVersion + a hash of PIP_DEPS so changes
91
+ // to either invalidate the venv. Without the PIP_DEPS half, a runner
92
+ // update that adds a dep (e.g. scrapling[fetchers] in 1.0.32) would
93
+ // silently skip the reinstall on any box whose sharedVersion hadn't
94
+ // also changed — operators would update the runner but never see the
95
+ // new deps. Hashing the full deps list catches additions, removals,
96
+ // AND version-pin changes within deps.
97
+ function _pipDepsHash() {
98
+ return createHash("sha256").update(JSON.stringify(PIP_DEPS)).digest("hex").slice(0, 12);
99
+ }
100
+ function venvMarkerValue(expectedVersion) {
101
+ return `${expectedVersion}::${_pipDepsHash()}`;
102
+ }
89
103
  function venvIsValid(expectedVersion) {
90
104
  if (!existsSync(venvPython()))
91
105
  return false;
@@ -93,7 +107,7 @@ function venvIsValid(expectedVersion) {
93
107
  return false;
94
108
  try {
95
109
  const cached = readFileSync(VENV_MARK, "utf-8").trim();
96
- return cached === expectedVersion;
110
+ return cached === venvMarkerValue(expectedVersion);
97
111
  }
98
112
  catch {
99
113
  return false;
@@ -161,16 +175,34 @@ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress
161
175
  }
162
176
  // Scrapling browser binaries — best-effort. The fast-path AsyncFetcher
163
177
  // (curl_cffi TLS impersonation) works without this; only StealthyFetcher
164
- // (Cloudflare-bypass rescue path) needs Chromium. If `scrapling install`
165
- // fails (offline, network blip, browser-download mirror down) we don't
166
- // fail the venv the search just stays on the fast path. Logs are
167
- // surfaced so the operator sees what happened.
178
+ // (Cloudflare-bypass rescue path) needs Chromium. Scrapling has no
179
+ // __main__.py invoke its CLI entry point via `from scrapling.cli
180
+ // import main; main(['install'])` so the call works the same on every
181
+ // platform without reaching into the venv's Scripts/bin folder.
182
+ // Non-fatal: if it fails (offline, mirror down, scrapling.cli rename in
183
+ // a future version) the search just stays on the fast path.
168
184
  onProgress("installing scrapling browsers (best-effort, for stealth rescue path)");
169
- const scrCode = await runProc(venvPython(), ["-m", "scrapling", "install"], onProgress);
185
+ const scrCode = await runProc(venvPython(), ["-c",
186
+ "import sys\n" +
187
+ "# Try every known CLI shape across scrapling versions, return 0 on first success.\n" +
188
+ "tried = []\n" +
189
+ "for mod, attr in (('scrapling.cli','main'), ('scrapling.__main__','main'), ('scrapling.cli','cli')):\n" +
190
+ " try:\n" +
191
+ " m = __import__(mod, fromlist=[attr])\n" +
192
+ " fn = getattr(m, attr)\n" +
193
+ " try: fn(['install'])\n" +
194
+ " except TypeError: fn() # zero-arg variants\n" +
195
+ " sys.exit(0)\n" +
196
+ " except Exception as e:\n" +
197
+ " tried.append(f'{mod}.{attr}: {type(e).__name__}: {e}')\n" +
198
+ " continue\n" +
199
+ "print('scrapling install: no working CLI entry — tried:'); [print(' -', t) for t in tried]\n" +
200
+ "sys.exit(2)\n",
201
+ ], onProgress);
170
202
  if (scrCode !== 0) {
171
203
  onProgress(`(scrapling install exited ${scrCode} — fast-path fetcher still works; stealth-rescue disabled)`);
172
204
  }
173
- writeFileSync(VENV_MARK, expectedVersion, "utf-8");
205
+ writeFileSync(VENV_MARK, venvMarkerValue(expectedVersion), "utf-8");
174
206
  // sanity: confirm shortuuid + agentscope (via PYTHONPATH) resolve now.
175
207
  // Probe must mirror the spawn env so PYTHONPATH=CACHE_DIR points at
176
208
  // the cached agentscope source — without this the probe ImportError's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,