@triflux/core 10.88.1 → 10.88.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/hub/lib/jev-addon.mjs +12 -3
- package/hub/lib/jev-host-config.mjs +76 -0
- package/package.json +1 -1
package/hub/lib/jev-addon.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// hub/lib/jev-addon.mjs
|
|
2
2
|
// Opt-in Jev (System One) 판단 애드온.
|
|
3
|
-
//
|
|
3
|
+
// 환경변수, 명시 CLI 또는 host 설정으로 활성화하며,
|
|
4
4
|
// 설정이 없거나 호출이 실패하면 항상 null을 반환해 기존 결정론적 로직을 그대로 유지한다.
|
|
5
5
|
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
@@ -9,6 +9,7 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { whichCommand } from "../platform.mjs";
|
|
12
|
+
import { readJevHostApiKey, readJevHostConfig } from "./jev-host-config.mjs";
|
|
12
13
|
|
|
13
14
|
const execFileAsync = promisify(execFile);
|
|
14
15
|
|
|
@@ -41,7 +42,12 @@ function isExplicitlyEnabled(env = process.env) {
|
|
|
41
42
|
|
|
42
43
|
export function resolveJevApiKey(env = process.env) {
|
|
43
44
|
const key = env.JEV_API_KEY || env.TYPESAFE_API_KEY || "";
|
|
44
|
-
return
|
|
45
|
+
return (
|
|
46
|
+
String(key).trim() ||
|
|
47
|
+
(isExplicitlyDisabled(env) || resolveJevCliBin(env)
|
|
48
|
+
? null
|
|
49
|
+
: readJevHostApiKey(env))
|
|
50
|
+
);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
export function resolveJevCliBin(env = process.env) {
|
|
@@ -75,6 +81,8 @@ function isDirectEnvObject(obj) {
|
|
|
75
81
|
return false;
|
|
76
82
|
}
|
|
77
83
|
return (
|
|
84
|
+
"HOME" in obj ||
|
|
85
|
+
"XDG_CONFIG_HOME" in obj ||
|
|
78
86
|
"TFX_JEV" in obj ||
|
|
79
87
|
"JEV_API_KEY" in obj ||
|
|
80
88
|
"TYPESAFE_API_KEY" in obj ||
|
|
@@ -102,8 +110,9 @@ export function isJevEnabled(options = {}) {
|
|
|
102
110
|
) {
|
|
103
111
|
return true;
|
|
104
112
|
}
|
|
105
|
-
if (
|
|
113
|
+
if (String(env.JEV_API_KEY || env.TYPESAFE_API_KEY || "").trim()) return true;
|
|
106
114
|
if (resolveJevCliBin(env)) return true;
|
|
115
|
+
if (readJevHostConfig(env)) return true;
|
|
107
116
|
return false;
|
|
108
117
|
}
|
|
109
118
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, openSync, readSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const MAX_FILE_BYTES = 64 * 1024;
|
|
6
|
+
|
|
7
|
+
function readConfigFile(path, privateFile = false) {
|
|
8
|
+
let fd;
|
|
9
|
+
try {
|
|
10
|
+
fd = openSync(
|
|
11
|
+
path,
|
|
12
|
+
constants.O_RDONLY |
|
|
13
|
+
(constants.O_NOFOLLOW || 0) |
|
|
14
|
+
(constants.O_NONBLOCK || 0),
|
|
15
|
+
);
|
|
16
|
+
const stat = fstatSync(fd);
|
|
17
|
+
if (!stat.isFile() || stat.size > MAX_FILE_BYTES) return null;
|
|
18
|
+
if (typeof process.getuid !== "function" || stat.uid !== process.getuid())
|
|
19
|
+
return null;
|
|
20
|
+
if (
|
|
21
|
+
privateFile ? (stat.mode & 0o7777) !== 0o600 : (stat.mode & 0o022) !== 0
|
|
22
|
+
)
|
|
23
|
+
return null;
|
|
24
|
+
const buffer = Buffer.alloc(MAX_FILE_BYTES + 1);
|
|
25
|
+
const size = readSync(fd, buffer, 0, buffer.length, 0);
|
|
26
|
+
return size <= MAX_FILE_BYTES ? buffer.toString("utf8", 0, size) : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
} finally {
|
|
30
|
+
if (fd !== undefined) closeSync(fd);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readJevHostConfig(env = process.env) {
|
|
35
|
+
// 명시 env fixture는 주입한 HOME/XDG만 사용한다. 빈 객체는 실제 홈을 읽지 않는다.
|
|
36
|
+
const configHome =
|
|
37
|
+
env.XDG_CONFIG_HOME ||
|
|
38
|
+
(env.HOME
|
|
39
|
+
? join(env.HOME, ".config")
|
|
40
|
+
: env === process.env
|
|
41
|
+
? join(homedir(), ".config")
|
|
42
|
+
: null);
|
|
43
|
+
if (!configHome || !isAbsolute(configHome)) return null;
|
|
44
|
+
try {
|
|
45
|
+
const config = JSON.parse(
|
|
46
|
+
readConfigFile(join(configHome, "triflux", "jev.json")),
|
|
47
|
+
);
|
|
48
|
+
return config?.enabled === true ? config : null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readJevHostApiKey(env = process.env) {
|
|
55
|
+
const config = readJevHostConfig(env);
|
|
56
|
+
if (typeof config?.apiKeyFile !== "string" || !isAbsolute(config.apiKeyFile))
|
|
57
|
+
return null;
|
|
58
|
+
const text = readConfigFile(config.apiKeyFile, true);
|
|
59
|
+
if (text == null) return null;
|
|
60
|
+
const keys = {};
|
|
61
|
+
for (const line of text.split(/\r?\n/)) {
|
|
62
|
+
const assignment = line.match(
|
|
63
|
+
/^\s*(?:export\s+)?(JEV_API_KEY|TYPESAFE_API_KEY)\s*=(.*)$/,
|
|
64
|
+
);
|
|
65
|
+
if (!assignment) continue;
|
|
66
|
+
const [, name, raw] = assignment;
|
|
67
|
+
const literal = raw
|
|
68
|
+
.trim()
|
|
69
|
+
.match(/^(?:'([^']*)'|"([^"]*)"|([^\s'";#]+))\s*(?:#.*)?$/);
|
|
70
|
+
if (!literal || name in keys) return null;
|
|
71
|
+
const value = (literal[1] ?? literal[2] ?? literal[3]).trim();
|
|
72
|
+
if (!value || /[$`\\\x00-\x1f\x7f]/.test(value)) return null;
|
|
73
|
+
keys[name] = value;
|
|
74
|
+
}
|
|
75
|
+
return keys.JEV_API_KEY || keys.TYPESAFE_API_KEY || null;
|
|
76
|
+
}
|