@oh-my-pi/pi-natives 16.1.14 → 16.1.16

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/native/index.d.ts CHANGED
@@ -116,6 +116,13 @@ export declare class Shell {
116
116
  * Returns `Ok(())` even when no commands are running.
117
117
  */
118
118
  abort(): Promise<void>
119
+ /**
120
+ * Count live background jobs (`&`/`nohup` children still running) on this
121
+ * session. Completed jobs are reaped first. The host uses this to retain a
122
+ * per-call shell whose background processes are still running instead of
123
+ * dropping it (which would SIGKILL them via kill-on-drop).
124
+ */
125
+ liveBackgroundJobCount(): Promise<number>
119
126
  }
120
127
 
121
128
  /**
@@ -155,7 +162,7 @@ export declare function __ompInstallTokioRuntime(): void
155
162
  * `packages/natives/native/index.js` (which derives the name from
156
163
  * `package.json#version`).
157
164
  */
158
- export declare function __piNativesV16_1_14(): void
165
+ export declare function __piNativesV16_1_16(): void
159
166
 
160
167
  /**
161
168
  * Apply conservative pre-execution rewrites to a bash command.
package/native/index.js CHANGED
@@ -24,7 +24,7 @@ export const Shell = nativeBindings.Shell;
24
24
 
25
25
  // functions
26
26
  export const __ompInstallTokioRuntime = nativeBindings.__ompInstallTokioRuntime;
27
- export const __piNativesV16_1_14 = nativeBindings.__piNativesV16_1_14;
27
+ export const __piNativesV16_1_16 = nativeBindings.__piNativesV16_1_16;
28
28
  export const applyBashFixups = nativeBindings.applyBashFixups;
29
29
  export const astEdit = nativeBindings.astEdit;
30
30
  export const astGrep = nativeBindings.astGrep;
@@ -69,4 +69,21 @@ export interface ExtractEmbeddedAddonArchiveInput {
69
69
  }
70
70
 
71
71
  export function extractEmbeddedAddonArchive(input: ExtractEmbeddedAddonArchiveInput): string[];
72
+
73
+ export interface SelectCpuVariantInput {
74
+ arch: string;
75
+ override: "modern" | "baseline" | null | undefined;
76
+ env: Record<string, string | undefined>;
77
+ detectAvx2: () => boolean;
78
+ }
79
+
80
+ export interface SelectCpuVariantResult {
81
+ variant: "modern" | "baseline" | null;
82
+ source: "non-x64" | "override" | "cache" | "detect";
83
+ cacheEnvKey?: string;
84
+ cacheEnvValue?: string;
85
+ }
86
+
87
+ export function selectCpuVariant(input: SelectCpuVariantInput): SelectCpuVariantResult;
88
+
72
89
  export function loadNative(): Record<string, unknown>;
@@ -213,7 +213,33 @@ export function cleanupStaleNativeVersions({ nativesDir, currentVersion }) {
213
213
  // above pay nothing for variant detection, subprocess spawns, or fs probes.
214
214
  // =========================================================================
215
215
 
216
+ /**
217
+ * Hidden env key for the resolved x64 variant. Once any context (main thread,
218
+ * worker, subprocess) finishes variant detection, the result is written here
219
+ * so every Bun worker and child process spawned afterwards inherits the same
220
+ * verdict and skips re-detection. See `selectCpuVariant` for the lookup order.
221
+ */
222
+ const VARIANT_CACHE_ENV_KEY = "__PI_NATIVE_VARIANT_CACHE";
223
+
224
+ /**
225
+ * Spawn `command` with `args` and capture stdout. Prefers `Bun.spawnSync`
226
+ * because Bun's `child_process.spawnSync` shim has been observed to return
227
+ * non-zero / null in worker threads on macOS even when the same binary works
228
+ * fine from the parent — the failure mode behind issue #3238, where the worker
229
+ * silently falls back to the "baseline" variant. Falls back to the Node shim
230
+ * for non-Bun embeds.
231
+ */
216
232
  function runCommand(command, args) {
233
+ if (typeof Bun !== "undefined" && typeof Bun.spawnSync === "function") {
234
+ try {
235
+ const result = Bun.spawnSync([command, ...args], { stdout: "pipe", stderr: "pipe" });
236
+ if (result.exitCode === 0) {
237
+ return result.stdout.toString("utf-8").trim();
238
+ }
239
+ } catch {
240
+ // fall through to childProcess
241
+ }
242
+ }
217
243
  try {
218
244
  const result = childProcess.spawnSync(command, args, { encoding: "utf-8" });
219
245
  if (result.error) return null;
@@ -246,12 +272,15 @@ function detectAvx2Support() {
246
272
  }
247
273
 
248
274
  if (process.platform === "darwin") {
249
- const leaf7 = runCommand("sysctl", ["-n", "machdep.cpu.leaf7_features"]);
250
- if (leaf7 && /\bAVX2\b/i.test(leaf7)) {
251
- return true;
275
+ // Try the absolute path before bare `sysctl`: PATH may not include
276
+ // `/usr/sbin` in worker/embedded spawn contexts (issue #3238).
277
+ for (const sysctlBin of ["/usr/sbin/sysctl", "sysctl"]) {
278
+ const leaf7 = runCommand(sysctlBin, ["-n", "machdep.cpu.leaf7_features"]);
279
+ if (leaf7 && /\bAVX2\b/i.test(leaf7)) return true;
280
+ const features = runCommand(sysctlBin, ["-n", "machdep.cpu.features"]);
281
+ if (features && /\bAVX2\b/i.test(features)) return true;
252
282
  }
253
- const features = runCommand("sysctl", ["-n", "machdep.cpu.features"]);
254
- return Boolean(features && /\bAVX2\b/i.test(features));
283
+ return false;
255
284
  }
256
285
 
257
286
  if (process.platform === "win32") {
@@ -267,10 +296,63 @@ function detectAvx2Support() {
267
296
  return false;
268
297
  }
269
298
 
299
+ /**
300
+ * Pure variant-selection helper, exposed for unit tests. Resolution order:
301
+ *
302
+ * 1. `override` (user-facing `PI_NATIVE_VARIANT` env var). Always wins.
303
+ * 2. The private `__PI_NATIVE_VARIANT_CACHE` env var, populated by the first
304
+ * context that detected at runtime. Lets child workers / subprocesses
305
+ * inherit the main thread's verdict instead of re-spawning `sysctl` etc.
306
+ * from a worker context where the spawn may fail (issue #3238).
307
+ * 3. `detectAvx2()` — the slow path, called at most once per process.
308
+ *
309
+ * Non-x64 architectures return `{ variant: null }` and never set the cache.
310
+ * When detection runs, the result is surfaced as `cacheEnvKey`/`cacheEnvValue`
311
+ * so the caller can write `process.env` (the pure helper itself stays
312
+ * side-effect-free, which keeps it easy to test).
313
+ *
314
+ * @param {{
315
+ * arch: string;
316
+ * override: "modern" | "baseline" | null | undefined;
317
+ * env: Record<string, string | undefined>;
318
+ * detectAvx2: () => boolean;
319
+ * }} input
320
+ * @returns {{
321
+ * variant: "modern" | "baseline" | null;
322
+ * source: "non-x64" | "override" | "cache" | "detect";
323
+ * cacheEnvKey?: string;
324
+ * cacheEnvValue?: string;
325
+ * }}
326
+ */
327
+ export function selectCpuVariant({ arch, override, env, detectAvx2 }) {
328
+ if (arch !== "x64") return { variant: null, source: "non-x64" };
329
+ if (override === "modern" || override === "baseline") {
330
+ return { variant: override, source: "override" };
331
+ }
332
+ const cached = env[VARIANT_CACHE_ENV_KEY];
333
+ if (cached === "modern" || cached === "baseline") {
334
+ return { variant: cached, source: "cache" };
335
+ }
336
+ const variant = detectAvx2() ? "modern" : "baseline";
337
+ return {
338
+ variant,
339
+ source: "detect",
340
+ cacheEnvKey: VARIANT_CACHE_ENV_KEY,
341
+ cacheEnvValue: variant,
342
+ };
343
+ }
344
+
270
345
  function resolveCpuVariant(override) {
271
- if (process.arch !== "x64") return null;
272
- if (override) return override;
273
- return detectAvx2Support() ? "modern" : "baseline";
346
+ const result = selectCpuVariant({
347
+ arch: process.arch,
348
+ override,
349
+ env: process.env,
350
+ detectAvx2: detectAvx2Support,
351
+ });
352
+ if (result.cacheEnvKey) {
353
+ process.env[result.cacheEnvKey] = result.cacheEnvValue;
354
+ }
355
+ return result.variant;
274
356
  }
275
357
 
276
358
  function selectEmbeddedAddonFile(selectedVariant) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-natives",
3
- "version": "16.1.14",
3
+ "version": "16.1.16",
4
4
  "description": "Native Rust bindings for grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
5
5
  "type": "module",
6
6
  "homepage": "https://omp.sh",
@@ -66,10 +66,10 @@
66
66
  }
67
67
  },
68
68
  "optionalDependencies": {
69
- "@oh-my-pi/pi-natives-linux-x64": "16.1.14",
70
- "@oh-my-pi/pi-natives-linux-arm64": "16.1.14",
71
- "@oh-my-pi/pi-natives-darwin-x64": "16.1.14",
72
- "@oh-my-pi/pi-natives-darwin-arm64": "16.1.14",
73
- "@oh-my-pi/pi-natives-win32-x64": "16.1.14"
69
+ "@oh-my-pi/pi-natives-linux-x64": "16.1.16",
70
+ "@oh-my-pi/pi-natives-linux-arm64": "16.1.16",
71
+ "@oh-my-pi/pi-natives-darwin-x64": "16.1.16",
72
+ "@oh-my-pi/pi-natives-darwin-arm64": "16.1.16",
73
+ "@oh-my-pi/pi-natives-win32-x64": "16.1.16"
74
74
  }
75
75
  }