@jayyuen66/dsh-ocr-review 0.1.7 → 0.1.9
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/CHANGELOG.md +325 -0
- package/LICENSE +1 -1
- package/README.md +204 -168
- package/README.zh-CN.md +209 -174
- package/THIRD_PARTY_NOTICES.md +11 -7
- package/client.js +1259 -543
- package/cordis.patch.yml +5 -3
- package/docs/DETAIL.md +28 -0
- package/docs/DETAIL.zh.md +28 -0
- package/host.js +2355 -632
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +59 -51
- package/scripts/get-cred.mjs +151 -55
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayyuen66/dsh-ocr-review",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "dsh 工作区代码审查插件:把 open-code-review(ocr CLI)封装为
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "dsh 工作区代码审查插件:把 open-code-review(ocr CLI)封装为 7 个模型工具 + 设置卡片(从宿主设置服务的 provider 清单选 provider/model 应用到 OCR 配置)。",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"DeepSeek Harness",
|
|
6
|
+
"ai-code-review",
|
|
8
7
|
"code-review",
|
|
9
8
|
"deepseek-harness",
|
|
10
9
|
"dsh",
|
|
@@ -23,11 +22,15 @@
|
|
|
23
22
|
"url": "git+https://github.com/JayYuen666/dsh-ocr-review.git"
|
|
24
23
|
},
|
|
25
24
|
"files": [
|
|
25
|
+
"locale/zh.json",
|
|
26
|
+
"locale/en.json",
|
|
27
|
+
"docs",
|
|
26
28
|
"icon.svg",
|
|
27
29
|
"host.js",
|
|
28
30
|
"client.js",
|
|
29
31
|
"cordis.patch.yml",
|
|
30
32
|
"scripts",
|
|
33
|
+
"CHANGELOG.md",
|
|
31
34
|
"THIRD_PARTY_NOTICES.md"
|
|
32
35
|
],
|
|
33
36
|
"type": "module",
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
".": "./host.js",
|
|
41
44
|
"./client": "./client.js",
|
|
42
45
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
46
|
+
"./locale/*.json": "./locale/*.json",
|
|
43
47
|
"./package.json": "./package.json"
|
|
44
48
|
},
|
|
45
49
|
"publishConfig": {
|
|
@@ -47,64 +51,68 @@
|
|
|
47
51
|
"registry": "https://registry.npmjs.org"
|
|
48
52
|
},
|
|
49
53
|
"dependencies": {
|
|
50
|
-
"@jayyuen66/dsh-plugin-shared": "
|
|
51
|
-
"js-yaml": "^5.4.3"
|
|
54
|
+
"@jayyuen66/dsh-plugin-shared": "0.2.1",
|
|
55
|
+
"js-yaml": "^5.4.3",
|
|
56
|
+
"zod": "4.6.7"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
|
-
"@deepseek-ai/cordis": "4.0.
|
|
55
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
56
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.
|
|
57
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.
|
|
58
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
59
|
-
"@deepseek-ai/dsh-client-store": "0.2.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
65
|
-
"@deepseek-ai/dsh-credentials": "0.2.
|
|
66
|
-
"@deepseek-ai/dsh-home-paths": "0.2.
|
|
67
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
68
|
-
"@deepseek-ai/dsh-jobs": "0.2.
|
|
69
|
-
"@deepseek-ai/dsh-jobs-local": "0.2.
|
|
70
|
-
"@deepseek-ai/dsh-output-retention": "0.2.
|
|
71
|
-
"@deepseek-ai/dsh-package-manifest": "0.2.
|
|
72
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.
|
|
73
|
-
"@deepseek-ai/dsh-sandbox": "0.2.
|
|
74
|
-
"@deepseek-ai/dsh-scope": "0.2.
|
|
75
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
76
|
-
"@deepseek-ai/dsh-session-projection": "0.2.
|
|
77
|
-
"@deepseek-ai/dsh-settings": "0.2.
|
|
78
|
-
"@deepseek-ai/dsh-shell": "0.2.
|
|
79
|
-
"@deepseek-ai/dsh-subprocess": "0.2.
|
|
80
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
81
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
82
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.
|
|
83
|
-
"@deepseek-ai/dsh-user-approval": "0.2.
|
|
84
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
85
|
-
"@
|
|
59
|
+
"@deepseek-ai/cordis": "4.0.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.2-alpha.2",
|
|
62
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-store": "0.2.2-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.2-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.2-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.2-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.2-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.2-alpha.2",
|
|
70
|
+
"@deepseek-ai/dsh-credentials": "0.2.2-alpha.2",
|
|
71
|
+
"@deepseek-ai/dsh-home-paths": "0.2.2-alpha.2",
|
|
72
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.2-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-jobs": "0.2.2-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-jobs-local": "0.2.2-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-output-retention": "0.2.2-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2",
|
|
77
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.2-alpha.2",
|
|
78
|
+
"@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2",
|
|
79
|
+
"@deepseek-ai/dsh-scope": "0.2.2-alpha.2",
|
|
80
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.2",
|
|
81
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2",
|
|
82
|
+
"@deepseek-ai/dsh-settings": "0.2.2-alpha.2",
|
|
83
|
+
"@deepseek-ai/dsh-shell": "0.2.2-alpha.2",
|
|
84
|
+
"@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2",
|
|
85
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2",
|
|
86
|
+
"@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
|
|
87
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2",
|
|
88
|
+
"@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2",
|
|
89
|
+
"@deepseek-ai/schemastery": "3.18.6-alpha.1",
|
|
90
|
+
"@e18e/eslint-plugin": "0.8.1",
|
|
91
|
+
"@types/node": "26.6.6",
|
|
86
92
|
"@types/react": "19.3.0",
|
|
87
93
|
"@vitest/coverage-v8": "5.0.5",
|
|
94
|
+
"eslint-plugin-regexp": "3.3.2",
|
|
88
95
|
"eslint-plugin-sonarjs": "4.2.2",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"oxlint
|
|
96
|
+
"eslint-plugin-zod": "5.2.2",
|
|
97
|
+
"oxfmt": "0.75.0",
|
|
98
|
+
"oxlint": "1.90.0",
|
|
99
|
+
"oxlint-tsgolint": "7.0.2005",
|
|
92
100
|
"react": "^19.3.0",
|
|
93
|
-
"rolldown": "1.2.
|
|
101
|
+
"rolldown": "1.2.15",
|
|
94
102
|
"typescript": "7.0.2",
|
|
95
|
-
"vite": "8.3.
|
|
103
|
+
"vite": "8.3.5",
|
|
96
104
|
"vitest": "5.0.5"
|
|
97
105
|
},
|
|
98
106
|
"peerDependencies": {
|
|
99
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.
|
|
100
|
-
"@deepseek-ai/dsh-credentials": "0.2.1-rc.
|
|
101
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.
|
|
102
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.
|
|
103
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
107
|
+
"@deepseek-ai/dsh-atomic-write": "^0.2.1-rc.3",
|
|
108
|
+
"@deepseek-ai/dsh-credentials": "^0.2.1-rc.3",
|
|
109
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.1-rc.3",
|
|
110
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.2.1-rc.3",
|
|
111
|
+
"@deepseek-ai/schemastery": "3.18.6-alpha.1"
|
|
104
112
|
},
|
|
105
113
|
"icon": "icon.svg",
|
|
106
114
|
"engines": {
|
|
107
|
-
"dsh": "^0.2.1-rc.
|
|
115
|
+
"dsh": "^0.2.1-rc.3",
|
|
108
116
|
"node": ">=22.12.0"
|
|
109
117
|
},
|
|
110
118
|
"dsh": {
|
|
@@ -117,11 +125,11 @@
|
|
|
117
125
|
"immediately": true
|
|
118
126
|
}
|
|
119
127
|
},
|
|
120
|
-
"ocrCliVersion": "1.13.
|
|
128
|
+
"ocrCliVersion": "1.13.3",
|
|
121
129
|
"scripts": {
|
|
122
130
|
"build:client": "node build-client.mjs",
|
|
123
131
|
"build": "node build-host.mjs",
|
|
124
|
-
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json",
|
|
132
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.client.json && tsc --noEmit -p src/tsconfig.json && tsc --noEmit -p test/client/tsconfig.json && tsc --noEmit -p tsconfig.build-scripts.json",
|
|
125
133
|
"lint": "oxlint .",
|
|
126
134
|
"test": "vitest run --coverage",
|
|
127
135
|
"test:watch": "vitest",
|
package/scripts/get-cred.mjs
CHANGED
|
@@ -62,6 +62,19 @@ const REF_PATTERN = /^[A-Z0-9_]+$/u;
|
|
|
62
62
|
/** 托管凭据文档的固定文件名(credentials-local 的 CREDENTIALS_FILENAME)。 */
|
|
63
63
|
const CREDENTIALS_FILENAME = ".credentials.yaml";
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 失败出口:写 stderr 并置退出码,**不**硬杀进程。
|
|
67
|
+
*
|
|
68
|
+
* process.stderr.write 对管道是异步的,紧随其后的 process.exit(1) 会在缓冲未清空时把
|
|
69
|
+
* 诊断截成半句;置 exitCode 让事件循环自然排空后以非零收场,报错全文有保证。
|
|
70
|
+
* @param {string} message
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
function fail(message) {
|
|
74
|
+
process.stderr.write(message);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* 对象守卫(shared/lib/tool-events 同款防御):typeof 收窄为 Record 而不经断言。
|
|
67
80
|
* @param {unknown} value
|
|
@@ -73,10 +86,8 @@ function isRecord(value) {
|
|
|
73
86
|
|
|
74
87
|
/**
|
|
75
88
|
* 数据目录:显式参数 > $DSH_HOME > homedir()/.dsh(空/全空白按未设置处理)。
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* 相对路径会被解析成 `<被审查仓库>/.dsh/…`——正是文件头声明要避免的「拿被审查
|
|
79
|
-
* 仓库里的文件当用户凭据」。猜不出用户指的是哪个目录,就不猜。
|
|
89
|
+
* 只做**选择**;绝对性校验在调用点(main)做——那是一条要报错退出的硬门,与本函数
|
|
90
|
+
* 「挑出候选目录」的职责分开。
|
|
80
91
|
* @param {string | undefined} fromArg
|
|
81
92
|
* @returns {string}
|
|
82
93
|
*/
|
|
@@ -90,15 +101,13 @@ function dataDirFrom(fromArg) {
|
|
|
90
101
|
if (arg !== "") {
|
|
91
102
|
chosen = arg;
|
|
92
103
|
}
|
|
93
|
-
if (!path.isAbsolute(chosen)) {
|
|
94
|
-
process.stderr.write(`[get-cred] dsh 数据目录必须是绝对路径,收到 ${JSON.stringify(chosen)}\n`);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
104
|
return chosen;
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
/**
|
|
101
|
-
*
|
|
107
|
+
/** 取值过程中遇到的坏消息(文件损坏/权限/引号写坏);收集但不提前退出——某一层的
|
|
108
|
+
* 小毛病不能把后面的合法层堵掉。出口只有两处:最终取不到时随失败原因一并报出,
|
|
109
|
+
* 或取到了更低一层时以警告形式报出(用户必须知道生效的 key 不是最高优先级给的)。
|
|
110
|
+
* 全程脱敏:明文值不进这条列表,YAML 报错走 yamlErrorDetail。 */
|
|
102
111
|
const problems = [];
|
|
103
112
|
|
|
104
113
|
/**
|
|
@@ -195,22 +204,6 @@ function fromCredentialsDoc(credPath, refName) {
|
|
|
195
204
|
return key !== "" && !/[\r\n]/u.test(key) ? key : undefined;
|
|
196
205
|
}
|
|
197
206
|
|
|
198
|
-
/**
|
|
199
|
-
* `.env` 的单行取值:去首尾空白,长度 > 1 且以引号开头时剥掉包裹引号。
|
|
200
|
-
* @param {string} raw
|
|
201
|
-
* @returns {string}
|
|
202
|
-
*/
|
|
203
|
-
function unquotedValue(raw) {
|
|
204
|
-
const trimmed = raw.trim();
|
|
205
|
-
const [first] = trimmed;
|
|
206
|
-
// 只有首尾**配对**的引号才剥。单边引号是用户写坏了,硬砍首尾等于把凭据改短
|
|
207
|
-
// 一位,拿一个错 key 去打上游(401),比报「未配置」难排查得多。
|
|
208
|
-
if (trimmed.length > 1 && (first === '"' || first === "'") && trimmed.endsWith(first)) {
|
|
209
|
-
return trimmed.slice(1, -1);
|
|
210
|
-
}
|
|
211
|
-
return trimmed;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
207
|
/**
|
|
215
208
|
* 继承来的进程环境层:去首尾空白,空白按「这一层没有值」处理;含内嵌换行的值整层作废。
|
|
216
209
|
*
|
|
@@ -225,8 +218,87 @@ function fromInheritedEnv(raw) {
|
|
|
225
218
|
return value === "" || /[\r\n]/u.test(value) ? undefined : value;
|
|
226
219
|
}
|
|
227
220
|
|
|
221
|
+
/** 未加引号的取值:trim,含「 #」时从那里剥离行内注释;剥完为空白按没有值。 */
|
|
222
|
+
function unquotedValueOf(trimmed) {
|
|
223
|
+
const commentAt = trimmed.indexOf(" #");
|
|
224
|
+
const bare = commentAt === -1 ? trimmed : trimmed.slice(0, commentAt).trim();
|
|
225
|
+
return { value: bare === "" ? undefined : bare, problem: undefined };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 扫描带引号的值:交出引号内文本、是否闭合、闭合后的游标。双引号内 `\"` 与 `\\`
|
|
230
|
+
* 是转义(交出的文本已还原),单引号内全部字面——与 dotenv 的两套引号语义一致。
|
|
231
|
+
* @param {string} trimmed
|
|
232
|
+
* @param {string} quote
|
|
233
|
+
* @returns {{ value: string, closed: boolean, end: number }}
|
|
234
|
+
*/
|
|
235
|
+
function scanQuoted(trimmed, quote) {
|
|
236
|
+
let value = "";
|
|
237
|
+
let index = 1;
|
|
238
|
+
while (index < trimmed.length) {
|
|
239
|
+
const ch = trimmed[index];
|
|
240
|
+
const escaped =
|
|
241
|
+
quote === '"' && ch === "\\" && (trimmed[index + 1] === '"' || trimmed[index + 1] === "\\");
|
|
242
|
+
if (escaped) {
|
|
243
|
+
value += trimmed[index + 1];
|
|
244
|
+
index += 2;
|
|
245
|
+
} else if (ch === quote) {
|
|
246
|
+
return { value, closed: true, end: index + 1 };
|
|
247
|
+
} else {
|
|
248
|
+
value += ch;
|
|
249
|
+
index += 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { value, closed: false, end: index };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 带引号形态的取值:成对才剥;闭引号之后只允许空白或行内注释。单边引号、闭引号后跟
|
|
257
|
+
* 多余字符都记 problem——硬砍首尾等于把凭据改短一位,拿一个错 key 去打上游(401),
|
|
258
|
+
* 比报「未配置」难排查得多。剥引号后再 trim 并判空:`KEY=""` / `KEY=" "` 与另两层
|
|
259
|
+
* (托管文档、进程环境)的口径一致——纯空白不是可用凭据,按「该行没有值」处理。
|
|
260
|
+
* @param {string} trimmed
|
|
261
|
+
* @param {string} quote
|
|
262
|
+
* @returns {{ value: string | undefined, problem: string | undefined }}
|
|
263
|
+
*/
|
|
264
|
+
function quotedValueOf(trimmed, quote) {
|
|
265
|
+
const scanned = scanQuoted(trimmed, quote);
|
|
266
|
+
if (!scanned.closed) {
|
|
267
|
+
return { value: undefined, problem: "引号未配对" };
|
|
268
|
+
}
|
|
269
|
+
const rest = trimmed.slice(scanned.end).trim();
|
|
270
|
+
if (rest !== "" && !rest.startsWith("#")) {
|
|
271
|
+
return { value: undefined, problem: "闭引号之后还有多余字符" };
|
|
272
|
+
}
|
|
273
|
+
const unquoted = scanned.value.trim();
|
|
274
|
+
return { value: unquoted === "" ? undefined : unquoted, problem: undefined };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* .env 一行里「=」右侧的取值:按首字符分派到未加引号 / 带引号两条解析路径(判据各自
|
|
279
|
+
* 见下)。只实现与官方 dotenv 相交且本脚本用得到的语义;拿不准的形态 fail-closed
|
|
280
|
+
* (按该行未配置处理并记 problem),绝不猜一个值出去。
|
|
281
|
+
* @param {string} raw
|
|
282
|
+
* @returns {{ value: string | undefined, problem: string | undefined }}
|
|
283
|
+
*/
|
|
284
|
+
function parseDotEnvValue(raw) {
|
|
285
|
+
const trimmed = raw.trim();
|
|
286
|
+
if (trimmed === "") {
|
|
287
|
+
return { value: undefined, problem: undefined };
|
|
288
|
+
}
|
|
289
|
+
const quote = trimmed.charAt(0);
|
|
290
|
+
if (quote === '"' || quote === "'") {
|
|
291
|
+
return quotedValueOf(trimmed, quote);
|
|
292
|
+
}
|
|
293
|
+
return unquotedValueOf(trimmed);
|
|
294
|
+
}
|
|
295
|
+
|
|
228
296
|
/**
|
|
229
|
-
* $DSH_HOME/.env 的 KEY=VALUE
|
|
297
|
+
* $DSH_HOME/.env 的 KEY=VALUE(文件不存在按「该层没有值」处理)。
|
|
298
|
+
*
|
|
299
|
+
* 同名 key 取**最后一行**的值(dotenv 语义:后者覆盖前者);空值行同样参与覆盖——
|
|
300
|
+
* 最后一行写 `KEY=` 就是把这层 unset。解析出 problem 的行不参与覆盖(该行整条作废,
|
|
301
|
+
* 不能拿一个坏行抹掉前面好好的值),但会记进 problems。
|
|
230
302
|
* @param {string} envPath
|
|
231
303
|
* @param {string} refName
|
|
232
304
|
* @returns {string | undefined}
|
|
@@ -241,41 +313,65 @@ function fromUserDotEnv(envPath, refName) {
|
|
|
241
313
|
}
|
|
242
314
|
}
|
|
243
315
|
const pattern = new RegExp(`^\\s*(?:export\\s+)?${refName}\\s*=\\s*(?<value>.*)$`, "u");
|
|
244
|
-
let hit
|
|
316
|
+
let hit;
|
|
245
317
|
// 按 /\r?\n/ 拆而不是 "\n":上面那条正则的 `.*` 不匹配 \r、`$` 又要求串尾,
|
|
246
318
|
// 而按 "\n" 拆出来的 CRLF 行每行都带尾随 \r,于是每一行都匹配不上——整个 .env
|
|
247
319
|
// 层对所有 key 静默失配,用户只看到隔了一层的「取不到凭据」。
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
320
|
+
const lines = (text ?? "").split(/\r?\n/u);
|
|
321
|
+
for (const [offset, line] of lines.entries()) {
|
|
322
|
+
const match = pattern.exec(line);
|
|
323
|
+
if (match === null) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const parsed = parseDotEnvValue(match.groups?.value ?? "");
|
|
327
|
+
if (parsed.problem === undefined) {
|
|
328
|
+
hit = parsed.value;
|
|
329
|
+
} else {
|
|
330
|
+
problems.push(
|
|
331
|
+
`${envPath} 第 ${offset + 1} 行(${refName})${parsed.problem},该行按未配置处理`,
|
|
332
|
+
);
|
|
253
333
|
}
|
|
254
334
|
}
|
|
255
|
-
return hit
|
|
335
|
+
return hit;
|
|
256
336
|
}
|
|
257
337
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const home = dataDirFrom(process.argv[3]);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
338
|
+
function main() {
|
|
339
|
+
const refName = process.argv[2] ?? "";
|
|
340
|
+
if (!REF_PATTERN.test(refName)) {
|
|
341
|
+
fail(`[get-cred] 非法 env key 名:${JSON.stringify(refName)}\n`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const home = dataDirFrom(process.argv[3]);
|
|
345
|
+
// 相对路径在这里是硬错误而不是照用:本脚本的 cwd 是 OCR 当时所在的那个仓库,
|
|
346
|
+
// 相对路径会被解析成 `<被审查仓库>/.dsh/…`——正是文件头声明要避免的「拿被审查
|
|
347
|
+
// 仓库里的文件当用户凭据」。猜不出用户指的是哪个目录,就不猜。
|
|
348
|
+
if (!path.isAbsolute(home)) {
|
|
349
|
+
fail(`[get-cred] dsh 数据目录必须是绝对路径,收到 ${JSON.stringify(home)}\n`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const found =
|
|
353
|
+
fromCredentialsDoc(path.join(home, CREDENTIALS_FILENAME), refName) ??
|
|
354
|
+
fromInheritedEnv(process.env[refName]) ??
|
|
355
|
+
fromUserDotEnv(path.join(home, ".env"), refName);
|
|
269
356
|
|
|
270
|
-
if (found === undefined) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
//
|
|
357
|
+
if (found === undefined) {
|
|
358
|
+
// 硬失败:OCR 契约里非零退出/空输出都不回退,所以原因必须写全(含查过的目录与
|
|
359
|
+
// 各层的坏消息),否则用户只能看到「llm 请求 401」这种隔了一层的症状。
|
|
360
|
+
const notes = problems.length === 0 ? "" : `;${problems.join(";")}`;
|
|
361
|
+
fail(
|
|
362
|
+
`[get-cred] 取不到 ${refName}:查过 ${path.join(home, CREDENTIALS_FILENAME)}、进程环境与 ${path.join(home, ".env")}${notes}\n`,
|
|
363
|
+
);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// 成功回退也要说话:托管文件损坏/无权限/ref 形状错被回退层盖过去时,用户必须知道
|
|
367
|
+
// 生效的 key 不是最高优先级那层给的。problems 在收集时已按脱敏口径拼装(YAML 报错走
|
|
368
|
+
// yamlErrorDetail,绝不带邻域原文),这里原样转述即可。
|
|
369
|
+
if (problems.length > 0) {
|
|
370
|
+
process.stderr.write(
|
|
371
|
+
`[get-cred] 注意:高优先级凭据层有坏消息(${problems.join(";")}),${refName} 实际取自更低的一层\n`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
280
374
|
process.stdout.write(`${found}\n`);
|
|
281
375
|
}
|
|
376
|
+
|
|
377
|
+
main();
|