@gajae-code/utils 0.1.2 → 0.2.0
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 +9 -0
- package/package.json +4 -4
- package/src/env.ts +75 -1
package/dist/types/env.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ export declare function isValidEnvName(name: string): boolean;
|
|
|
15
15
|
export declare function isSafeEnvName(name: string): boolean;
|
|
16
16
|
export declare function isSafeEnvValue(value: string): boolean;
|
|
17
17
|
export declare function filterProcessEnv(env: Record<string, string | undefined>): Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Parses simple POSIX shell environment assignments from files such as
|
|
20
|
+
* ~/.zshrc without executing user shell code. Supports `export KEY=value` and
|
|
21
|
+
* `KEY=value`, including single/double quoted literal values. Dynamic shell
|
|
22
|
+
* expressions are intentionally ignored because evaluating startup files would
|
|
23
|
+
* run arbitrary code during CLI startup.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseShellEnvFile(filePath: string): Record<string, string>;
|
|
18
26
|
/**
|
|
19
27
|
* Parses a .env file synchronously and extracts key-value string pairs.
|
|
20
28
|
* Ignores lines that are empty or start with '#'. Trims whitespace.
|
|
@@ -22,6 +30,7 @@ export declare function filterProcessEnv(env: Record<string, string | undefined>
|
|
|
22
30
|
* Returns an object of key-value pairs.
|
|
23
31
|
*/
|
|
24
32
|
export declare function parseEnvFile(filePath: string): Record<string, string>;
|
|
33
|
+
export declare function $inheritedEnv(name: string): string | undefined;
|
|
25
34
|
/**
|
|
26
35
|
* Intentional re-export of Bun.env.
|
|
27
36
|
*
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/utils",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
|
-
"homepage": "https://gajae
|
|
7
|
-
"author": "
|
|
6
|
+
"homepage": "https://gaebal-gajae.dev",
|
|
7
|
+
"author": "Yeachan-Heo",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@gajae-code/natives": "0.
|
|
34
|
+
"@gajae-code/natives": "0.2.0",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/env.ts
CHANGED
|
@@ -40,6 +40,63 @@ export function filterProcessEnv(env: Record<string, string | undefined>): Recor
|
|
|
40
40
|
return result;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function stripInlineShellComment(value: string): string {
|
|
44
|
+
let quote: '"' | "'" | undefined;
|
|
45
|
+
for (let i = 0; i < value.length; i++) {
|
|
46
|
+
const char = value[i];
|
|
47
|
+
if (char === "\\") {
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if ((char === '"' || char === "'") && (!quote || quote === char)) {
|
|
52
|
+
quote = quote ? undefined : char;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === "#" && !quote && (i === 0 || /\s/.test(value[i - 1] ?? ""))) {
|
|
56
|
+
return value.slice(0, i).trimEnd();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return value.trimEnd();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses simple POSIX shell environment assignments from files such as
|
|
64
|
+
* ~/.zshrc without executing user shell code. Supports `export KEY=value` and
|
|
65
|
+
* `KEY=value`, including single/double quoted literal values. Dynamic shell
|
|
66
|
+
* expressions are intentionally ignored because evaluating startup files would
|
|
67
|
+
* run arbitrary code during CLI startup.
|
|
68
|
+
*/
|
|
69
|
+
export function parseShellEnvFile(filePath: string): Record<string, string> {
|
|
70
|
+
const result: Record<string, string> = {};
|
|
71
|
+
try {
|
|
72
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
73
|
+
for (const line of content.split("\n")) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
76
|
+
|
|
77
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
|
|
78
|
+
if (!match) continue;
|
|
79
|
+
|
|
80
|
+
const key = match[1];
|
|
81
|
+
if (!isValidEnvName(key)) continue;
|
|
82
|
+
|
|
83
|
+
let value = stripInlineShellComment(match[2] ?? "").trim();
|
|
84
|
+
if (value.endsWith(";")) value = value.slice(0, -1).trimEnd();
|
|
85
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
86
|
+
value = value.slice(1, -1);
|
|
87
|
+
}
|
|
88
|
+
if (!isSafeEnvValue(value)) continue;
|
|
89
|
+
if (/[$`]/.test(value)) continue;
|
|
90
|
+
|
|
91
|
+
result[key] = value;
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// File doesn't exist or can't be read - return empty result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
43
100
|
/**
|
|
44
101
|
* Parses a .env file synchronously and extracts key-value string pairs.
|
|
45
102
|
* Ignores lines that are empty or start with '#'. Trims whitespace.
|
|
@@ -78,7 +135,24 @@ export function parseEnvFile(filePath: string): Record<string, string> {
|
|
|
78
135
|
return result;
|
|
79
136
|
}
|
|
80
137
|
|
|
138
|
+
const inheritedEnv = filterProcessEnv(Bun.env);
|
|
139
|
+
|
|
140
|
+
export function $inheritedEnv(name: string): string | undefined {
|
|
141
|
+
if (!isSafeEnvName(name)) return undefined;
|
|
142
|
+
const value = inheritedEnv[name];
|
|
143
|
+
if (value === undefined || !isSafeEnvValue(value)) return undefined;
|
|
144
|
+
const trimmed = value.trim();
|
|
145
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
// Eagerly parse the user's $HOME/.env and the current project's .env (from cwd)
|
|
149
|
+
const homeShellEnv = {
|
|
150
|
+
...parseShellEnvFile(path.join(os.homedir(), ".zshenv")),
|
|
151
|
+
...parseShellEnvFile(path.join(os.homedir(), ".zprofile")),
|
|
152
|
+
...parseShellEnvFile(path.join(os.homedir(), ".zshrc")),
|
|
153
|
+
...parseShellEnvFile(path.join(os.homedir(), ".bash_profile")),
|
|
154
|
+
...parseShellEnvFile(path.join(os.homedir(), ".bashrc")),
|
|
155
|
+
};
|
|
82
156
|
const homeEnv = parseEnvFile(path.join(os.homedir(), ".env"));
|
|
83
157
|
const piEnv = parseEnvFile(path.join(getConfigRootDir(), ".env"));
|
|
84
158
|
const agentEnv = parseEnvFile(path.join(getAgentDir(), ".env"));
|
|
@@ -91,7 +165,7 @@ for (const key of Object.keys(Bun.env)) {
|
|
|
91
165
|
}
|
|
92
166
|
}
|
|
93
167
|
|
|
94
|
-
for (const file of [projectEnv, agentEnv, piEnv, homeEnv]) {
|
|
168
|
+
for (const file of [projectEnv, agentEnv, piEnv, homeEnv, homeShellEnv]) {
|
|
95
169
|
for (const key in file) {
|
|
96
170
|
if (!Bun.env[key]) {
|
|
97
171
|
Bun.env[key] = file[key];
|