@d5render/cli 0.0.91 → 0.1.3

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/bin/d5cli CHANGED
@@ -1,99 +1,272 @@
1
- #!/usr/bin/env node
2
-
3
- import { execSync, spawn } from "node:child_process";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { dirname, join } from "node:path";
6
- import { env, exit, platform } from "node:process";
7
- import { fileURLToPath } from "node:url";
8
- import { errorMessage, getHash, name, report, tools } from "../copilot.config.js";
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
-
12
- const prompt = `call the mcp tool '${name}-${getHash}' to load review commits, if the task encounters an error, throw that, otherwise, you can decide for yourself which tools to use to enrich context and determine whether a specific commit needs to be reviewed.
13
- Then, use chinese as default language to organize the report into JSON format and call the mcp tool '${name}-${report}', please ignore error encountered when calling this and report title should not contain words such as MCP or "中文".`;
14
-
15
- // TODO: 只支持保护分支触发
16
- install();
17
- const copilot = spawn("node", [findCopilopt(), "--stream", "off", ...tools, "-p", prompt], {
18
- cwd: env.CI_PROJECT_DIR,
19
- ...(platform === "win32" && { windowsHide: true }),
20
- });
21
- let hasError = false;
22
- const errorRegex = new RegExp(errorMessage, "i");
23
- copilot.stdout.on("data", (data) => {
24
- const str = String(data);
25
- console.log(str);
26
-
27
- if (errorRegex.test(str)) hasError = true;
28
- });
29
- copilot.stderr.on("data", (data) => console.error(String(data)));
30
- copilot.on("close", () => {
31
- if (hasError) exit(1);
32
- else exit(0);
33
- });
34
-
35
- function install() {
36
- if (!env.CI) return;
37
- console.log("install dependencies...");
38
- try {
39
- const local = execSync("npm list @github/copilot -g --depth=0 --json").toString();
40
- const localInfo = JSON.parse(local);
41
- const localVersion = localInfo.dependencies ? localInfo.dependencies?.["@github/copilot"]?.version : null;
42
- const remoteVersion = execSync("npm view @github/copilot version --registry=https://registry.npmmirror.com")
43
- .toString()
44
- .trim();
45
- if (localVersion !== remoteVersion) {
46
- execSync("npm uninstall -g @github/copilot --force");
47
- execSync("npm install -g @github/copilot --registry=https://registry.npmmirror.com", {
48
- stdio: "inherit",
49
- });
50
- }
51
- } catch (error) {
52
- console.error("install dependencies error");
53
- console.error(error);
54
- if (error.status === 1 && error.stdout) {
55
- execSync("npm install -g @github/copilot --registry=https://registry.npmmirror.com", {
56
- stdio: "inherit",
57
- });
58
- console.log("install dependencies success");
59
- }
60
- }
61
- }
62
- function findCopilopt() {
63
- let copilot = execSync("npm list @github/copilot -g -p").toString().trim();
64
- if (!copilot) {
65
- try {
66
- const pathEnv = env.PATH || env.Path || "";
67
- const pathSeparator = platform === "win32" ? ";" : ":";
68
- const npm = pathEnv.split(pathSeparator).find((p) => p.includes("npm"));
69
- if (npm) copilot = join(npm, "node_modules", "@github", "copilot");
70
- } catch {}
71
- }
72
- const pkg = join(copilot, "package.json");
73
- if (!existsSync(pkg)) throw new Error("找不到 copilot 包");
74
- const copilotPackage = JSON.parse(readFileSync(pkg, "utf8"));
75
- // return join(copilot, copilotPackage.bin.copilot);
76
- // 兼容 bin 字段可能是字符串或对象的情况
77
- const binPath =
78
- typeof copilotPackage.bin === "string"
79
- ? copilotPackage.bin
80
- : copilotPackage.bin?.copilot || copilotPackage.bin?.["@github/copilot"];
81
- if (!binPath) throw new Error("找不到 copilot 可执行文件路径");
82
- const copilotVersion = copilotPackage.version || "未知版本";
83
-
84
- // 读取 d5cli 的版本
85
- const d5cliPkg = join(__dirname, "..", "package.json");
86
- let d5cliVersion = "未知版本";
87
- if (existsSync(d5cliPkg)) {
88
- try {
89
- const d5cliPackage = JSON.parse(readFileSync(d5cliPkg, "utf8"));
90
- d5cliVersion = d5cliPackage.version || "未知版本";
91
- } catch {}
92
- }
93
-
94
- console.log(
95
- join(copilot, binPath),
96
- `copilot 已经被发现,copilot 版本: ${copilotVersion}, d5cli 版本: ${d5cliVersion}`
97
- );
98
- return join(copilot, binPath);
99
- }
1
+ #!/usr/bin/env node
2
+ import { execSync, spawn } from "node:child_process";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { argv, env, exit, platform } from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ //#region package.json
9
+ var name = "@d5render/cli";
10
+ var version = "0.1.3";
11
+
12
+ //#endregion
13
+ //#region copilot/server/config.ts
14
+ const name$1 = "d5_mcp_review_builtin";
15
+ const report = "report";
16
+ const getHash = "getHash";
17
+ const file = "./copilot.js";
18
+ const serveFile = join(dirname(fileURLToPath(import.meta.url)), file);
19
+ const envJson = buildEnv();
20
+ const envUsed = {
21
+ CI_SERVER_URL: toEnv("CI_SERVER_URL"),
22
+ CI_PROJECT_PATH: toEnv("CI_PROJECT_PATH"),
23
+ CI_PROJECT_ID: toEnv("CI_PROJECT_ID"),
24
+ CI_PROJECT_NAME: toEnv("CI_PROJECT_NAME"),
25
+ CI_COMMIT_SHA: toEnv("CI_COMMIT_SHA"),
26
+ CI_COMMIT_BEFORE_SHA: toEnv("CI_COMMIT_BEFORE_SHA"),
27
+ CI_MERGE_REQUEST_IID: toEnv("CI_MERGE_REQUEST_IID"),
28
+ CI_MERGE_REQUEST_TITLE: toEnv("CI_MERGE_REQUEST_TITLE"),
29
+ CI_MERGE_REQUEST_DESCRIPTION: toEnv("CI_MERGE_REQUEST_DESCRIPTION"),
30
+ JIRA_BASE_URL: toEnv("JIRA_BASE_URL", "http://jira.d5techs.com.cn"),
31
+ DINGTALK_WEBHOOK: toEnv("DINGTALK_WEBHOOK"),
32
+ GITLAB_TOKEN: toEnv("GITLAB_TOKEN"),
33
+ JIRA_PAT: toEnv("JIRA_PAT"),
34
+ JIRA_COOKIE: toEnv("JIRA_COOKIE"),
35
+ JIRA_USERNAME: toEnv("JIRA_USERNAME"),
36
+ JIRA_PASSWORD: toEnv("JIRA_PASSWORD")
37
+ };
38
+ const tools = [
39
+ "--additional-mcp-config",
40
+ JSON.stringify({ mcpServers: { [name$1]: {
41
+ type: "local",
42
+ command: "node",
43
+ args: [serveFile, `--customizenv=${JSON.stringify(envUsed)}`],
44
+ tools: ["*"]
45
+ } } }),
46
+ "--allow-all-tools",
47
+ "--deny-tool",
48
+ "write",
49
+ "--deny-tool",
50
+ "github-mcp-server"
51
+ ];
52
+ function toEnv(key, defaultValue) {
53
+ return envJson[key] || process.env[key] || defaultValue;
54
+ }
55
+ function buildEnv() {
56
+ const envArg = argv.find((arg) => arg.startsWith("--customizenv="));
57
+ let envJson$1 = {};
58
+ if (envArg) envJson$1 = JSON.parse(envArg.replace("--customizenv=", ""));
59
+ return envJson$1;
60
+ }
61
+
62
+ //#endregion
63
+ //#region packages/gitlab/url.ts
64
+ function buildHeaders() {
65
+ const headers = new Headers();
66
+ headers.set("Content-Type", "application/json");
67
+ const { GITLAB_TOKEN = "" } = envUsed;
68
+ if (GITLAB_TOKEN.startsWith("glpat-")) headers.set("PRIVATE-TOKEN", GITLAB_TOKEN);
69
+ else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
70
+ return headers;
71
+ }
72
+ const visitCommit = () => {
73
+ const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
74
+ if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/commit`;
75
+ };
76
+ function visitPipeline(mid) {
77
+ const { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
78
+ if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
79
+ if (CI_MERGE_REQUEST_IID) return {
80
+ url: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
81
+ type: "merge_request"
82
+ };
83
+ if (CI_COMMIT_SHA) return {
84
+ url: `${visitCommit()}/${CI_COMMIT_SHA}`,
85
+ type: "commit"
86
+ };
87
+ return {};
88
+ }
89
+ const commits = () => {
90
+ const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
91
+ if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
92
+ };
93
+
94
+ //#endregion
95
+ //#region packages/message/dingding.ts
96
+ function send(title, text) {
97
+ if (!envUsed.DINGTALK_WEBHOOK) return Promise.reject(/* @__PURE__ */ new Error("non DINGTALK_WEBHOOK"));
98
+ const urls = envUsed.DINGTALK_WEBHOOK.split(",");
99
+ return Promise.all(urls.map((url) => fetch(url, {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({
103
+ msgtype: "markdown",
104
+ markdown: {
105
+ title,
106
+ text
107
+ },
108
+ at: {
109
+ atMobiles: ["17856104313"],
110
+ isAtAll: false
111
+ }
112
+ })
113
+ })));
114
+ }
115
+
116
+ //#endregion
117
+ //#region copilot/bin/deploy.ts
118
+ const root = () => {
119
+ const dir = env.HOME ?? env.HOMEPATH;
120
+ if (!dir) throw new Error("cannot find home directory");
121
+ return dir;
122
+ };
123
+ const cwd = join(dirname(fileURLToPath(import.meta.url)), "../");
124
+ function deploy() {
125
+ const cl = join(cwd, "bin/CHANGELOG.md");
126
+ if (existsSync(cl)) {
127
+ let clt = readFileSync(cl, "utf8");
128
+ if (env.DINGTALK_WEBHOOK) send("NOTICE", `#### cli upgrade\n\n${clt}\n\n##### [HISTORY CHANGELOG](https://www.npmjs.com/package/${name}/v/${version}?activeTab=code)`).then(() => rmSync(cl)).catch(() => {});
129
+ }
130
+ const dir = join(root(), ".copilot/skills/codereview");
131
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
132
+ const skillRoot = join(cwd, ".skills/review");
133
+ const versiono = readFileSync(join(cwd, ".skills/review/version"), "utf8");
134
+ const versionnPath = join(dir, "version");
135
+ if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === versiono) return;
136
+ readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
137
+ const instructionsRoot = join(cwd, ".github/instructions");
138
+ readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
139
+ }
140
+
141
+ //#endregion
142
+ //#region copilot/bin/canireview.ts
143
+ function canireview() {
144
+ const { CI_MERGE_REQUEST_IID, CI_COMMIT_SHA, CI_PROJECT_PATH } = envUsed;
145
+ const file$1 = join(root(), ".ci-ai-review", CI_PROJECT_PATH || "defaults");
146
+ const dir = dirname(file$1);
147
+ if (CI_MERGE_REQUEST_IID) {
148
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
149
+ let appended = `${existsSync(file$1) ? readFileSync(file$1, "utf8") : ""}\n${CI_MERGE_REQUEST_IID}`.split("\n");
150
+ const max = 5;
151
+ if (appended.length > max) appended = appended.slice(-max);
152
+ writeFileSync(file$1, appended.join("\n"), "utf8");
153
+ return true;
154
+ }
155
+ if (!CI_COMMIT_SHA) return true;
156
+ const match = execSync(`git log -1 --format=%B ${CI_COMMIT_SHA}`, {
157
+ cwd: env.CI_PROJECT_DIR,
158
+ encoding: "utf8"
159
+ }).toString().match(/See merge request[\s\S]+!(\d+)/);
160
+ if (!match) return true;
161
+ const iid = match[1];
162
+ const yes = (existsSync(file$1) ? readFileSync(file$1, "utf8") : "").split("\n").includes(iid);
163
+ if (yes) {
164
+ console.warn(`Merge Request !${iid} has been AI reviewed before.`);
165
+ const mergeURL = visitPipeline(iid).url;
166
+ const url = `${commits()}/${CI_COMMIT_SHA}/comments`;
167
+ fetch(url, {
168
+ method: "POST",
169
+ headers: buildHeaders(),
170
+ body: JSON.stringify({
171
+ note: `请查看管道评论:${mergeURL}#note`,
172
+ line_type: "new"
173
+ })
174
+ }).then((res) => res.text());
175
+ }
176
+ return !yes;
177
+ }
178
+
179
+ //#endregion
180
+ //#region copilot/bin/install.ts
181
+ function install() {
182
+ if (!env.CI) return;
183
+ console.log("install copilot...");
184
+ let success = false;
185
+ let local = "";
186
+ try {
187
+ local = execSync("npm list @github/copilot -g --depth=0 --json").toString();
188
+ } catch {
189
+ local = "{}";
190
+ }
191
+ try {
192
+ const localInfo = JSON.parse(local);
193
+ const localVersion = localInfo.dependencies ? localInfo.dependencies?.["@github/copilot"]?.version : void 0;
194
+ if (!localVersion) {
195
+ installCopilot();
196
+ success = true;
197
+ console.log("install copilot success");
198
+ return;
199
+ }
200
+ if (localVersion !== execSync("npm view @github/copilot version --registry=https://registry.npmmirror.com").toString().trim()) {
201
+ execSync("npm uninstall -g @github/copilot --force");
202
+ installCopilot();
203
+ success = true;
204
+ console.log("update copilot success");
205
+ } else {
206
+ success = true;
207
+ console.log("copilot exists and is up-to-date");
208
+ }
209
+ } catch (error) {
210
+ console.error(error);
211
+ }
212
+ if (!success) try {
213
+ console.warn("try to reinstall copilot...");
214
+ installCopilot();
215
+ success = true;
216
+ } catch (error) {
217
+ console.error(error);
218
+ }
219
+ if (success) return;
220
+ console.warn("try to reinstall copilot...");
221
+ installCopilot();
222
+ }
223
+ function installCopilot() {
224
+ execSync("npm install -g @github/copilot --registry=https://registry.npmmirror.com", { stdio: "inherit" });
225
+ }
226
+
227
+ //#endregion
228
+ //#region copilot/bin/index.ts
229
+ function review() {
230
+ install();
231
+ const copilot = spawn("node", [
232
+ findCopilopt(),
233
+ ...tools,
234
+ "-p",
235
+ prompt
236
+ ], {
237
+ cwd: env.CI_PROJECT_DIR,
238
+ ...platform === "win32" && { windowsHide: true }
239
+ });
240
+ copilot.stdout.on("data", (data) => console.log(String(data)));
241
+ copilot.stderr.on("data", (data) => console.error(String(data)));
242
+ copilot.on("close", (code) => exit(code));
243
+ }
244
+ function findCopilopt() {
245
+ let copilot = execSync("npm list @github/copilot -g -p").toString().trim();
246
+ if (!copilot) try {
247
+ const pathEnv = env.PATH || env.Path || "";
248
+ const pathSeparator = platform === "win32" ? ";" : ":";
249
+ const npm = pathEnv.split(pathSeparator).find((p) => p.includes("npm"));
250
+ if (npm) copilot = join(npm, "node_modules", "@github", "copilot");
251
+ } catch {}
252
+ const pkg = join(copilot, "package.json");
253
+ if (!existsSync(pkg)) throw new Error("non copilot package found");
254
+ const copilotPackage = JSON.parse(readFileSync(pkg, "utf8"));
255
+ const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.["@github/copilot"];
256
+ if (!binPath) throw new Error("non copilot executable found");
257
+ const copilotVersion = copilotPackage.version || "unknown";
258
+ const copilotPath = join(copilot, binPath);
259
+ console.log(`${name} server:
260
+ version: ${version}
261
+ path: ${serveFile}
262
+ copilot:
263
+ version: ${copilotVersion}
264
+ path: ${copilotPath}`);
265
+ return copilotPath;
266
+ }
267
+ const prompt = `call the mcp tool '${name$1}-${getHash}' to load review commits, if the task encounters an error, throw that, otherwise, you can decide for yourself which tools to use to enrich context and determine whether a specific commit needs to be reviewed.
268
+ Then, use chinese as default language to organize the report into JSON format and call the mcp tool '${name$1}-${report}', please ignore error encountered when calling this and report title should not contain words such as MCP or "中文".`;
269
+ deploy();
270
+ if (canireview()) review();
271
+
272
+ //#endregion
package/package.json CHANGED
@@ -4,26 +4,29 @@
4
4
  "license": "MIT",
5
5
  "author": "jasirou",
6
6
  "main": "./bin/d5cli",
7
- "version": "0.0.91",
7
+ "version": "0.1.3",
8
8
  "devDependencies": {
9
- "@modelcontextprotocol/sdk": "^1.24.3",
10
- "@types/node": "22.18.1",
11
- "@types/vscode": "^1.88.0",
12
- "@vscode/vsce": "^3.2.2",
9
+ "@modelcontextprotocol/sdk": "^1.25.1",
10
+ "@types/node": "^25.0.3",
11
+ "@types/vscode": "^1.107.0",
12
+ "@vscode/vsce": "^3.7.1",
13
13
  "oxfmt": "^0.21.0",
14
- "oxlint": "^1.36.0",
15
- "rolldown": "^1.0.0-beta.53",
16
- "zod": "^4.1.13"
14
+ "oxlint": "^1.37.0",
15
+ "rolldown": "1.0.0-beta.58",
16
+ "zod": "^4.3.5"
17
17
  },
18
18
  "files": [
19
+ ".github/instructions/review.instructions.md",
20
+ ".github/instructions/severity.instructions.md",
21
+ ".skills/review",
19
22
  "bin",
20
- "./copilot.config.js"
23
+ "CHANGELOG.md"
21
24
  ],
22
25
  "bin": {
23
26
  "d5cli": "bin/d5cli"
24
27
  },
25
28
  "scripts": {
26
- "build:copilot": "rolldown -c packages/copilot/rolldown.config.ts",
29
+ "build:copilot": "node ./setup.js build",
27
30
  "setup": "node ./setup.js",
28
31
  "release": "node ./setup.js publish"
29
32
  }
package/copilot.config.js DELETED
@@ -1,68 +0,0 @@
1
- import { dirname, join } from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- export const name = "d5_mcp_review_builtin";
5
- export const report = "report";
6
- export const getPrompt = "default_review_requirements";
7
- export const getHash = "getHash";
8
-
9
- export const errorMessage = "error: call mcp " + name;
10
- export const noMandatory = "dont't call under non-mandatory conditions";
11
-
12
- export const file = join(dirname(fileURLToPath(import.meta.url)), "bin/copilot.js");
13
-
14
- const { argv } = process;
15
- const envArg = argv.find((arg) => arg.startsWith("--customizenv="));
16
- let envJson = {};
17
- if (envArg) envJson = JSON.parse(envArg.replace("--customizenv=", ""));
18
- function toEnv(key, defaultValue = undefined) {
19
- return envJson[key] || process.env[key] || defaultValue;
20
- }
21
- // 辣鸡: https://github.com/modelcontextprotocol/typescript-sdk/issues/259
22
- export const envUsed = {
23
- CI_SERVER_URL: toEnv("CI_SERVER_URL"),
24
- CI_PROJECT_PATH: toEnv("CI_PROJECT_PATH"),
25
- CI_PROJECT_ID: toEnv("CI_PROJECT_ID"),
26
- CI_PROJECT_NAME: toEnv("CI_PROJECT_NAME"),
27
-
28
- CI_COMMIT_SHA: toEnv("CI_COMMIT_SHA"),
29
- CI_COMMIT_BEFORE_SHA: toEnv("CI_COMMIT_BEFORE_SHA"),
30
-
31
- CI_COMMIT_AUTHOR: toEnv("CI_COMMIT_AUTHOR"),
32
- CI_COMMIT_AUTHOR_EMAIL: toEnv("CI_COMMIT_AUTHOR_EMAIL"),
33
-
34
- CI_MERGE_REQUEST_IID: toEnv("CI_MERGE_REQUEST_IID"),
35
- CI_MERGE_REQUEST_TITLE: toEnv("CI_MERGE_REQUEST_TITLE"),
36
- CI_MERGE_REQUEST_DESCRIPTION: toEnv("CI_MERGE_REQUEST_DESCRIPTION"),
37
-
38
- JIRA_BASE_URL: toEnv("JIRA_BASE_URL", "http://jira.d5techs.com.cn"),
39
-
40
- DINGTALK_WEBHOOK: toEnv("DINGTALK_WEBHOOK"),
41
- GITLAB_TOKEN: toEnv("GITLAB_TOKEN"),
42
- JIRA_PAT: toEnv("JIRA_PAT"),
43
- JIRA_COOKIE: toEnv("JIRA_COOKIE"),
44
-
45
- JIRA_USERNAME: toEnv("JIRA_USERNAME"),
46
- JIRA_PASSWORD: toEnv("JIRA_PASSWORD")
47
- };
48
-
49
- const config = JSON.stringify({
50
- mcpServers: {
51
- [name]: {
52
- type: "local",
53
- command: "node",
54
- args: [file, `--customizenv=${JSON.stringify(envUsed)}`],
55
- tools: ["*"]
56
- }
57
- }
58
- });
59
- export const tools = [
60
- "--additional-mcp-config",
61
- config,
62
- "--allow-all-tools",
63
- "--resume",
64
- "--deny-tool",
65
- "write",
66
- "--deny-tool",
67
- "github-mcp-server"
68
- ];