@d5render/cli 0.1.18 → 0.1.24
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/.skills/devops/README.md +40 -0
- package/.skills/review/version +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +2 -39
- package/bin/copilot.js +245 -227
- package/bin/d5cli +151 -97
- package/package.json +6 -4
package/bin/d5cli
CHANGED
|
@@ -7,15 +7,143 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name$1 = "@d5render/cli";
|
|
10
|
-
var version = "0.1.
|
|
10
|
+
var version = "0.1.24";
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region packages/gitlab/url.ts
|
|
14
|
+
function buildHeaders() {
|
|
15
|
+
const headers = new Headers();
|
|
16
|
+
headers.set("Content-Type", "application/json");
|
|
17
|
+
const { GITLAB_TOKEN = "" } = envUsed;
|
|
18
|
+
if (GITLAB_TOKEN.startsWith("glpat-")) headers.set("PRIVATE-TOKEN", GITLAB_TOKEN);
|
|
19
|
+
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
22
|
+
const visitCommit = () => {
|
|
23
|
+
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
24
|
+
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/commit`;
|
|
25
|
+
};
|
|
26
|
+
function visitPipeline(mid) {
|
|
27
|
+
const { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
28
|
+
if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
|
|
29
|
+
if (CI_MERGE_REQUEST_IID) return {
|
|
30
|
+
url: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
31
|
+
type: "merge_request"
|
|
32
|
+
};
|
|
33
|
+
if (CI_COMMIT_SHA) return {
|
|
34
|
+
url: `${visitCommit()}/${CI_COMMIT_SHA}`,
|
|
35
|
+
type: "commit"
|
|
36
|
+
};
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
const commits = () => {
|
|
40
|
+
const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
|
|
41
|
+
if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region packages/message/sendding.ts
|
|
46
|
+
async function sendding(title, text) {
|
|
47
|
+
if (!envUsed.DINGTALK_WEBHOOK) throw new Error("non DINGTALK_WEBHOOK");
|
|
48
|
+
let res = new Response();
|
|
49
|
+
for (const url of envUsed.DINGTALK_WEBHOOK.split(",")) res = await fetch(url, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
msgtype: "markdown",
|
|
54
|
+
markdown: {
|
|
55
|
+
title,
|
|
56
|
+
text
|
|
57
|
+
},
|
|
58
|
+
at: {
|
|
59
|
+
atMobiles: ["17856104313"],
|
|
60
|
+
isAtAll: false
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
return res;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region copilot/bin/utils.ts
|
|
69
|
+
const NAME = name$1.replaceAll("/", "_");
|
|
70
|
+
const VERSION = version;
|
|
71
|
+
const RUNTIME_CWD = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
72
|
+
const HOME = env.USERPROFILE ?? env.HOME ?? env.HOMEPATH;
|
|
73
|
+
if (!HOME) throw new Error("cannot find `USERPROFILE` directory");
|
|
74
|
+
const TEMP = env.CI_PROJECT_DIR + NAME;
|
|
75
|
+
function deploy() {
|
|
76
|
+
if (!env.CI) return;
|
|
77
|
+
const config = join(HOME, ".copilot/config.json"), dir = join(HOME, ".copilot/skills/codereview");
|
|
78
|
+
console.log("deploy...");
|
|
79
|
+
if (existsSync(config)) {
|
|
80
|
+
rmSync(config);
|
|
81
|
+
console.log("removed config cache.");
|
|
82
|
+
}
|
|
83
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
84
|
+
const versiono = readFileSync(join(RUNTIME_CWD, ".skills/review/version"), "utf8");
|
|
85
|
+
const versionnPath = join(dir, "version");
|
|
86
|
+
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === versiono) return;
|
|
87
|
+
const skillRoot = join(RUNTIME_CWD, ".skills/review");
|
|
88
|
+
readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
|
|
89
|
+
const instructionsRoot = join(RUNTIME_CWD, ".github/instructions");
|
|
90
|
+
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
91
|
+
console.log("to new skill.");
|
|
92
|
+
const changelog = readFileSync(join(RUNTIME_CWD, "CHANGELOG.md"), "utf8");
|
|
93
|
+
const cachepath = join(TEMP, "CHANGELOG");
|
|
94
|
+
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
95
|
+
if (changelog !== (existsSync(cachepath) ? readFileSync(cachepath, "utf8") : "")) {
|
|
96
|
+
if (!existsSync(cachepath)) mkdirSync(cachepath, { recursive: true });
|
|
97
|
+
writeFileSync(cachepath, changelog, "utf8");
|
|
98
|
+
console.log("updated CHANGELOG cache.");
|
|
99
|
+
sendding("NOTICE", `NOTICE: codereview skills has been updated to version ${versiono}\n\nfor more details, please refer to [ONLINE](https://www.npmjs.com/package/@d5render/cli?activeTab=readme)`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function need() {
|
|
103
|
+
if (!env.CI) return;
|
|
104
|
+
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
105
|
+
const { CI_MERGE_REQUEST_IID, CI_COMMIT_SHA } = env;
|
|
106
|
+
const file$1 = join(TEMP, "CODEREVIEW");
|
|
107
|
+
if (CI_MERGE_REQUEST_IID) {
|
|
108
|
+
let appended = `${existsSync(file$1) ? readFileSync(file$1, "utf8") : ""}\n${CI_MERGE_REQUEST_IID}`.split("\n");
|
|
109
|
+
const max = 1e4;
|
|
110
|
+
if (appended.length > max) appended = appended.slice(-max);
|
|
111
|
+
writeFileSync(file$1, appended.join("\n"), "utf8");
|
|
112
|
+
console.log("merge pipeline, recorded IID:", CI_MERGE_REQUEST_IID, "to:", file$1);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (!CI_COMMIT_SHA) return true;
|
|
116
|
+
console.log("commit pipeline");
|
|
117
|
+
const match = execSync(`git log -1 --format=%B ${CI_COMMIT_SHA}`, {
|
|
118
|
+
cwd: env.CI_PROJECT_DIR,
|
|
119
|
+
encoding: "utf8"
|
|
120
|
+
}).toString().match(/See merge request[\s\S]+!(\d+)/);
|
|
121
|
+
if (!match) return true;
|
|
122
|
+
const iid = match[1];
|
|
123
|
+
const yes = (existsSync(file$1) ? readFileSync(file$1, "utf8") : "").split("\n").includes(iid);
|
|
124
|
+
if (yes) {
|
|
125
|
+
console.warn(`Merge Request !${iid} has been AI reviewed before.`);
|
|
126
|
+
const mergeURL = visitPipeline(iid).url;
|
|
127
|
+
const url = `${commits()}/${CI_COMMIT_SHA}/comments`;
|
|
128
|
+
fetch(url, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: buildHeaders(),
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
note: `请查看管道评论:${mergeURL}#note`,
|
|
133
|
+
line_type: "new"
|
|
134
|
+
})
|
|
135
|
+
}).then((res) => res.text());
|
|
136
|
+
}
|
|
137
|
+
return !yes;
|
|
138
|
+
}
|
|
11
139
|
|
|
12
140
|
//#endregion
|
|
13
141
|
//#region copilot/server/config.ts
|
|
14
142
|
const name = "d5_mcp_review_builtin";
|
|
15
143
|
const report = "report";
|
|
16
144
|
const getHash = "hash";
|
|
17
|
-
const file = "
|
|
18
|
-
const serveFile = join(
|
|
145
|
+
const file = "bin/copilot.js";
|
|
146
|
+
const serveFile = join(RUNTIME_CWD, file);
|
|
19
147
|
const envJson = buildEnv();
|
|
20
148
|
const envUsed = {
|
|
21
149
|
CI_SERVER_URL: toEnv("CI_SERVER_URL"),
|
|
@@ -109,99 +237,22 @@ function installCopilot() {
|
|
|
109
237
|
}
|
|
110
238
|
|
|
111
239
|
//#endregion
|
|
112
|
-
//#region
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
119
|
-
return headers;
|
|
120
|
-
}
|
|
121
|
-
const visitCommit = () => {
|
|
122
|
-
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
123
|
-
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/commit`;
|
|
124
|
-
};
|
|
125
|
-
function visitPipeline(mid) {
|
|
126
|
-
const { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
127
|
-
if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
|
|
128
|
-
if (CI_MERGE_REQUEST_IID) return {
|
|
129
|
-
url: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
130
|
-
type: "merge_request"
|
|
131
|
-
};
|
|
132
|
-
if (CI_COMMIT_SHA) return {
|
|
133
|
-
url: `${visitCommit()}/${CI_COMMIT_SHA}`,
|
|
134
|
-
type: "commit"
|
|
135
|
-
};
|
|
136
|
-
return {};
|
|
137
|
-
}
|
|
138
|
-
const commits = () => {
|
|
139
|
-
const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
|
|
140
|
-
if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
//#endregion
|
|
144
|
-
//#region copilot/bin/utils.ts
|
|
145
|
-
const HOME = env.HOME ?? env.HOMEPATH;
|
|
146
|
-
const root = HOME ?? env.LOCALAPPDATA;
|
|
147
|
-
if (!root) throw new Error("cannot find home directory");
|
|
148
|
-
const cwd = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
149
|
-
function deploy() {
|
|
150
|
-
const config = join(HOME, ".copilot/config.json");
|
|
151
|
-
if (existsSync(config)) rmSync(config);
|
|
152
|
-
const dir = join(root, ".copilot/skills/codereview");
|
|
153
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
154
|
-
const skillRoot = join(cwd, ".skills/review");
|
|
155
|
-
const versiono = readFileSync(join(cwd, ".skills/review/version"), "utf8");
|
|
156
|
-
const versionnPath = join(dir, "version");
|
|
157
|
-
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === versiono) return;
|
|
158
|
-
readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
|
|
159
|
-
const instructionsRoot = join(cwd, ".github/instructions");
|
|
160
|
-
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
240
|
+
//#region copilot/bin/index.ts
|
|
241
|
+
try {
|
|
242
|
+
codereview();
|
|
243
|
+
} catch (error) {
|
|
244
|
+
sendding("CRITICAL", "CRITICAL CI ERROR: 未知错误,请自行检查日志");
|
|
245
|
+
throw error;
|
|
161
246
|
}
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
168
|
-
let appended = `${existsSync(file$1) ? readFileSync(file$1, "utf8") : ""}\n${CI_MERGE_REQUEST_IID}`.split("\n");
|
|
169
|
-
const max = 5;
|
|
170
|
-
if (appended.length > max) appended = appended.slice(-max);
|
|
171
|
-
writeFileSync(file$1, appended.join("\n"), "utf8");
|
|
172
|
-
return true;
|
|
247
|
+
function codereview() {
|
|
248
|
+
deploy();
|
|
249
|
+
if (!need()) {
|
|
250
|
+
console.log("重复提交,进程跳过");
|
|
251
|
+
return;
|
|
173
252
|
}
|
|
174
|
-
if (!CI_COMMIT_SHA) return true;
|
|
175
|
-
const match = execSync(`git log -1 --format=%B ${CI_COMMIT_SHA}`, {
|
|
176
|
-
cwd: env.CI_PROJECT_DIR,
|
|
177
|
-
encoding: "utf8"
|
|
178
|
-
}).toString().match(/See merge request[\s\S]+!(\d+)/);
|
|
179
|
-
if (!match) return true;
|
|
180
|
-
const iid = match[1];
|
|
181
|
-
const yes = (existsSync(file$1) ? readFileSync(file$1, "utf8") : "").split("\n").includes(iid);
|
|
182
|
-
if (yes) {
|
|
183
|
-
console.warn(`Merge Request !${iid} has been AI reviewed before.`);
|
|
184
|
-
const mergeURL = visitPipeline(iid).url;
|
|
185
|
-
const url = `${commits()}/${CI_COMMIT_SHA}/comments`;
|
|
186
|
-
fetch(url, {
|
|
187
|
-
method: "POST",
|
|
188
|
-
headers: buildHeaders(),
|
|
189
|
-
body: JSON.stringify({
|
|
190
|
-
note: `请查看管道评论:${mergeURL}#note`,
|
|
191
|
-
line_type: "new"
|
|
192
|
-
})
|
|
193
|
-
}).then((res) => res.text());
|
|
194
|
-
}
|
|
195
|
-
return !yes;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region copilot/bin/index.ts
|
|
200
|
-
const prompt = `Load skills, then call the mcp tool '${name}-${getHash}' to load code-review commits, if the task encounters an error, throw that.
|
|
201
|
-
Otherwise, use chinese as default language to call the mcp tool '${name}-${report}'`;
|
|
202
|
-
deploy();
|
|
203
|
-
if (need()) {
|
|
204
253
|
install();
|
|
254
|
+
const prompt = `Load skills, then call the mcp tool '${name}-${getHash}' to load code-review commits, if the task encounters an error, throw that.
|
|
255
|
+
Otherwise, use chinese as default language to call the mcp tool '${name}-${report}'`;
|
|
205
256
|
const copilot = spawn("node", [
|
|
206
257
|
findCopilopt(),
|
|
207
258
|
...tools,
|
|
@@ -220,8 +271,11 @@ if (need()) {
|
|
|
220
271
|
});
|
|
221
272
|
copilot.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
222
273
|
copilot.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
223
|
-
copilot.on("close", (code) =>
|
|
224
|
-
|
|
274
|
+
copilot.on("close", (code) => {
|
|
275
|
+
sendding("CRITICAL", "CRITICAL CI ERROR: 代码审查任务失败,请自行检查日志");
|
|
276
|
+
exit(code);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
225
279
|
function findCopilopt() {
|
|
226
280
|
let copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
227
281
|
if (!copilot) {
|
|
@@ -238,8 +292,8 @@ function findCopilopt() {
|
|
|
238
292
|
if (!binPath) throw new Error("non copilot executable found");
|
|
239
293
|
const copilotVersion = copilotPackage.version || "unknown";
|
|
240
294
|
const copilotPath = join(copilot, binPath);
|
|
241
|
-
console.log(`${
|
|
242
|
-
version: ${
|
|
295
|
+
console.log(`${NAME} server:
|
|
296
|
+
version: ${VERSION}
|
|
243
297
|
path: ${serveFile}
|
|
244
298
|
copilot:
|
|
245
299
|
version: ${copilotVersion}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "jasirou",
|
|
6
6
|
"main": "./bin/d5cli",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.24",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
10
10
|
"@types/node": "^25.0.3",
|
|
@@ -18,16 +18,18 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
".github/instructions/review.instructions.md",
|
|
20
20
|
".github/instructions/severity.instructions.md",
|
|
21
|
+
".skills/devops",
|
|
21
22
|
".skills/review",
|
|
22
23
|
"bin/copilot.js",
|
|
23
|
-
"bin/d5cli"
|
|
24
|
+
"bin/d5cli",
|
|
25
|
+
"CHANGELOG.md"
|
|
24
26
|
],
|
|
25
27
|
"bin": {
|
|
26
28
|
"d5cli": "bin/d5cli"
|
|
27
29
|
},
|
|
28
30
|
"scripts": {
|
|
29
31
|
"build:copilot": "node ./setup.js build",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
+
"release": "node ./setup.js publish",
|
|
33
|
+
"setup": "node ./setup.js"
|
|
32
34
|
}
|
|
33
35
|
}
|