@gajae-code/utils 0.17.1 → 0.17.2
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/dirs.d.ts +25 -8
- package/dist/types/env-file.d.ts +37 -0
- package/package.json +2 -2
- package/src/dirs.ts +51 -47
- package/src/env-file.ts +63 -0
- package/src/logger.ts +15 -3
package/dist/types/dirs.d.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* move data to the new locations. No filesystem existence checks are performed
|
|
12
12
|
* — if the env var is set, gjc trusts that the migration has been done.
|
|
13
13
|
*/
|
|
14
|
+
import { canonicalEnvKey } from "./env-file";
|
|
15
|
+
export { canonicalEnvKey };
|
|
14
16
|
/** App name (e.g. "gjc") */
|
|
15
17
|
export declare const APP_NAME: string;
|
|
16
18
|
/** Config directory name (e.g. ".gjc") */
|
|
@@ -50,14 +52,6 @@ export declare function relativePathWithinRoot(root: string, candidate: string):
|
|
|
50
52
|
export declare function getProjectDir(): string;
|
|
51
53
|
/** Set the project directory. */
|
|
52
54
|
export declare function setProjectDir(dir: string): void;
|
|
53
|
-
/**
|
|
54
|
-
* Windows environment variable names are case-insensitive, so a project dotenv
|
|
55
|
-
* line `userprofile=...` is what `process.env.USERPROFILE` resolves to. Every
|
|
56
|
-
* provenance lookup here is spelled in upper case, so the snapshot must be
|
|
57
|
-
* keyed the same way or the declaration is invisible to the guard while still
|
|
58
|
-
* being live in the process. POSIX names are case-sensitive and must not fold.
|
|
59
|
-
*/
|
|
60
|
-
export declare function canonicalEnvKey(name: string): string;
|
|
61
55
|
export declare function getConfigAgentDirName(): string;
|
|
62
56
|
/** Get the config root directory (~/.gjc). */
|
|
63
57
|
export declare function getConfigRootDir(): string;
|
|
@@ -109,6 +103,29 @@ export declare function getReportsDir(): string;
|
|
|
109
103
|
export declare function getLogsDir(): string;
|
|
110
104
|
/** Get the path to a dated log file (~/.gjc/logs/gjc.YYYY-MM-DD.log). */
|
|
111
105
|
export declare function getLogPath(date?: Date): string;
|
|
106
|
+
/**
|
|
107
|
+
* The logs directory the process actually reads and writes.
|
|
108
|
+
*
|
|
109
|
+
* A trusted `GJC_LOG_DIR` redirects the sink — the test preload pins it to a
|
|
110
|
+
* per-process temp directory so `bun test` stops appending fixture
|
|
111
|
+
* `level:error` records to the operator's shared log (issue #5618) — and
|
|
112
|
+
* everything else falls back to {@link getLogsDir}.
|
|
113
|
+
*
|
|
114
|
+
* The override is provenance-checked through the same {@link trustedValue} rule
|
|
115
|
+
* as the config and agent directories: Bun loads `cwd/.env` into `process.env`
|
|
116
|
+
* before any module runs, so a repository could otherwise redirect where the
|
|
117
|
+
* operator's production logs are written. A dynamic (`$`/backtick) declaration
|
|
118
|
+
* is rejected there too, which fails closed onto the canonical path.
|
|
119
|
+
*
|
|
120
|
+
* This is deliberately *not* folded into {@link getLogsDir}: that function's
|
|
121
|
+
* config-root semantics are pinned by tests, and the preload sets `GJC_LOG_DIR`
|
|
122
|
+
* for every test process, so folding the override in would move it under every
|
|
123
|
+
* test. Writers and readers must both come through here instead — the split
|
|
124
|
+
* between the two is what let the transport and the log readers disagree.
|
|
125
|
+
*/
|
|
126
|
+
export declare function getEffectiveLogsDir(): string;
|
|
127
|
+
/** Get the dated log file under {@link getEffectiveLogsDir}. */
|
|
128
|
+
export declare function getEffectiveLogPath(date?: Date): string;
|
|
112
129
|
/**
|
|
113
130
|
* Get the plugins directory (~/.gjc/plugins or its XDG equivalent).
|
|
114
131
|
*
|
package/dist/types/env-file.d.ts
CHANGED
|
@@ -32,3 +32,40 @@ export declare function parseShellEnvFile(filePath: string): Record<string, stri
|
|
|
32
32
|
export declare function parseEnvFile(filePath: string): Record<string, string>;
|
|
33
33
|
/** Parse dotenv content that has already been read from a trusted file. */
|
|
34
34
|
export declare function parseEnvFileContent(content: string): Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* What the caller's checkout declares through its dotenv files.
|
|
37
|
+
*
|
|
38
|
+
* `values` is the merged declaration set, later layers winning. `dynamic` holds
|
|
39
|
+
* the keys whose surviving declaration is one Bun expands at load time, which
|
|
40
|
+
* every provenance guard refuses outright because a value comparison cannot see
|
|
41
|
+
* what such a declaration became.
|
|
42
|
+
*/
|
|
43
|
+
export interface ProjectEnvSnapshot {
|
|
44
|
+
values: Record<string, string>;
|
|
45
|
+
dynamic: Set<string>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Windows environment variable names are case-insensitive, so a project dotenv
|
|
49
|
+
* line `userprofile=...` is what `process.env.USERPROFILE` resolves to. Every
|
|
50
|
+
* provenance lookup is spelled in upper case, so the snapshot must be keyed the
|
|
51
|
+
* same way or the declaration is invisible to the guard while still being live
|
|
52
|
+
* in the process. POSIX names are case-sensitive and must not fold.
|
|
53
|
+
*/
|
|
54
|
+
export declare function canonicalEnvKey(name: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* The layered dotenv declarations Bun overlays into `process.env` for a cwd.
|
|
57
|
+
*
|
|
58
|
+
* Lives in this leaf module so every consumer shares ONE notion of provenance.
|
|
59
|
+
* `dirs.ts` resolves the config, agent and log directories from it, and
|
|
60
|
+
* `scripts/test-preload.ts` decides test isolation from it — a second, narrower
|
|
61
|
+
* reader (only `cwd/.env`, its own regex, its own dynamic test) is how a
|
|
62
|
+
* `GJC_LOG_DIR` declared in `.env.local` / `.env.$NODE_ENV` came to be honored
|
|
63
|
+
* by the preload and then rejected in production, silently routing test log
|
|
64
|
+
* records to the operator's canonical sink. This module imports only `node:fs`,
|
|
65
|
+
* `node:path` and the import-free `./spawn-env`, so importing it has no side
|
|
66
|
+
* effects and cannot freeze resolver state the way importing `dirs.ts` would.
|
|
67
|
+
*
|
|
68
|
+
* `.env.local` is deliberately skipped when `NODE_ENV === "test"`, matching the
|
|
69
|
+
* convention that a local override file is not part of a test run.
|
|
70
|
+
*/
|
|
71
|
+
export declare function projectEnvSnapshot(cwd?: string): ProjectEnvSnapshot;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/utils",
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.2",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
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.17.
|
|
34
|
+
"@gajae-code/natives": "0.17.2",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/dirs.ts
CHANGED
|
@@ -16,7 +16,15 @@ import * as fs from "node:fs";
|
|
|
16
16
|
import * as os from "node:os";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import { engines, version } from "../package.json" with { type: "json" };
|
|
19
|
-
import {
|
|
19
|
+
import { canonicalEnvKey, type ProjectEnvSnapshot, projectEnvSnapshot } from "./env-file";
|
|
20
|
+
|
|
21
|
+
// The provenance snapshot and its key fold live in the leaf `env-file` module so
|
|
22
|
+
// `scripts/test-preload.ts` can share this exact logic without importing this
|
|
23
|
+
// module (whose load-time resolver construction would freeze state before the
|
|
24
|
+
// preload sets its isolation variables). Re-exported here because `env.ts` and
|
|
25
|
+
// `test/env-provenance.windows.test.ts` import `canonicalEnvKey` from this
|
|
26
|
+
// module's public surface.
|
|
27
|
+
export { canonicalEnvKey };
|
|
20
28
|
|
|
21
29
|
/** App name (e.g. "gjc") */
|
|
22
30
|
export const APP_NAME: string = "gjc";
|
|
@@ -170,39 +178,6 @@ function sanitizeConfigDirName(value: string | undefined): string | undefined {
|
|
|
170
178
|
return trimmed;
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
/**
|
|
174
|
-
* Windows environment variable names are case-insensitive, so a project dotenv
|
|
175
|
-
* line `userprofile=...` is what `process.env.USERPROFILE` resolves to. Every
|
|
176
|
-
* provenance lookup here is spelled in upper case, so the snapshot must be
|
|
177
|
-
* keyed the same way or the declaration is invisible to the guard while still
|
|
178
|
-
* being live in the process. POSIX names are case-sensitive and must not fold.
|
|
179
|
-
*/
|
|
180
|
-
export function canonicalEnvKey(name: string): string {
|
|
181
|
-
return process.platform === "win32" ? name.toUpperCase() : name;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function projectEnvSnapshot(cwd = process.cwd()): { values: Record<string, string>; dynamic: Set<string> } {
|
|
185
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
186
|
-
const validNodeEnv = nodeEnv && /^[A-Za-z0-9_-]+$/.test(nodeEnv) ? nodeEnv : undefined;
|
|
187
|
-
const files = [
|
|
188
|
-
".env",
|
|
189
|
-
...(validNodeEnv ? [`.env.${validNodeEnv}`] : []),
|
|
190
|
-
...(validNodeEnv !== "test" ? [".env.local"] : []),
|
|
191
|
-
...(validNodeEnv ? [`.env.${validNodeEnv}.local`] : []),
|
|
192
|
-
];
|
|
193
|
-
const values: Record<string, string> = {};
|
|
194
|
-
const dynamic = new Set<string>();
|
|
195
|
-
for (const file of files) {
|
|
196
|
-
for (const [rawKey, value] of Object.entries(parseEnvFile(path.join(cwd, file)))) {
|
|
197
|
-
const key = canonicalEnvKey(rawKey);
|
|
198
|
-
values[key] = value;
|
|
199
|
-
if (/[$`]/.test(value)) dynamic.add(key);
|
|
200
|
-
else dynamic.delete(key);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return { values, dynamic };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
181
|
/**
|
|
207
182
|
* Resolve an environment value only when it is not supplied by the caller's
|
|
208
183
|
* project dotenv (or when the inherited value is observably distinct).
|
|
@@ -220,10 +195,7 @@ function projectEnvSnapshot(cwd = process.cwd()): { values: Record<string, strin
|
|
|
220
195
|
* happens to carry the identical value loses the override, which is the same
|
|
221
196
|
* trade-off `resolveLiveCredentialEnvValue` already makes.
|
|
222
197
|
*/
|
|
223
|
-
function trustedValue(
|
|
224
|
-
name: string,
|
|
225
|
-
project: { values: Record<string, string>; dynamic: Set<string> },
|
|
226
|
-
): string | undefined {
|
|
198
|
+
function trustedValue(name: string, project: ProjectEnvSnapshot): string | undefined {
|
|
227
199
|
const value = process.env[name];
|
|
228
200
|
if (!value) return undefined;
|
|
229
201
|
const key = canonicalEnvKey(name);
|
|
@@ -232,7 +204,7 @@ function trustedValue(
|
|
|
232
204
|
return value;
|
|
233
205
|
}
|
|
234
206
|
|
|
235
|
-
function resolveConfigDirName(project:
|
|
207
|
+
function resolveConfigDirName(project: ProjectEnvSnapshot): string {
|
|
236
208
|
return (
|
|
237
209
|
sanitizeConfigDirName(trustedValue("GJC_CONFIG_DIR", project)) ??
|
|
238
210
|
sanitizeConfigDirName(trustedValue("PI_CONFIG_DIR", project)) ??
|
|
@@ -369,7 +341,7 @@ function accountHomeFromSystem(): AccountHome | undefined {
|
|
|
369
341
|
* per call is what makes the contract call-time; the provenance comparison above
|
|
370
342
|
* is what keeps an untrusted mutable home from being honored.
|
|
371
343
|
*/
|
|
372
|
-
function resolveTrustedHome(project:
|
|
344
|
+
function resolveTrustedHome(project: ProjectEnvSnapshot): string {
|
|
373
345
|
const authoritativeHomeKey = process.platform === "win32" ? "USERPROFILE" : "HOME";
|
|
374
346
|
const declaredHomeKey = canonicalEnvKey(authoritativeHomeKey);
|
|
375
347
|
const declaredHome = project.values[declaredHomeKey];
|
|
@@ -425,7 +397,7 @@ type XdgCategory = "data" | "state" | "cache";
|
|
|
425
397
|
class DirResolver {
|
|
426
398
|
configRoot: string;
|
|
427
399
|
agentDir: string;
|
|
428
|
-
readonly #projectEnv:
|
|
400
|
+
readonly #projectEnv: ProjectEnvSnapshot;
|
|
429
401
|
#configDirName: string;
|
|
430
402
|
readonly #agentDirOverride: boolean;
|
|
431
403
|
#trustedHome: string;
|
|
@@ -491,10 +463,7 @@ class DirResolver {
|
|
|
491
463
|
* change storage lane when a home refresh made its path coincide with the
|
|
492
464
|
* new default.
|
|
493
465
|
*/
|
|
494
|
-
private refreshCategoryDirs(
|
|
495
|
-
snapshot: { values: Record<string, string>; dynamic: Set<string> },
|
|
496
|
-
isDefault: boolean,
|
|
497
|
-
): void {
|
|
466
|
+
private refreshCategoryDirs(snapshot: ProjectEnvSnapshot, isDefault: boolean): void {
|
|
498
467
|
let xdgData: string | undefined;
|
|
499
468
|
let xdgState: string | undefined;
|
|
500
469
|
let xdgCache: string | undefined;
|
|
@@ -619,7 +588,7 @@ class DirResolver {
|
|
|
619
588
|
this.refreshConfigDirOverride();
|
|
620
589
|
if (!this.#homeAvailable) throw new Error("User state is unavailable: no trustworthy home directory");
|
|
621
590
|
}
|
|
622
|
-
get trustSnapshot():
|
|
591
|
+
get trustSnapshot(): ProjectEnvSnapshot {
|
|
623
592
|
return this.#projectEnv;
|
|
624
593
|
}
|
|
625
594
|
}
|
|
@@ -735,9 +704,44 @@ export function getLogsDir(): string {
|
|
|
735
704
|
return dirs.rootSubdir("logs", "state");
|
|
736
705
|
}
|
|
737
706
|
|
|
707
|
+
/** Dated log file name, shared by the canonical and effective log paths so they cannot drift. */
|
|
708
|
+
function datedLogFileName(date: Date): string {
|
|
709
|
+
return `${APP_NAME}.${date.toISOString().slice(0, 10)}.log`;
|
|
710
|
+
}
|
|
711
|
+
|
|
738
712
|
/** Get the path to a dated log file (~/.gjc/logs/gjc.YYYY-MM-DD.log). */
|
|
739
713
|
export function getLogPath(date = new Date()): string {
|
|
740
|
-
return path.join(getLogsDir(),
|
|
714
|
+
return path.join(getLogsDir(), datedLogFileName(date));
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* The logs directory the process actually reads and writes.
|
|
719
|
+
*
|
|
720
|
+
* A trusted `GJC_LOG_DIR` redirects the sink — the test preload pins it to a
|
|
721
|
+
* per-process temp directory so `bun test` stops appending fixture
|
|
722
|
+
* `level:error` records to the operator's shared log (issue #5618) — and
|
|
723
|
+
* everything else falls back to {@link getLogsDir}.
|
|
724
|
+
*
|
|
725
|
+
* The override is provenance-checked through the same {@link trustedValue} rule
|
|
726
|
+
* as the config and agent directories: Bun loads `cwd/.env` into `process.env`
|
|
727
|
+
* before any module runs, so a repository could otherwise redirect where the
|
|
728
|
+
* operator's production logs are written. A dynamic (`$`/backtick) declaration
|
|
729
|
+
* is rejected there too, which fails closed onto the canonical path.
|
|
730
|
+
*
|
|
731
|
+
* This is deliberately *not* folded into {@link getLogsDir}: that function's
|
|
732
|
+
* config-root semantics are pinned by tests, and the preload sets `GJC_LOG_DIR`
|
|
733
|
+
* for every test process, so folding the override in would move it under every
|
|
734
|
+
* test. Writers and readers must both come through here instead — the split
|
|
735
|
+
* between the two is what let the transport and the log readers disagree.
|
|
736
|
+
*/
|
|
737
|
+
export function getEffectiveLogsDir(): string {
|
|
738
|
+
const override = trustedValue("GJC_LOG_DIR", dirs.trustSnapshot)?.trim();
|
|
739
|
+
return override || getLogsDir();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** Get the dated log file under {@link getEffectiveLogsDir}. */
|
|
743
|
+
export function getEffectiveLogPath(date = new Date()): string {
|
|
744
|
+
return path.join(getEffectiveLogsDir(), datedLogFileName(date));
|
|
741
745
|
}
|
|
742
746
|
|
|
743
747
|
/**
|
package/src/env-file.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* below both of them.
|
|
7
7
|
*/
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
9
10
|
import { isSafeEnvValue } from "./spawn-env";
|
|
10
11
|
|
|
11
12
|
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -155,3 +156,65 @@ export function parseEnvFileContent(content: string): Record<string, string> {
|
|
|
155
156
|
|
|
156
157
|
return result;
|
|
157
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* What the caller's checkout declares through its dotenv files.
|
|
162
|
+
*
|
|
163
|
+
* `values` is the merged declaration set, later layers winning. `dynamic` holds
|
|
164
|
+
* the keys whose surviving declaration is one Bun expands at load time, which
|
|
165
|
+
* every provenance guard refuses outright because a value comparison cannot see
|
|
166
|
+
* what such a declaration became.
|
|
167
|
+
*/
|
|
168
|
+
export interface ProjectEnvSnapshot {
|
|
169
|
+
values: Record<string, string>;
|
|
170
|
+
dynamic: Set<string>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Windows environment variable names are case-insensitive, so a project dotenv
|
|
175
|
+
* line `userprofile=...` is what `process.env.USERPROFILE` resolves to. Every
|
|
176
|
+
* provenance lookup is spelled in upper case, so the snapshot must be keyed the
|
|
177
|
+
* same way or the declaration is invisible to the guard while still being live
|
|
178
|
+
* in the process. POSIX names are case-sensitive and must not fold.
|
|
179
|
+
*/
|
|
180
|
+
export function canonicalEnvKey(name: string): string {
|
|
181
|
+
return process.platform === "win32" ? name.toUpperCase() : name;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The layered dotenv declarations Bun overlays into `process.env` for a cwd.
|
|
186
|
+
*
|
|
187
|
+
* Lives in this leaf module so every consumer shares ONE notion of provenance.
|
|
188
|
+
* `dirs.ts` resolves the config, agent and log directories from it, and
|
|
189
|
+
* `scripts/test-preload.ts` decides test isolation from it — a second, narrower
|
|
190
|
+
* reader (only `cwd/.env`, its own regex, its own dynamic test) is how a
|
|
191
|
+
* `GJC_LOG_DIR` declared in `.env.local` / `.env.$NODE_ENV` came to be honored
|
|
192
|
+
* by the preload and then rejected in production, silently routing test log
|
|
193
|
+
* records to the operator's canonical sink. This module imports only `node:fs`,
|
|
194
|
+
* `node:path` and the import-free `./spawn-env`, so importing it has no side
|
|
195
|
+
* effects and cannot freeze resolver state the way importing `dirs.ts` would.
|
|
196
|
+
*
|
|
197
|
+
* `.env.local` is deliberately skipped when `NODE_ENV === "test"`, matching the
|
|
198
|
+
* convention that a local override file is not part of a test run.
|
|
199
|
+
*/
|
|
200
|
+
export function projectEnvSnapshot(cwd = process.cwd()): ProjectEnvSnapshot {
|
|
201
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
202
|
+
const validNodeEnv = nodeEnv && /^[A-Za-z0-9_-]+$/.test(nodeEnv) ? nodeEnv : undefined;
|
|
203
|
+
const files = [
|
|
204
|
+
".env",
|
|
205
|
+
...(validNodeEnv ? [`.env.${validNodeEnv}`] : []),
|
|
206
|
+
...(validNodeEnv !== "test" ? [".env.local"] : []),
|
|
207
|
+
...(validNodeEnv ? [`.env.${validNodeEnv}.local`] : []),
|
|
208
|
+
];
|
|
209
|
+
const values: Record<string, string> = {};
|
|
210
|
+
const dynamic = new Set<string>();
|
|
211
|
+
for (const file of files) {
|
|
212
|
+
for (const [rawKey, value] of Object.entries(parseEnvFile(path.join(cwd, file)))) {
|
|
213
|
+
const key = canonicalEnvKey(rawKey);
|
|
214
|
+
values[key] = value;
|
|
215
|
+
if (/[$`]/.test(value)) dynamic.add(key);
|
|
216
|
+
else dynamic.delete(key);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { values, dynamic };
|
|
220
|
+
}
|
package/src/logger.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import type * as winston from "winston";
|
|
15
|
-
import {
|
|
15
|
+
import { getEffectiveLogsDir } from "./dirs";
|
|
16
16
|
|
|
17
17
|
/** Ensure a logs directory exists; return the resolved path. */
|
|
18
18
|
function ensureDir(dir: string): string {
|
|
@@ -73,10 +73,22 @@ function makeLogFormat(winston: WinstonModule): winston.Logform.Format {
|
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Build a rotating file transport, materializing the target directory lazily.
|
|
78
|
+
*
|
|
79
|
+
* Destination precedence:
|
|
80
|
+
* 1. `dir` — an explicit path from {@link setTransports}(`{ file: "<path>" }`).
|
|
81
|
+
* 2. {@link getEffectiveLogsDir} — the provenance-checked `GJC_LOG_DIR`
|
|
82
|
+
* override, else the real config root (`~/.gjc/logs`).
|
|
83
|
+
*
|
|
84
|
+
* The resolution is centralized in `dirs.ts` rather than read from the
|
|
85
|
+
* environment here: the log *readers* (report bundles, the debug log view, the
|
|
86
|
+
* HTTP dump directory) call the same helper, and a second env read in this file
|
|
87
|
+
* is what let the transport write somewhere the readers never looked.
|
|
88
|
+
*/
|
|
77
89
|
function makeFileTransport(DailyRotateFile: DailyRotateFileCtor, dir?: string): Transport {
|
|
78
90
|
return new DailyRotateFile({
|
|
79
|
-
dirname: ensureDir(dir ??
|
|
91
|
+
dirname: ensureDir(dir ?? getEffectiveLogsDir()),
|
|
80
92
|
filename: "gjc.%DATE%.log",
|
|
81
93
|
datePattern: "YYYY-MM-DD",
|
|
82
94
|
maxSize: "10m",
|