@melaya/runner 1.1.32 → 1.1.33

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.
@@ -57,9 +57,10 @@ function findOnPath(bin) {
57
57
  }
58
58
  // Inactivity-bounded child runner (mirrors pythonEnv.runProc): a managed-CPython
59
59
  // download legitimately takes a while but streams progress, so we bound silence.
60
- function run(cmd, args, onLine, inactivityMs = 5 * 60_000) {
60
+ function run(cmd, args, onLine, opts = {}) {
61
+ const inactivityMs = opts.inactivityMs ?? 5 * 60_000;
61
62
  return new Promise((resolve) => {
62
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: process.env });
63
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...opts.env }, cwd: opts.cwd });
63
64
  let done = false;
64
65
  let timer;
65
66
  const finish = (c) => { if (done)
@@ -85,15 +86,38 @@ function run(cmd, args, onLine, inactivityMs = 5 * 60_000) {
85
86
  bump();
86
87
  });
87
88
  }
88
- function capture(cmd, args) {
89
+ function capture(cmd, args, env = {}) {
89
90
  return new Promise((resolve) => {
90
91
  let out = "";
91
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: process.env });
92
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...env } });
92
93
  child.stdout?.on("data", (b) => { out += b.toString(); });
93
94
  child.on("exit", () => resolve(out.trim()));
94
95
  child.on("error", () => resolve(""));
95
96
  });
96
97
  }
98
+ // Extract the downloaded uv archive. uv ships .tar.gz for macOS/Linux and .zip
99
+ // for Windows, so extraction must branch by type — a single `tar` call can't
100
+ // cover both portably (a shell's `tar` may be MSYS GNU tar, which can't read
101
+ // zip). destDir already contains the archive; we extract in place.
102
+ async function extractArchive(archivePath, asset, destDir, onLine) {
103
+ if (asset.endsWith(".zip")) {
104
+ // Windows only. PowerShell's Expand-Archive is always present and reliably
105
+ // reads zips, unlike whatever `tar` happens to be on PATH.
106
+ const ps = `Expand-Archive -LiteralPath '${archivePath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`;
107
+ let code = await run("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], onLine);
108
+ if (code === 0)
109
+ return true;
110
+ // Fallback: Windows' bundled bsdtar (System32\tar.exe) also handles zip.
111
+ const sys32 = join(process.env.SystemRoot || "C:\\Windows", "System32", "tar.exe");
112
+ code = await run(existsSync(sys32) ? sys32 : "tar", ["-xf", asset, "-C", "."], onLine, { cwd: destDir });
113
+ return code === 0;
114
+ }
115
+ // macOS/Linux: .tar.gz — bsdtar (macOS) and GNU tar (Linux) both read gz.
116
+ // cwd + bare relative name keeps args colon-free (harmless on unix, required
117
+ // on Windows for the fallback path above).
118
+ const code = await run("tar", ["-xf", asset, "-C", "."], onLine, { cwd: destDir });
119
+ return code === 0;
120
+ }
97
121
  function findBinaryRecursive(dir, name) {
98
122
  let entries;
99
123
  try {
@@ -154,9 +178,9 @@ async function downloadUv(onLine) {
154
178
  return null;
155
179
  }
156
180
  onLine("extracting uv");
157
- const code = await run("tar", ["-xf", archivePath, "-C", tmp], onLine);
158
- if (code !== 0) {
159
- onLine("tar extraction of uv failed (is `tar` available?)");
181
+ const extracted = await extractArchive(archivePath, asset, tmp, onLine);
182
+ if (!extracted) {
183
+ onLine("extraction of the uv archive failed");
160
184
  return null;
161
185
  }
162
186
  const found = findBinaryRecursive(tmp, uvBinName());
@@ -200,12 +224,20 @@ export async function provisionPython(version, onLine) {
200
224
  if (!uv)
201
225
  return null;
202
226
  onLine(`installing a managed Python ${version} (one-time; ~30-60s)`);
203
- const code = await run(uv, ["python", "install", version], onLine);
227
+ // UV_SYSTEM_CERTS makes uv verify against the OS trust store instead of its
228
+ // bundled webpki roots — required behind TLS-intercepting proxies/antivirus
229
+ // (else "invalid peer certificate: UnknownIssuer") and on networks whose
230
+ // issuer isn't in webpki. Set as an env var (not the CLI flag) so it's a
231
+ // no-op on uv versions that don't know it rather than an unknown-flag error;
232
+ // the flag was also renamed from --native-tls, and env vars dodge that churn.
233
+ // UV_HTTP_TIMEOUT lifts the per-request timeout for the ~20MB CPython download.
234
+ const uvEnv = { UV_SYSTEM_CERTS: "1", UV_HTTP_TIMEOUT: "300" };
235
+ const code = await run(uv, ["python", "install", version], onLine, { env: uvEnv });
204
236
  if (code !== 0) {
205
237
  onLine(`uv python install ${version} failed`);
206
238
  return null;
207
239
  }
208
- const path = await capture(uv, ["python", "find", version]);
240
+ const path = await capture(uv, ["python", "find", version], uvEnv);
209
241
  if (path && existsSync(path))
210
242
  return path;
211
243
  onLine(`uv could not resolve a Python ${version} interpreter path after install`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.1.32",
3
+ "version": "1.1.33",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,