@gajae-code/utils 0.4.4 → 0.5.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.
@@ -18,6 +18,22 @@ export declare const CONFIG_DIR_NAME: string;
18
18
  export declare const VERSION: string;
19
19
  /** Minimum Bun version */
20
20
  export declare const MIN_BUN_VERSION: string;
21
+ /**
22
+ * Build the diagnostic shown when the Bun runtime executing `gjc` is older
23
+ * than {@link MIN_BUN_VERSION}. This is the most common Windows native-install
24
+ * failure (issue #525): `bun install -g gajae-code` probes a recent Bun while
25
+ * the `gjc` launcher resolves an older Bun still on PATH. The message names the
26
+ * exact detected runtime path and gives a platform-specific upgrade + PATH fix
27
+ * instead of a bare `bun upgrade`.
28
+ *
29
+ * Pure and platform-parameterized so it can be unit-tested cross-platform.
30
+ */
31
+ export declare function formatBunRuntimeError(opts: {
32
+ currentVersion: string;
33
+ minVersion: string;
34
+ execPath?: string;
35
+ platform?: NodeJS.Platform;
36
+ }): string;
21
37
  export declare function resolveEquivalentPath(inputPath: string): string;
22
38
  export declare function normalizePathForComparison(inputPath: string): string;
23
39
  export declare function pathIsWithin(root: string, candidate: string): boolean;
@@ -37,6 +37,9 @@ export declare function $inheritedEnv(name: string): string | undefined;
37
37
  * All users should import this env module (import { $env } from "@gajae-code/utils")
38
38
  * before using environment variables. This ensures that .env files have been loaded and
39
39
  * overrides (project, home) have been applied, so $env always reflects the correct values.
40
+ *
41
+ * Provider credential resolution must not use this merged view because it includes the
42
+ * caller's cwd/.env. Use $credentialEnv/$pickCredentialEnv for model authentication.
40
43
  */
41
44
  export declare const $env: Record<string, string>;
42
45
  /**
@@ -45,6 +48,17 @@ export declare const $env: Record<string, string>;
45
48
  * @returns The first environment variable value, or undefined if no value is found.
46
49
  */
47
50
  export declare function $pickenv(...keys: string[]): string | undefined;
51
+ /**
52
+ * Resolve credential-bearing environment variables without consulting the caller's project .env.
53
+ *
54
+ * GJC loads cwd/.env into $env for project-aware tools, but model-provider authentication should
55
+ * only use values explicitly inherited from the launching shell or GJC/user-owned config files.
56
+ */
57
+ export declare function $credentialEnv(name: string): string | undefined;
58
+ /**
59
+ * Resolve the first credential env value from the given keys, excluding cwd/.env overlays.
60
+ */
61
+ export declare function $pickCredentialEnv(...keys: string[]): string | undefined;
48
62
  /**
49
63
  * Parses a positive decimal integer from `$env[name]`.
50
64
  * Empty, invalid, NaN, zero, or negative values return `defaultValue`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/utils",
4
- "version": "0.4.4",
4
+ "version": "0.5.0",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@gajae-code/natives": "0.4.4",
34
+ "@gajae-code/natives": "0.5.0",
35
35
  "beautiful-mermaid": "^1.1.3",
36
36
  "handlebars": "^4.7.9",
37
37
  "winston": "^3.19.0",
package/src/dirs.ts CHANGED
@@ -28,6 +28,58 @@ export const VERSION: string = version;
28
28
  /** Minimum Bun version */
29
29
  export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
30
30
 
31
+ /**
32
+ * Build the diagnostic shown when the Bun runtime executing `gjc` is older
33
+ * than {@link MIN_BUN_VERSION}. This is the most common Windows native-install
34
+ * failure (issue #525): `bun install -g gajae-code` probes a recent Bun while
35
+ * the `gjc` launcher resolves an older Bun still on PATH. The message names the
36
+ * exact detected runtime path and gives a platform-specific upgrade + PATH fix
37
+ * instead of a bare `bun upgrade`.
38
+ *
39
+ * Pure and platform-parameterized so it can be unit-tested cross-platform.
40
+ */
41
+ export function formatBunRuntimeError(opts: {
42
+ currentVersion: string;
43
+ minVersion: string;
44
+ execPath?: string;
45
+ platform?: NodeJS.Platform;
46
+ }): string {
47
+ const platform = opts.platform ?? process.platform;
48
+ const lines = [
49
+ `error: ${APP_NAME} requires Bun >= ${opts.minVersion}, but the running Bun is v${opts.currentVersion}.`,
50
+ ];
51
+ if (opts.execPath) {
52
+ lines.push(` detected Bun runtime: ${opts.execPath}`);
53
+ }
54
+ if (platform === "win32") {
55
+ lines.push(
56
+ "",
57
+ "The 'gjc' launcher is using an older Bun than the one used to install it.",
58
+ "Upgrade Bun, then restart your terminal so PATH and the runtime refresh:",
59
+ "",
60
+ ' powershell -c "irm bun.sh/install.ps1|iex"',
61
+ "",
62
+ "After restarting the terminal, verify both versions match:",
63
+ " bun --version",
64
+ " gjc --version",
65
+ "",
66
+ "If 'gjc' still loads the old runtime, make sure %USERPROFILE%\\.bun\\bin is",
67
+ "first on PATH and remove any stale Bun installs shadowing it.",
68
+ );
69
+ } else {
70
+ lines.push(
71
+ "",
72
+ "Upgrade Bun, then restart your terminal:",
73
+ " bun upgrade",
74
+ "",
75
+ "Then verify:",
76
+ " bun --version",
77
+ " gjc --version",
78
+ );
79
+ }
80
+ return `${lines.join("\n")}\n`;
81
+ }
82
+
31
83
  // =============================================================================
32
84
  // Project directory
33
85
  // =============================================================================
package/src/env.ts CHANGED
@@ -135,16 +135,32 @@ export function parseEnvFile(filePath: string): Record<string, string> {
135
135
  return result;
136
136
  }
137
137
 
138
- const inheritedEnv = filterProcessEnv(Bun.env);
139
-
140
- export function $inheritedEnv(name: string): string | undefined {
138
+ function resolveFileEnvValue(file: Record<string, string>, name: string): string | undefined {
141
139
  if (!isSafeEnvName(name)) return undefined;
142
- const value = inheritedEnv[name];
140
+ const value = file[name];
143
141
  if (value === undefined || !isSafeEnvValue(value)) return undefined;
144
142
  const trimmed = value.trim();
145
143
  return trimmed.length > 0 ? trimmed : undefined;
146
144
  }
147
145
 
146
+ function filterCredentialInheritedEnv(env: Record<string, string | undefined>): Record<string, string> {
147
+ const result: Record<string, string> = {};
148
+ for (const key in env) {
149
+ const value = env[key];
150
+ if (!isSafeEnvName(key) || value === undefined || !isSafeEnvValue(value)) continue;
151
+
152
+ // Bun may have already loaded cwd/.env before JS runs. It does not expose the
153
+ // source of each entry, so an exact match with projectEnv is ambiguous. Use
154
+ // the safer credential rule: ambiguous project matches are excluded from the
155
+ // credential-only inherited snapshot, while remaining available through $env.
156
+ const projectValue = resolveFileEnvValue(projectEnv, key);
157
+ if (projectValue !== undefined && projectValue === value) continue;
158
+
159
+ result[key] = value;
160
+ }
161
+ return result;
162
+ }
163
+
148
164
  // Eagerly parse the user's $HOME/.env and the current project's .env (from cwd)
149
165
  const homeShellEnv = {
150
166
  ...parseShellEnvFile(path.join(os.homedir(), ".zshenv")),
@@ -158,11 +174,29 @@ const piEnv = parseEnvFile(path.join(getConfigRootDir(), ".env"));
158
174
  const agentEnv = parseEnvFile(path.join(getAgentDir(), ".env"));
159
175
  const projectEnv = parseEnvFile(path.join(process.cwd(), ".env"));
160
176
 
161
- for (const key of Object.keys(Bun.env)) {
162
- const value = Bun.env[key];
163
- if (!isSafeEnvName(key) || value === undefined || !isSafeEnvValue(value)) {
164
- delete Bun.env[key];
177
+ const inheritedEnv = filterCredentialInheritedEnv(Bun.env);
178
+
179
+ export function $inheritedEnv(name: string): string | undefined {
180
+ return resolveFileEnvValue(inheritedEnv, name);
181
+ }
182
+
183
+ function resolveLiveCredentialEnvValue(name: string): string | undefined {
184
+ if (!isSafeEnvName(name)) return undefined;
185
+ const value = Bun.env[name];
186
+ if (value === undefined || !isSafeEnvValue(value)) return undefined;
187
+ const trimmed = value.trim();
188
+ if (trimmed.length === 0) return undefined;
189
+
190
+ const projectValue = resolveFileEnvValue(projectEnv, name);
191
+ if (
192
+ projectValue !== undefined &&
193
+ projectValue === trimmed &&
194
+ resolveFileEnvValue(inheritedEnv, name) === undefined
195
+ ) {
196
+ return undefined;
165
197
  }
198
+
199
+ return trimmed;
166
200
  }
167
201
 
168
202
  for (const file of [projectEnv, agentEnv, piEnv, homeEnv, homeShellEnv]) {
@@ -179,6 +213,9 @@ for (const file of [projectEnv, agentEnv, piEnv, homeEnv, homeShellEnv]) {
179
213
  * All users should import this env module (import { $env } from "@gajae-code/utils")
180
214
  * before using environment variables. This ensures that .env files have been loaded and
181
215
  * overrides (project, home) have been applied, so $env always reflects the correct values.
216
+ *
217
+ * Provider credential resolution must not use this merged view because it includes the
218
+ * caller's cwd/.env. Use $credentialEnv/$pickCredentialEnv for model authentication.
182
219
  */
183
220
  export const $env: Record<string, string> = Bun.env as Record<string, string>;
184
221
 
@@ -197,6 +234,34 @@ export function $pickenv(...keys: string[]): string | undefined {
197
234
  return undefined;
198
235
  }
199
236
 
237
+ /**
238
+ * Resolve credential-bearing environment variables without consulting the caller's project .env.
239
+ *
240
+ * GJC loads cwd/.env into $env for project-aware tools, but model-provider authentication should
241
+ * only use values explicitly inherited from the launching shell or GJC/user-owned config files.
242
+ */
243
+ export function $credentialEnv(name: string): string | undefined {
244
+ return (
245
+ $inheritedEnv(name) ??
246
+ resolveLiveCredentialEnvValue(name) ??
247
+ resolveFileEnvValue(agentEnv, name) ??
248
+ resolveFileEnvValue(piEnv, name) ??
249
+ resolveFileEnvValue(homeEnv, name) ??
250
+ resolveFileEnvValue(homeShellEnv, name)
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Resolve the first credential env value from the given keys, excluding cwd/.env overlays.
256
+ */
257
+ export function $pickCredentialEnv(...keys: string[]): string | undefined {
258
+ for (const key of keys) {
259
+ const value = $credentialEnv(key);
260
+ if (value) return value;
261
+ }
262
+ return undefined;
263
+ }
264
+
200
265
  /**
201
266
  * Parses a positive decimal integer from `$env[name]`.
202
267
  * Empty, invalid, NaN, zero, or negative values return `defaultValue`.