@d5render/cli 0.1.56 → 0.1.58
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/.github/instructions/severity.instructions.md +34 -19
- package/.skills/code-review/SKILL.md +35 -6
- package/.skills/devops/{README.md → SKILL.md} +21 -15
- package/README.md +30 -2
- package/bin/d5cli +210 -314
- package/bin/{copilot.js → mcpServer.js} +964 -927
- package/package.json +26 -23
- package/.github/instructions/code-review.instructions.md +0 -43
- package/CHANGELOG.md +0 -10
package/bin/d5cli
CHANGED
|
@@ -1,115 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { argv, env, platform } from "node:process";
|
|
2
3
|
import { execSync, spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
-
import { argv, env, platform } from "node:process";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
8
7
|
|
|
9
|
-
//#region
|
|
10
|
-
|
|
8
|
+
//#region package.json
|
|
9
|
+
var name$1 = "@d5render/cli";
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region packages/env.ts
|
|
13
|
+
const name = "d5_mcp_review_builtin";
|
|
11
14
|
const report = "report";
|
|
12
15
|
const getHash = "hash";
|
|
13
|
-
const file = "bin/
|
|
16
|
+
const file = "bin/mcpServer.js";
|
|
14
17
|
const RUNTIME_CWD = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
15
18
|
const serveFile = join(RUNTIME_CWD, file);
|
|
16
19
|
const envJson = buildEnv();
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const tools = [
|
|
36
|
-
"--additional-mcp-config",
|
|
37
|
-
JSON.stringify({ mcpServers: { [name$1]: {
|
|
38
|
-
type: "local",
|
|
39
|
-
command: "node",
|
|
40
|
-
args: [serveFile, `--customizenv=${JSON.stringify(envUsed)}`],
|
|
41
|
-
tools: ["*"]
|
|
42
|
-
} } }),
|
|
43
|
-
"--allow-all-paths",
|
|
44
|
-
"--allow-all-tools",
|
|
45
|
-
"--deny-tool",
|
|
46
|
-
"write",
|
|
47
|
-
"--deny-tool",
|
|
48
|
-
"github-mcp-server"
|
|
20
|
+
const envConfigKeys = [
|
|
21
|
+
"CI_SERVER_URL",
|
|
22
|
+
"CI_PROJECT_PATH",
|
|
23
|
+
"CI_PROJECT_ID",
|
|
24
|
+
"CI_PROJECT_NAME",
|
|
25
|
+
"CI_COMMIT_SHA",
|
|
26
|
+
"CI_COMMIT_BEFORE_SHA",
|
|
27
|
+
"CI_MERGE_REQUEST_IID",
|
|
28
|
+
"CI_MERGE_REQUEST_TITLE",
|
|
29
|
+
"CI_MERGE_REQUEST_DESCRIPTION",
|
|
30
|
+
"JIRA_BASE_URL",
|
|
31
|
+
"DINGTALK_WEBHOOK",
|
|
32
|
+
"GITLAB_TOKEN",
|
|
33
|
+
"JIRA_PAT",
|
|
34
|
+
"JIRA_COOKIE",
|
|
35
|
+
"JIRA_USERNAME",
|
|
36
|
+
"JIRA_PASSWORD",
|
|
37
|
+
"TOKEN_USAGE"
|
|
49
38
|
];
|
|
39
|
+
const envUsed = Object.defineProperty({}, "JIRA_BASE_URL", {
|
|
40
|
+
get: () => toEnv("CI_SERVER_URL", "http://jira.d5techs.com.cn"),
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true
|
|
43
|
+
});
|
|
44
|
+
envConfigKeys.forEach((key) => Object.defineProperty(envUsed, key, {
|
|
45
|
+
get: () => toEnv(key),
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true
|
|
48
|
+
}));
|
|
50
49
|
function toEnv(key, defaultValue) {
|
|
51
50
|
return envJson[key] || process.env[key] || defaultValue;
|
|
52
51
|
}
|
|
53
52
|
function buildEnv() {
|
|
54
53
|
const envArg = argv.find((arg) => arg.startsWith("--customizenv="));
|
|
55
|
-
let envJson
|
|
56
|
-
if (envArg) envJson
|
|
57
|
-
return envJson
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region copilot/bin/install.ts
|
|
62
|
-
function install() {
|
|
63
|
-
if (!env.CI) return;
|
|
64
|
-
console.log("install copilot...");
|
|
65
|
-
let success = false;
|
|
66
|
-
let local = "";
|
|
67
|
-
try {
|
|
68
|
-
local = execSync("npm list @github/copilot -g --depth=0 --json").toString();
|
|
69
|
-
} catch {
|
|
70
|
-
local = "{}";
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const localInfo = JSON.parse(local);
|
|
74
|
-
const localVersion = localInfo.dependencies ? localInfo.dependencies?.["@github/copilot"]?.version : void 0;
|
|
75
|
-
if (!localVersion) {
|
|
76
|
-
installCopilot();
|
|
77
|
-
success = true;
|
|
78
|
-
console.log("install copilot success");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (localVersion !== execSync("npm view @github/copilot version --registry=https://registry.npmmirror.com").toString().trim()) {
|
|
82
|
-
execSync("npm uninstall -g @github/copilot --force");
|
|
83
|
-
installCopilot();
|
|
84
|
-
success = true;
|
|
85
|
-
console.log("update copilot success");
|
|
86
|
-
} else {
|
|
87
|
-
success = true;
|
|
88
|
-
console.log("copilot exists and is up-to-date");
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.error(error);
|
|
92
|
-
}
|
|
93
|
-
if (!success) try {
|
|
94
|
-
console.warn("try to reinstall copilot...");
|
|
95
|
-
installCopilot();
|
|
96
|
-
success = true;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error(error);
|
|
99
|
-
}
|
|
100
|
-
if (success) return;
|
|
101
|
-
console.warn("try to reinstall copilot...");
|
|
102
|
-
installCopilot();
|
|
103
|
-
}
|
|
104
|
-
function installCopilot() {
|
|
105
|
-
execSync("npm install -g @github/copilot --registry=https://registry.npmmirror.com", { stdio: "inherit" });
|
|
54
|
+
let envJson = {};
|
|
55
|
+
if (envArg) envJson = JSON.parse(envArg.replace("--customizenv=", ""));
|
|
56
|
+
return envJson;
|
|
106
57
|
}
|
|
107
58
|
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region package.json
|
|
110
|
-
var name = "@d5render/cli";
|
|
111
|
-
var version = "0.1.56";
|
|
112
|
-
|
|
113
59
|
//#endregion
|
|
114
60
|
//#region packages/gitlab/url.ts
|
|
115
61
|
function buildHeaders() {
|
|
@@ -154,8 +100,8 @@ const commits = () => {
|
|
|
154
100
|
//#endregion
|
|
155
101
|
//#region packages/message/sendding.ts
|
|
156
102
|
async function sendding(title, text) {
|
|
157
|
-
if (!envUsed.DINGTALK_WEBHOOK) throw new Error("non DINGTALK_WEBHOOK");
|
|
158
103
|
let res = new Response();
|
|
104
|
+
if (!envUsed.DINGTALK_WEBHOOK) throw res;
|
|
159
105
|
for (const url of envUsed.DINGTALK_WEBHOOK.split(",")) res = await fetch(url, {
|
|
160
106
|
method: "POST",
|
|
161
107
|
headers: { "Content-Type": "application/json" },
|
|
@@ -175,60 +121,46 @@ async function sendding(title, text) {
|
|
|
175
121
|
}
|
|
176
122
|
|
|
177
123
|
//#endregion
|
|
178
|
-
//#region
|
|
179
|
-
const NAME = name.replaceAll("/", "_");
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
124
|
+
//#region review/helper.ts
|
|
125
|
+
const NAME = name$1.replaceAll("/", "_");
|
|
126
|
+
const TEMP = join(env.CI_PROJECT_DIR || "", `../../.${NAME}`);
|
|
127
|
+
const common_review_prompt = `Load code-review skills, then call the mcp tool '${name}-${getHash}' to load code-review commits.
|
|
128
|
+
Then use chinese(中文) to call the mcp tool '${name}-${report}', only main agent can call the tool '${name}-${report}', **prevent** subagent from calling tool '${name}-${report}`;
|
|
129
|
+
async function changelog() {
|
|
130
|
+
const changelog = readFileSync(join(RUNTIME_CWD, "README.md"), "utf8");
|
|
131
|
+
const cachepath = join(TEMP, "CHANGELOG_" + (env.CI_RUNNER_ID ?? env.CI_PROJECT_ID ?? "0"));
|
|
132
|
+
if (changelog === (existsSync(cachepath) ? readFileSync(cachepath, "utf8") : "")) return;
|
|
133
|
+
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
134
|
+
writeFileSync(cachepath, changelog, "utf8");
|
|
135
|
+
console.log("updated CHANGELOG cache.");
|
|
136
|
+
let matched = changelog.match(/CHANGELOG[\s\S]*?(#+[^\n]*\n+([\s\S]*?))(?=#+|$)/)?.[1] ?? "";
|
|
137
|
+
const matcheds = matched.split("\n");
|
|
138
|
+
matcheds.shift();
|
|
139
|
+
matched = matcheds.join("\n").trim();
|
|
140
|
+
if (matched) await dingding("NOTICE", `skills 更新\n\n\n${matched}\n\n\n历史请参考 [线上文档内容.skills文件夹](https://www.npmjs.com/package/@d5render/cli)`);
|
|
141
|
+
}
|
|
142
|
+
function deploySkills(SKILLS_DIR = ".copilot/skills") {
|
|
192
143
|
const HOME = env.USERPROFILE ?? env.HOME ?? env.HOMEPATH;
|
|
193
144
|
if (!HOME) throw new Error("cannot find `USERPROFILE` directory");
|
|
194
|
-
const
|
|
195
|
-
const cachepath = join(TEMP, "CHANGELOG-" + env.CI_RUNNER_ID || "0");
|
|
196
|
-
let cache = "";
|
|
197
|
-
if (existsSync(cachepath)) cache = readFileSync(cachepath, "utf8");
|
|
198
|
-
else if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
199
|
-
if (changelog !== cache) {
|
|
200
|
-
writeFileSync(cachepath, changelog, "utf8");
|
|
201
|
-
console.log("updated CHANGELOG cache.");
|
|
202
|
-
await dingding("NOTICE", `code-review/SKILL.md 更新\n\n细节请参考 [线上文档内容.skills](https://www.npmjs.com/package/@d5render/cli?activeTab=code)`);
|
|
203
|
-
}
|
|
204
|
-
const config = join(HOME, ".copilot/config.json"), dir = join(HOME, ".copilot/skills/code-review");
|
|
205
|
-
console.log("deploy...");
|
|
206
|
-
if (existsSync(config)) {
|
|
207
|
-
rmSync(config);
|
|
208
|
-
console.log("removed config cache.");
|
|
209
|
-
}
|
|
210
|
-
if (existsSync(dir)) rmSync(dir, {
|
|
211
|
-
recursive: true,
|
|
212
|
-
force: true
|
|
213
|
-
});
|
|
145
|
+
const dir = join(HOME, `${SKILLS_DIR}/code-review`);
|
|
214
146
|
mkdirSync(dir, { recursive: true });
|
|
147
|
+
console.log("deploy code-review skills...");
|
|
215
148
|
const skillRoot = join(RUNTIME_CWD, ".skills/code-review");
|
|
216
|
-
readdirSync(skillRoot).forEach((
|
|
149
|
+
readdirSync(skillRoot).forEach((file) => copyFileSync(join(skillRoot, file), join(dir, file)));
|
|
217
150
|
const instructionsRoot = join(RUNTIME_CWD, ".github/instructions");
|
|
218
151
|
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
219
|
-
console.log("to new skill.");
|
|
220
152
|
}
|
|
221
153
|
async function need() {
|
|
222
154
|
if (!env.CI) return true;
|
|
223
155
|
const { CI_MERGE_REQUEST_IID, CI_COMMIT_SHA } = env;
|
|
224
|
-
const file
|
|
156
|
+
const file = join(TEMP, "CODEREVIEW_" + (env.CI_PROJECT_ID ?? "0"));
|
|
225
157
|
if (CI_MERGE_REQUEST_IID) {
|
|
226
|
-
let appended = `${existsSync(file
|
|
158
|
+
let appended = `${existsSync(file) ? readFileSync(file, "utf8") : ""}\n${CI_MERGE_REQUEST_IID}`.split("\n");
|
|
227
159
|
const max = 1e4;
|
|
228
160
|
if (appended.length > max) appended = appended.slice(-max);
|
|
229
161
|
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
230
|
-
writeFileSync(file
|
|
231
|
-
console.log("merge pipeline, recorded IID:", CI_MERGE_REQUEST_IID, "to:", file
|
|
162
|
+
writeFileSync(file, appended.join("\n"), "utf8");
|
|
163
|
+
console.log("merge pipeline, recorded IID:", CI_MERGE_REQUEST_IID, "to:", file);
|
|
232
164
|
return true;
|
|
233
165
|
}
|
|
234
166
|
if (!CI_COMMIT_SHA) return true;
|
|
@@ -238,8 +170,8 @@ async function need() {
|
|
|
238
170
|
encoding: "utf8"
|
|
239
171
|
}).toString().match(/See merge request[\s\S]+!(\d+)/);
|
|
240
172
|
if (!match) return true;
|
|
241
|
-
const iid = match
|
|
242
|
-
const yes = (existsSync(file
|
|
173
|
+
const [, iid] = match;
|
|
174
|
+
const yes = (existsSync(file) ? readFileSync(file, "utf8") : "").split("\n").includes(iid);
|
|
243
175
|
if (yes) {
|
|
244
176
|
console.warn(`Merge Request !${iid} has been AI reviewed before.`);
|
|
245
177
|
const mergeURL = visitPipeline(iid).url;
|
|
@@ -255,32 +187,135 @@ async function need() {
|
|
|
255
187
|
}
|
|
256
188
|
return !yes;
|
|
257
189
|
}
|
|
190
|
+
const dingding = async (...args) => {
|
|
191
|
+
try {
|
|
192
|
+
const msg = await (await sendding(...args)).text();
|
|
193
|
+
console.log(msg);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.warn(error);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
258
198
|
|
|
259
199
|
//#endregion
|
|
260
|
-
//#region
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
200
|
+
//#region review/token.copilot.ts
|
|
201
|
+
const GITHUB_API = "https://api.github.com";
|
|
202
|
+
const COPILOT_HEADERS = {
|
|
203
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
204
|
+
"Editor-Version": "vscode/1.107.0",
|
|
205
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
206
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
207
|
+
};
|
|
208
|
+
function getOAuthToken() {
|
|
209
|
+
const token = env.GITHUB_COPILOT_TOKEN || env.COPILOT_TOKEN || env.GITHUB_TOKEN || env.GH_TOKEN;
|
|
210
|
+
if (!token) throw new Error("未找到 GitHub Token,请设置 GITHUB_COPILOT_TOKEN / GITHUB_TOKEN / GH_TOKEN 环境变量");
|
|
211
|
+
return token;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 将 OAuth token 换成 Copilot session token
|
|
215
|
+
* 新版 OpenCode 官方 OAuth 接入需要此步骤才能访问 /copilot_internal/* API
|
|
216
|
+
*/
|
|
217
|
+
async function exchangeForCopilotToken(oauthToken) {
|
|
218
|
+
try {
|
|
219
|
+
const res = await fetch(`${GITHUB_API}/copilot_internal/v2/token`, { headers: {
|
|
220
|
+
Accept: "application/json",
|
|
221
|
+
Authorization: `Bearer ${oauthToken}`,
|
|
222
|
+
...COPILOT_HEADERS
|
|
223
|
+
} });
|
|
224
|
+
if (!res.ok) return void 0;
|
|
225
|
+
return (await res.json()).token || void 0;
|
|
226
|
+
} catch {
|
|
269
227
|
return;
|
|
270
228
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
229
|
+
}
|
|
230
|
+
async function fetchInternalUsage() {
|
|
231
|
+
const oauthToken = getOAuthToken();
|
|
232
|
+
const directRes = await fetch(`${GITHUB_API}/copilot_internal/user`, { headers: {
|
|
233
|
+
Accept: "application/json",
|
|
234
|
+
Authorization: `token ${oauthToken}`,
|
|
235
|
+
...COPILOT_HEADERS
|
|
236
|
+
} });
|
|
237
|
+
if (directRes.ok) return directRes.json();
|
|
238
|
+
const copilotToken = await exchangeForCopilotToken(oauthToken);
|
|
239
|
+
if (!copilotToken) throw new Error([
|
|
240
|
+
"Copilot OAuth token 无法访问配额 API。",
|
|
241
|
+
"请创建 fine-grained PAT(https://github.com/settings/tokens?type=beta)",
|
|
242
|
+
"并在 Account permissions 中设置 Plan = Read-only,",
|
|
243
|
+
"然后通过 GITHUB_PAT 环境变量传入。"
|
|
244
|
+
].join("\n"));
|
|
245
|
+
const res = await fetch(`${GITHUB_API}/copilot_internal/user`, { headers: {
|
|
246
|
+
Accept: "application/json",
|
|
247
|
+
Authorization: `Bearer ${copilotToken}`,
|
|
248
|
+
...COPILOT_HEADERS
|
|
249
|
+
} });
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
const text = await res.text();
|
|
252
|
+
throw new Error(`查询 Copilot 内部 API 失败: ${res.status} ${text}`);
|
|
253
|
+
}
|
|
254
|
+
return res.json();
|
|
255
|
+
}
|
|
256
|
+
function formatInternalResult(data) {
|
|
257
|
+
const premium = data.quota_snapshots.premium_interactions;
|
|
258
|
+
return premium.entitlement - premium.remaining;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 查询 Copilot Premium Request 使用量
|
|
262
|
+
*
|
|
263
|
+
* 优先级:
|
|
264
|
+
* 1. 若设置了 GITHUB_PAT(fine-grained PAT)→ 使用 Public Billing API,支持模型明细
|
|
265
|
+
* 2. 否则使用 Internal API(GITHUB_COPILOT_TOKEN OAuth token),返回配额百分比和重置时间
|
|
266
|
+
*/
|
|
267
|
+
async function getCopilotUsage(options = {}) {
|
|
268
|
+
try {
|
|
269
|
+
return formatInternalResult(await fetchInternalUsage());
|
|
270
|
+
} catch {
|
|
271
|
+
return "使用量未知";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region review/copilot/deploy.ts
|
|
277
|
+
const config = join(dirname(fileURLToPath(import.meta.url)), "copilot-mcp.json");
|
|
278
|
+
const tools = [
|
|
279
|
+
"--additional-mcp-config",
|
|
280
|
+
`"@${config}"`,
|
|
281
|
+
"--model",
|
|
282
|
+
"claude-opus-4.6",
|
|
283
|
+
"--allow-all-paths",
|
|
284
|
+
"--allow-all-tools",
|
|
285
|
+
"--enable-all-github-mcp-tools",
|
|
286
|
+
"--deny-tool",
|
|
287
|
+
"write",
|
|
288
|
+
"--stream",
|
|
289
|
+
"off"
|
|
290
|
+
];
|
|
291
|
+
async function deploy() {
|
|
292
|
+
if (!env.CI) return;
|
|
293
|
+
await changelog();
|
|
294
|
+
execSync("npm i -g @github/copilot@latest --registry=https://registry.npmmirror.com", { stdio: "inherit" });
|
|
295
|
+
deploySkills();
|
|
296
|
+
const token = await getCopilotUsage();
|
|
297
|
+
env["TOKEN_USAGE"] = String(token);
|
|
298
|
+
writeFileSync(config, JSON.stringify({ mcpServers: { [name]: {
|
|
299
|
+
type: "local",
|
|
300
|
+
command: "node",
|
|
301
|
+
args: [serveFile, `--customizenv=${JSON.stringify(envUsed)}`],
|
|
302
|
+
tools: ["*"]
|
|
303
|
+
} } }, void 0, 2), "utf8");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region review/copilot/index.ts
|
|
308
|
+
const bind = "copilot";
|
|
309
|
+
async function cli() {
|
|
310
|
+
await deploy();
|
|
311
|
+
const httpProxy = env.HTTP_PROXY || env.http_proxy || "";
|
|
312
|
+
const httpsProxy = env.HTTPS_PROXY || env.https_proxy || "";
|
|
313
|
+
if (httpProxy) env.HTTP_PROXY = httpProxy;
|
|
314
|
+
if (httpsProxy) env.HTTPS_PROXY = httpsProxy;
|
|
315
|
+
const child = spawn(bind, [
|
|
279
316
|
...tools,
|
|
280
|
-
"--stream",
|
|
281
|
-
"off",
|
|
282
317
|
"-p",
|
|
283
|
-
|
|
318
|
+
`"${common_review_prompt.replace(/"/g, "\\\"")}"`
|
|
284
319
|
], {
|
|
285
320
|
cwd: env.CI_PROJECT_DIR,
|
|
286
321
|
stdio: [
|
|
@@ -288,169 +323,30 @@ Otherwise, use chinese as default language to call the mcp tool '${name$1}-${rep
|
|
|
288
323
|
"pipe",
|
|
289
324
|
"pipe"
|
|
290
325
|
],
|
|
291
|
-
...platform === "win32" && {
|
|
292
|
-
env: {
|
|
293
|
-
...process.env,
|
|
294
|
-
HTTP_PROXY: httpProxy,
|
|
295
|
-
HTTPS_PROXY: httpsProxy
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
copilot.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
299
|
-
copilot.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
300
|
-
return new Promise((res, rej) => {
|
|
301
|
-
copilot.on("close", (code) => res());
|
|
326
|
+
...platform === "win32" && { shell: true }
|
|
302
327
|
});
|
|
328
|
+
child.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
329
|
+
child.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
330
|
+
return new Promise((res, rej) => child.on("close", (code) => getCopilotUsage().then((res) => console.log("本次Token积累使用量:", res)).catch(() => {}).finally(() => {
|
|
331
|
+
if (code === 0) res();
|
|
332
|
+
else rej(/* @__PURE__ */ new Error(`${bind} exited with code ${code}`));
|
|
333
|
+
})));
|
|
303
334
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const proxyKeys = Object.keys(process.env).filter((k) => /proxy/i.test(k));
|
|
309
|
-
if (proxyKeys.length > 0) console.log(`${tag} 当前进程里含 proxy 的环境变量名: ${proxyKeys.join(", ")}`);
|
|
310
|
-
else console.log(`${tag} 当前进程里没有任何含 proxy 的环境变量(GitLab 未传入或未生效)`);
|
|
311
|
-
console.log(`${tag} 当前进程 env: HTTP_PROXY = ${process.env.HTTP_PROXY ?? "(未设置)"}, http_proxy = ${process.env.http_proxy ?? "(未设置)"}`);
|
|
312
|
-
console.log(`${tag} 当前进程 env: HTTPS_PROXY = ${process.env.HTTPS_PROXY ?? "(未设置)"}, https_proxy = ${process.env.https_proxy ?? "(未设置)"}`);
|
|
313
|
-
console.log(`${tag} 传给子进程: HTTP_PROXY = ${httpProxy || "(空)"}, HTTPS_PROXY = ${httpsProxy || "(空)"}`);
|
|
314
|
-
const proxyUrl = httpsProxy || httpProxy;
|
|
315
|
-
if (!proxyUrl) {
|
|
316
|
-
console.log(`${tag} 结论: 未设置代理,子进程将直连外网,不会经代理`);
|
|
317
|
-
await logGitHubReachable(tag, false);
|
|
318
|
-
console.log(`${tag} ------------------------`);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
let host;
|
|
322
|
-
let port;
|
|
323
|
-
try {
|
|
324
|
-
const u = new URL(proxyUrl);
|
|
325
|
-
host = u.hostname || "127.0.0.1";
|
|
326
|
-
port = u.port ? parseInt(u.port, 10) : 7890;
|
|
327
|
-
} catch {
|
|
328
|
-
console.log(`${tag} 结论: 代理 URL 解析失败,子进程可能无法正确使用代理: ${proxyUrl}`);
|
|
329
|
-
console.log(`${tag} ------------------------`);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const reachable = await checkPortReachable(host, port);
|
|
333
|
-
if (reachable) {
|
|
334
|
-
console.log(`${tag} 代理检测: ${host}:${port} 可连接,代理服务应在运行`);
|
|
335
|
-
logPortListener(port, tag);
|
|
336
|
-
} else {
|
|
337
|
-
console.log(`${tag} 代理检测: ${host}:${port} 无法连接,可能代理未启动或地址/端口错误`);
|
|
338
|
-
logPortListener(port, tag);
|
|
339
|
-
}
|
|
340
|
-
await logGitHubReachable(tag, !!proxyUrl);
|
|
341
|
-
if (reachable) console.log(`${tag} 结论: 子进程已继承代理环境变量,请求应能通过代理发出`);
|
|
342
|
-
else console.log(`${tag} 结论: 子进程虽有代理变量,但代理不可达,请求可能失败或直连`);
|
|
343
|
-
console.log(`${tag} ------------------------`);
|
|
344
|
-
}
|
|
345
|
-
function checkPortReachable(host, port, timeoutMs = 3e3) {
|
|
346
|
-
return new Promise((resolve) => {
|
|
347
|
-
const socket = createConnection(port, host, () => {
|
|
348
|
-
socket.destroy();
|
|
349
|
-
resolve(true);
|
|
350
|
-
});
|
|
351
|
-
socket.setTimeout(timeoutMs);
|
|
352
|
-
socket.on("timeout", () => {
|
|
353
|
-
socket.destroy();
|
|
354
|
-
resolve(false);
|
|
355
|
-
});
|
|
356
|
-
socket.on("error", () => resolve(false));
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
/** 打印指定端口上的监听进程(如 7890 上是 mihomo) */
|
|
360
|
-
function logPortListener(port, tag) {
|
|
335
|
+
|
|
336
|
+
//#endregion
|
|
337
|
+
//#region review/index.ts
|
|
338
|
+
async function codereview() {
|
|
361
339
|
try {
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
encoding: "utf8",
|
|
365
|
-
maxBuffer: 65536
|
|
366
|
-
});
|
|
367
|
-
console.log(`${tag} 端口 ${port} 监听情况 (netstat):\n${out.trim().split("\n").slice(0, 10).join("\n")}`);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const lsof = execSync(`lsof -i :${port} 2>/dev/null`, {
|
|
371
|
-
encoding: "utf8",
|
|
372
|
-
maxBuffer: 65536
|
|
373
|
-
}).trim();
|
|
374
|
-
if (lsof) {
|
|
375
|
-
console.log(`${tag} 端口 ${port} 监听进程 (lsof):\n${lsof}`);
|
|
340
|
+
if (!await need()) {
|
|
341
|
+
console.log("重复提交,进程跳过");
|
|
376
342
|
return;
|
|
377
343
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
maxBuffer: 65536
|
|
383
|
-
}).trim();
|
|
384
|
-
if (ss) console.log(`${tag} 端口 ${port} 监听进程 (ss):\n${ss}`);
|
|
385
|
-
else console.log(`${tag} 端口 ${port}: 无法获取监听进程 (无 lsof/ss 或无权限)`);
|
|
386
|
-
} catch {
|
|
387
|
-
console.log(`${tag} 端口 ${port}: 无法获取监听进程 (无 lsof/ss 或无权限)`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
/** 检测 GitHub 是否可访问(Node 内置 fetch 不走代理,此处为直连可达性) */
|
|
391
|
-
async function logGitHubReachable(tag, proxySet) {
|
|
392
|
-
const url = "https://api.github.com";
|
|
393
|
-
const note = proxySet ? " (当前进程已设代理,但 Node fetch 不经过代理,此处为直连)" : " (直连)";
|
|
394
|
-
try {
|
|
395
|
-
const ac = new AbortController();
|
|
396
|
-
const t = setTimeout(() => ac.abort(), 1e4);
|
|
397
|
-
const res = await fetch(url, { signal: ac.signal });
|
|
398
|
-
clearTimeout(t);
|
|
399
|
-
const ok = res.ok || res.status === 403;
|
|
400
|
-
console.log(`${tag} GitHub 连通性${note}: ${ok ? "可调通" : "异常"} status=${res.status}`);
|
|
401
|
-
} catch (e) {
|
|
402
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
403
|
-
console.log(`${tag} GitHub 连通性${note}: 调不通 - ${msg}`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
function findCopilopt() {
|
|
407
|
-
let copilot = "";
|
|
408
|
-
try {
|
|
409
|
-
copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
410
|
-
} catch {}
|
|
411
|
-
if (!copilot) {
|
|
412
|
-
const first = platform === "win32" ? win : linux;
|
|
413
|
-
const second = platform === "win32" ? linux : win;
|
|
414
|
-
copilot = first();
|
|
415
|
-
if (!copilot) copilot = second();
|
|
416
|
-
if (!copilot) throw new Error("没找到安装的包");
|
|
417
|
-
}
|
|
418
|
-
const pkg = join(copilot, "package.json");
|
|
419
|
-
if (!existsSync(pkg)) throw new Error("安装的包找不到正确版本 " + pkg);
|
|
420
|
-
const copilotPackage = JSON.parse(readFileSync(pkg, "utf8"));
|
|
421
|
-
const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.["@github/copilot"];
|
|
422
|
-
if (!binPath) throw new Error("non copilot executable found");
|
|
423
|
-
const copilotVersion = copilotPackage.version || "unknown";
|
|
424
|
-
const copilotPath = join(copilot, binPath);
|
|
425
|
-
console.log(`${NAME} server:
|
|
426
|
-
version: ${VERSION}
|
|
427
|
-
path: ${serveFile}
|
|
428
|
-
copilot:
|
|
429
|
-
version: ${copilotVersion}
|
|
430
|
-
path: ${copilotPath}`);
|
|
431
|
-
return copilotPath;
|
|
432
|
-
}
|
|
433
|
-
function win() {
|
|
434
|
-
const pathEnv = env.PATH || env.Path || "";
|
|
435
|
-
const pathSeparator = platform === "win32" ? ";" : ":";
|
|
436
|
-
const npm = pathEnv.split(pathSeparator).find((p) => p.includes("npm"));
|
|
437
|
-
if (!npm) return "";
|
|
438
|
-
const fallbackPath = join(npm, "node_modules", "@github/copilot");
|
|
439
|
-
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
440
|
-
return "";
|
|
441
|
-
}
|
|
442
|
-
function linux() {
|
|
443
|
-
let cached = env.NVM_BIN;
|
|
444
|
-
if (!cached) {
|
|
445
|
-
const pathEnv = env.PATH || env.Path || "";
|
|
446
|
-
const pathSeparator = platform === "win32" ? ";" : ":";
|
|
447
|
-
const npm = pathEnv.split(pathSeparator).find((p) => p.includes(".nvm"));
|
|
448
|
-
if (npm) cached = npm;
|
|
344
|
+
await cli();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
await dingding("CRITICAL", "CI ERROR: 未知错误,请自行检查日志");
|
|
347
|
+
throw error;
|
|
449
348
|
}
|
|
450
|
-
if (!cached) return "";
|
|
451
|
-
const fallbackPath = join(cached, "..", "lib", "node_modules", "@github/copilot");
|
|
452
|
-
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
453
|
-
return "";
|
|
454
349
|
}
|
|
350
|
+
if (argv.includes("codereview")) codereview();
|
|
455
351
|
|
|
456
352
|
//#endregion
|