@oh-my-pi/pi-utils 16.3.10 → 16.3.12

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.3.12] - 2026-07-08
6
+
7
+ ### Added
8
+
9
+ - Added `postmortem.interceptUnhandledRejections()` to register interceptors consulted before an unhandled rejection tears the process down; a consuming interceptor (e.g. the JS eval runtime claiming rejections floated by user cell code) keeps the process alive and owns reporting.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed child shell environment filtering to drop launch-directory `.env.local` values that Bun auto-loaded before OMP starts command shells. ([#4723](https://github.com/can1357/oh-my-pi/issues/4723))
14
+
5
15
  ## [16.3.10] - 2026-07-06
6
16
 
7
17
  ### Added
@@ -17,6 +17,8 @@ export declare function isSafeEnvName(name: string): boolean;
17
17
  export declare function isSafeEnvValue(value: string): boolean;
18
18
  export declare function isMacosMallocStackLoggingEnvName(name: string): boolean;
19
19
  export declare function filterProcessEnv(env: Record<string, string | undefined>): Record<string, string>;
20
+ /** Filters process env for child shells without launch-cwd `.env.local` values. */
21
+ export declare function filterChildShellEnv(env: Record<string, string | undefined>, cwd?: string): Record<string, string>;
20
22
  /**
21
23
  * Parses a .env file synchronously and extracts key-value string pairs.
22
24
  * Ignores lines that are empty or start with '#'. Trims whitespace.
@@ -31,6 +31,14 @@ export declare function markExpectedCleanupError<T extends object>(reason: T): T
31
31
  * its `cause`.
32
32
  */
33
33
  export declare function isExpectedCleanupError(reason: unknown): boolean;
34
+ /**
35
+ * Register an interceptor consulted before an unhandled rejection tears the
36
+ * process down. Return `true` to consume the rejection — the interceptor owns
37
+ * reporting and the process continues. Used by embedded script runtimes (JS
38
+ * eval cells) whose user code can float rejections the host must not die for.
39
+ * Returns an unregister function.
40
+ */
41
+ export declare function interceptUnhandledRejections(interceptor: (reason: unknown) => boolean): () => void;
34
42
  /**
35
43
  * Register a process cleanup callback, to be run on shutdown, signal, or fatal error.
36
44
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "16.3.10",
4
+ "version": "16.3.12",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "16.3.10",
34
+ "@oh-my-pi/pi-natives": "16.3.12",
35
35
  "handlebars": "^4.7.9",
36
36
  "winston": "^3.19.0",
37
37
  "winston-daily-rotate-file": "^5.0.0"
package/src/env.ts CHANGED
@@ -53,6 +53,19 @@ export function filterProcessEnv(env: Record<string, string | undefined>): Recor
53
53
  return result;
54
54
  }
55
55
 
56
+ /** Filters process env for child shells without launch-cwd `.env.local` values. */
57
+ export function filterChildShellEnv(
58
+ env: Record<string, string | undefined>,
59
+ cwd: string = process.cwd(),
60
+ ): Record<string, string> {
61
+ const result = filterProcessEnv(env);
62
+ const launchLocalEnv = parseEnvFile(path.join(cwd, ".env.local"));
63
+ for (const key in launchLocalEnv) {
64
+ if (result[key] === launchLocalEnv[key]) delete result[key];
65
+ }
66
+ return result;
67
+ }
68
+
56
69
  /**
57
70
  * Parses a .env file synchronously and extracts key-value string pairs.
58
71
  * Ignores lines that are empty or start with '#'. Trims whitespace.
package/src/postmortem.ts CHANGED
@@ -121,6 +121,24 @@ export function isExpectedCleanupError(reason: unknown): boolean {
121
121
  return false;
122
122
  }
123
123
 
124
+ /**
125
+ * Interceptors consulted by the global `unhandledRejection` handler before the
126
+ * fatal path. See {@link interceptUnhandledRejections}.
127
+ */
128
+ const rejectionInterceptors = new Set<(reason: unknown) => boolean>();
129
+
130
+ /**
131
+ * Register an interceptor consulted before an unhandled rejection tears the
132
+ * process down. Return `true` to consume the rejection — the interceptor owns
133
+ * reporting and the process continues. Used by embedded script runtimes (JS
134
+ * eval cells) whose user code can float rejections the host must not die for.
135
+ * Returns an unregister function.
136
+ */
137
+ export function interceptUnhandledRejections(interceptor: (reason: unknown) => boolean): () => void {
138
+ rejectionInterceptors.add(interceptor);
139
+ return () => rejectionInterceptors.delete(interceptor);
140
+ }
141
+
124
142
  function formatFatalError(label: string, err: Error): string {
125
143
  const name = err.name || "Error";
126
144
  const message = err.message || "(no message)";
@@ -172,6 +190,15 @@ if (isMainThread) {
172
190
  logger.warn("Ignoring expected cleanup rejection", { err });
173
191
  return;
174
192
  }
193
+ for (const interceptor of rejectionInterceptors) {
194
+ try {
195
+ if (interceptor(reason)) return;
196
+ } catch (interceptorErr) {
197
+ logger.warn("Unhandled-rejection interceptor threw; continuing with fatal path", {
198
+ err: interceptorErr,
199
+ });
200
+ }
201
+ }
175
202
  process.stderr.write(formatFatalError("Unhandled Rejection", err));
176
203
  logger.error("Unhandled rejection", { err });
177
204
  await runCleanup(Reason.UNHANDLED_REJECTION);
package/src/procmgr.ts CHANGED
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { Process, ProcessStatus } from "@oh-my-pi/pi-natives";
4
4
  import type { Subprocess } from "bun";
5
- import { $env, filterProcessEnv } from "./env";
5
+ import { $env, filterChildShellEnv } from "./env";
6
6
  import { $which } from "./which";
7
7
 
8
8
  export interface ShellConfig {
@@ -31,7 +31,7 @@ export function isExecutable(path: string): boolean {
31
31
  function buildSpawnEnv(shell: string): Record<string, string> {
32
32
  const noCI = $env.PI_BASH_NO_CI || $env.CLAUDE_BASH_NO_CI;
33
33
  return {
34
- ...filterProcessEnv(Bun.env),
34
+ ...filterChildShellEnv(Bun.env),
35
35
  SHELL: shell,
36
36
  GIT_EDITOR: "true",
37
37
  GPG_TTY: "not a tty",