@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.
@@ -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.1.2",
4
+ "version": "0.2.0",
5
5
  "description": "Shared utilities for pi packages",
6
- "homepage": "https://gajae-code.dev",
7
- "author": "Can Boluk",
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.1.2",
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];