@oh-my-pi/pi-utils 15.1.3 → 15.1.5
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/dist/types/env.d.ts +24 -0
- package/dist/types/fetch-retry.d.ts +1 -1
- package/package.json +2 -2
- package/src/env.ts +50 -3
- package/src/fetch-retry.ts +2 -1
- package/src/procmgr.ts +2 -2
package/dist/types/env.d.ts
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict shell-identifier shape. Used for dotenv keys we accept into
|
|
3
|
+
* `Bun.env` — those should be referenceable as `$NAME` from POSIX shells,
|
|
4
|
+
* so we reject anything outside `[A-Za-z_][A-Za-z0-9_]*`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isValidEnvName(name: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The only names that are genuinely unsafe to forward to a native `execve`
|
|
9
|
+
* spawn: empty, containing `=` (would corrupt the `KEY=VALUE` framing) or
|
|
10
|
+
* NUL (terminates the C string mid-entry). Windows ships standard variables
|
|
11
|
+
* whose names contain parentheses (e.g. `ProgramFiles(x86)`, `CommonProgramFiles(x86)`)
|
|
12
|
+
* — those MUST survive the scrub so downstream resolvers (Git Bash discovery
|
|
13
|
+
* in `procmgr.ts`, etc.) can still read them.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isSafeEnvName(name: string): boolean;
|
|
16
|
+
export declare function isSafeEnvValue(value: string): boolean;
|
|
17
|
+
export declare function filterProcessEnv(env: Record<string, string | undefined>): Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Parses a .env file synchronously and extracts key-value string pairs.
|
|
20
|
+
* Ignores lines that are empty or start with '#'. Trims whitespace.
|
|
21
|
+
* Allows values to be quoted with single or double quotes.
|
|
22
|
+
* Returns an object of key-value pairs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseEnvFile(filePath: string): Record<string, string>;
|
|
1
25
|
/**
|
|
2
26
|
* Intentional re-export of Bun.env.
|
|
3
27
|
*
|
|
@@ -58,7 +58,7 @@ export declare function fetchWithRetry(url: string | URL | ((attempt: number) =>
|
|
|
58
58
|
* Inspect an arbitrary error value (or its `cause` chain, up to depth 2) for an
|
|
59
59
|
* HTTP status code. Reads `status`, `statusCode`, and `response.status` fields,
|
|
60
60
|
* coerces string values, and falls back to scanning the error message for
|
|
61
|
-
* common patterns like `error (429)
|
|
61
|
+
* common patterns like `Error: 401`, `error (429)`, or `HTTP 503`.
|
|
62
62
|
*/
|
|
63
63
|
export declare function extractHttpStatusFromError(error: unknown): number | undefined;
|
|
64
64
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-utils",
|
|
4
|
-
"version": "15.1.
|
|
4
|
+
"version": "15.1.5",
|
|
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": "15.1.
|
|
34
|
+
"@oh-my-pi/pi-natives": "15.1.5",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/env.ts
CHANGED
|
@@ -3,13 +3,50 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { getAgentDir, getConfigRootDir } from "./dirs";
|
|
5
5
|
|
|
6
|
+
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Strict shell-identifier shape. Used for dotenv keys we accept into
|
|
10
|
+
* `Bun.env` — those should be referenceable as `$NAME` from POSIX shells,
|
|
11
|
+
* so we reject anything outside `[A-Za-z_][A-Za-z0-9_]*`.
|
|
12
|
+
*/
|
|
13
|
+
export function isValidEnvName(name: string): boolean {
|
|
14
|
+
return ENV_NAME_RE.test(name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The only names that are genuinely unsafe to forward to a native `execve`
|
|
19
|
+
* spawn: empty, containing `=` (would corrupt the `KEY=VALUE` framing) or
|
|
20
|
+
* NUL (terminates the C string mid-entry). Windows ships standard variables
|
|
21
|
+
* whose names contain parentheses (e.g. `ProgramFiles(x86)`, `CommonProgramFiles(x86)`)
|
|
22
|
+
* — those MUST survive the scrub so downstream resolvers (Git Bash discovery
|
|
23
|
+
* in `procmgr.ts`, etc.) can still read them.
|
|
24
|
+
*/
|
|
25
|
+
export function isSafeEnvName(name: string): boolean {
|
|
26
|
+
return name.length > 0 && !name.includes("=") && !name.includes("\0");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isSafeEnvValue(value: string): boolean {
|
|
30
|
+
return !value.includes("\0");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function filterProcessEnv(env: Record<string, string | undefined>): Record<string, string> {
|
|
34
|
+
const result: Record<string, string> = {};
|
|
35
|
+
for (const key in env) {
|
|
36
|
+
const value = env[key];
|
|
37
|
+
if (!isSafeEnvName(key) || value === undefined || !isSafeEnvValue(value)) continue;
|
|
38
|
+
result[key] = value;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
6
43
|
/**
|
|
7
44
|
* Parses a .env file synchronously and extracts key-value string pairs.
|
|
8
45
|
* Ignores lines that are empty or start with '#'. Trims whitespace.
|
|
9
46
|
* Allows values to be quoted with single or double quotes.
|
|
10
47
|
* Returns an object of key-value pairs.
|
|
11
48
|
*/
|
|
12
|
-
function parseEnvFile(filePath: string): Record<string, string> {
|
|
49
|
+
export function parseEnvFile(filePath: string): Record<string, string> {
|
|
13
50
|
const result: Record<string, string> = {};
|
|
14
51
|
try {
|
|
15
52
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -22,12 +59,15 @@ function parseEnvFile(filePath: string): Record<string, string> {
|
|
|
22
59
|
if (eqIndex === -1) continue;
|
|
23
60
|
|
|
24
61
|
const key = trimmed.slice(0, eqIndex).trim();
|
|
62
|
+
if (!isValidEnvName(key)) continue;
|
|
63
|
+
|
|
25
64
|
let value = trimmed.slice(eqIndex + 1).trim();
|
|
26
65
|
|
|
27
66
|
// Remove surrounding quotes (" or ')
|
|
28
67
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
29
68
|
value = value.slice(1, -1);
|
|
30
69
|
}
|
|
70
|
+
if (!isSafeEnvValue(value)) continue;
|
|
31
71
|
|
|
32
72
|
result[key] = value;
|
|
33
73
|
}
|
|
@@ -51,10 +91,17 @@ const piEnv = parseEnvFile(path.join(getConfigRootDir(), ".env"));
|
|
|
51
91
|
const agentEnv = parseEnvFile(path.join(getAgentDir(), ".env"));
|
|
52
92
|
const projectEnv = parseEnvFile(path.join(process.cwd(), ".env"));
|
|
53
93
|
|
|
94
|
+
for (const key of Object.keys(Bun.env)) {
|
|
95
|
+
const value = Bun.env[key];
|
|
96
|
+
if (!isSafeEnvName(key) || value === undefined || !isSafeEnvValue(value)) {
|
|
97
|
+
delete Bun.env[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
54
101
|
for (const file of [projectEnv, agentEnv, piEnv, homeEnv]) {
|
|
55
|
-
for (const
|
|
102
|
+
for (const key in file) {
|
|
56
103
|
if (!Bun.env[key]) {
|
|
57
|
-
Bun.env[key] =
|
|
104
|
+
Bun.env[key] = file[key];
|
|
58
105
|
}
|
|
59
106
|
}
|
|
60
107
|
}
|
package/src/fetch-retry.ts
CHANGED
|
@@ -198,7 +198,7 @@ function resolveDefaultDelay(
|
|
|
198
198
|
* Inspect an arbitrary error value (or its `cause` chain, up to depth 2) for an
|
|
199
199
|
* HTTP status code. Reads `status`, `statusCode`, and `response.status` fields,
|
|
200
200
|
* coerces string values, and falls back to scanning the error message for
|
|
201
|
-
* common patterns like `error (429)
|
|
201
|
+
* common patterns like `Error: 401`, `error (429)`, or `HTTP 503`.
|
|
202
202
|
*/
|
|
203
203
|
export function extractHttpStatusFromError(error: unknown): number | undefined {
|
|
204
204
|
return extractHttpStatusFromErrorInternal(error, 0);
|
|
@@ -236,6 +236,7 @@ function extractHttpStatusFromErrorInternal(error: unknown, depth: number): numb
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
const STATUS_MESSAGE_PATTERNS = [
|
|
239
|
+
/\berror\s*[:=]\s*(\d{3})\b/i,
|
|
239
240
|
/error\s*\((\d{3})\)/i,
|
|
240
241
|
/status\s*[:=]?\s*(\d{3})/i,
|
|
241
242
|
/\bhttp\s*(\d{3})\b/i,
|
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 } from "./env";
|
|
5
|
+
import { $env, filterProcessEnv } from "./env";
|
|
6
6
|
import { $which } from "./which";
|
|
7
7
|
|
|
8
8
|
export interface ShellConfig {
|
|
@@ -45,7 +45,7 @@ function isExecutable(path: string): boolean {
|
|
|
45
45
|
function buildSpawnEnv(shell: string): Record<string, string> {
|
|
46
46
|
const noCI = $env.PI_BASH_NO_CI || $env.CLAUDE_BASH_NO_CI;
|
|
47
47
|
return {
|
|
48
|
-
...Bun.env,
|
|
48
|
+
...filterProcessEnv(Bun.env),
|
|
49
49
|
SHELL: shell,
|
|
50
50
|
GIT_EDITOR: "true",
|
|
51
51
|
GPG_TTY: "not a tty",
|