@d5render/cli 0.1.22 → 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 -230
- package/bin/d5cli +164 -144
- package/package.json +4 -2
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"),
|
|
@@ -60,29 +188,6 @@ function buildEnv() {
|
|
|
60
188
|
return envJson$1;
|
|
61
189
|
}
|
|
62
190
|
|
|
63
|
-
//#endregion
|
|
64
|
-
//#region packages/message/sendding.ts
|
|
65
|
-
async function sendding(title, text) {
|
|
66
|
-
if (!envUsed.DINGTALK_WEBHOOK) throw new Error("non DINGTALK_WEBHOOK");
|
|
67
|
-
let res = new Response();
|
|
68
|
-
for (const url of envUsed.DINGTALK_WEBHOOK.split(",")) res = await fetch(url, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: { "Content-Type": "application/json" },
|
|
71
|
-
body: JSON.stringify({
|
|
72
|
-
msgtype: "markdown",
|
|
73
|
-
markdown: {
|
|
74
|
-
title,
|
|
75
|
-
text
|
|
76
|
-
},
|
|
77
|
-
at: {
|
|
78
|
-
atMobiles: ["17856104313"],
|
|
79
|
-
isAtAll: false
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
});
|
|
83
|
-
return res;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
191
|
//#endregion
|
|
87
192
|
//#region copilot/bin/install.ts
|
|
88
193
|
function install() {
|
|
@@ -131,130 +236,45 @@ function installCopilot() {
|
|
|
131
236
|
execSync("npm install -g @github/copilot --registry=https://registry.npmmirror.com", { stdio: "inherit" });
|
|
132
237
|
}
|
|
133
238
|
|
|
134
|
-
//#endregion
|
|
135
|
-
//#region packages/gitlab/url.ts
|
|
136
|
-
function buildHeaders() {
|
|
137
|
-
const headers = new Headers();
|
|
138
|
-
headers.set("Content-Type", "application/json");
|
|
139
|
-
const { GITLAB_TOKEN = "" } = envUsed;
|
|
140
|
-
if (GITLAB_TOKEN.startsWith("glpat-")) headers.set("PRIVATE-TOKEN", GITLAB_TOKEN);
|
|
141
|
-
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
142
|
-
return headers;
|
|
143
|
-
}
|
|
144
|
-
const visitCommit = () => {
|
|
145
|
-
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
146
|
-
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/commit`;
|
|
147
|
-
};
|
|
148
|
-
function visitPipeline(mid) {
|
|
149
|
-
const { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
150
|
-
if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
|
|
151
|
-
if (CI_MERGE_REQUEST_IID) return {
|
|
152
|
-
url: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
153
|
-
type: "merge_request"
|
|
154
|
-
};
|
|
155
|
-
if (CI_COMMIT_SHA) return {
|
|
156
|
-
url: `${visitCommit()}/${CI_COMMIT_SHA}`,
|
|
157
|
-
type: "commit"
|
|
158
|
-
};
|
|
159
|
-
return {};
|
|
160
|
-
}
|
|
161
|
-
const commits = () => {
|
|
162
|
-
const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
|
|
163
|
-
if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
//#endregion
|
|
167
|
-
//#region copilot/bin/utils.ts
|
|
168
|
-
const HOME = env.HOME ?? env.HOMEPATH;
|
|
169
|
-
const root = HOME ?? env.LOCALAPPDATA;
|
|
170
|
-
if (!root) throw new Error("cannot find home directory");
|
|
171
|
-
const cwd = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
172
|
-
function deploy() {
|
|
173
|
-
const config = join(HOME, ".copilot/config.json");
|
|
174
|
-
if (existsSync(config)) rmSync(config);
|
|
175
|
-
const dir = join(root, ".copilot/skills/codereview");
|
|
176
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
177
|
-
const skillRoot = join(cwd, ".skills/review");
|
|
178
|
-
const versiono = readFileSync(join(cwd, ".skills/review/version"), "utf8");
|
|
179
|
-
const versionnPath = join(dir, "version");
|
|
180
|
-
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === versiono) return;
|
|
181
|
-
readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
|
|
182
|
-
const instructionsRoot = join(cwd, ".github/instructions");
|
|
183
|
-
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
184
|
-
}
|
|
185
|
-
function need() {
|
|
186
|
-
const { CI_MERGE_REQUEST_IID, CI_COMMIT_SHA, CI_PROJECT_PATH } = env;
|
|
187
|
-
const file$1 = join(root, ".ci-ai-review", CI_PROJECT_PATH || "defaults");
|
|
188
|
-
const dir = dirname(file$1);
|
|
189
|
-
if (CI_MERGE_REQUEST_IID) {
|
|
190
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
191
|
-
let appended = `${existsSync(file$1) ? readFileSync(file$1, "utf8") : ""}\n${CI_MERGE_REQUEST_IID}`.split("\n");
|
|
192
|
-
const max = 5;
|
|
193
|
-
if (appended.length > max) appended = appended.slice(-max);
|
|
194
|
-
writeFileSync(file$1, appended.join("\n"), "utf8");
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
if (!CI_COMMIT_SHA) return true;
|
|
198
|
-
const match = execSync(`git log -1 --format=%B ${CI_COMMIT_SHA}`, {
|
|
199
|
-
cwd: env.CI_PROJECT_DIR,
|
|
200
|
-
encoding: "utf8"
|
|
201
|
-
}).toString().match(/See merge request[\s\S]+!(\d+)/);
|
|
202
|
-
if (!match) return true;
|
|
203
|
-
const iid = match[1];
|
|
204
|
-
const yes = (existsSync(file$1) ? readFileSync(file$1, "utf8") : "").split("\n").includes(iid);
|
|
205
|
-
if (yes) {
|
|
206
|
-
console.warn(`Merge Request !${iid} has been AI reviewed before.`);
|
|
207
|
-
const mergeURL = visitPipeline(iid).url;
|
|
208
|
-
const url = `${commits()}/${CI_COMMIT_SHA}/comments`;
|
|
209
|
-
fetch(url, {
|
|
210
|
-
method: "POST",
|
|
211
|
-
headers: buildHeaders(),
|
|
212
|
-
body: JSON.stringify({
|
|
213
|
-
note: `请查看管道评论:${mergeURL}#note`,
|
|
214
|
-
line_type: "new"
|
|
215
|
-
})
|
|
216
|
-
}).then((res) => res.text());
|
|
217
|
-
}
|
|
218
|
-
return !yes;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
239
|
//#endregion
|
|
222
240
|
//#region copilot/bin/index.ts
|
|
223
241
|
try {
|
|
224
|
-
|
|
242
|
+
codereview();
|
|
225
243
|
} catch (error) {
|
|
226
|
-
sendding("CRITICAL", "未知错误,请自行检查日志");
|
|
244
|
+
sendding("CRITICAL", "CRITICAL CI ERROR: 未知错误,请自行检查日志");
|
|
227
245
|
throw error;
|
|
228
246
|
}
|
|
229
|
-
function
|
|
247
|
+
function codereview() {
|
|
248
|
+
deploy();
|
|
249
|
+
if (!need()) {
|
|
250
|
+
console.log("重复提交,进程跳过");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
install();
|
|
230
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.
|
|
231
255
|
Otherwise, use chinese as default language to call the mcp tool '${name}-${report}'`;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
sendding("CRITICAL", "Copilot 运行过程发生错误,请自行检查日志");
|
|
255
|
-
exit(code);
|
|
256
|
-
});
|
|
257
|
-
} else console.log("重复提交,进程跳过");
|
|
256
|
+
const copilot = spawn("node", [
|
|
257
|
+
findCopilopt(),
|
|
258
|
+
...tools,
|
|
259
|
+
"--stream",
|
|
260
|
+
"off",
|
|
261
|
+
"-p",
|
|
262
|
+
prompt
|
|
263
|
+
], {
|
|
264
|
+
cwd: env.CI_PROJECT_DIR,
|
|
265
|
+
stdio: [
|
|
266
|
+
"inherit",
|
|
267
|
+
"pipe",
|
|
268
|
+
"pipe"
|
|
269
|
+
],
|
|
270
|
+
...platform === "win32" && { windowsHide: true }
|
|
271
|
+
});
|
|
272
|
+
copilot.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
273
|
+
copilot.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
274
|
+
copilot.on("close", (code) => {
|
|
275
|
+
sendding("CRITICAL", "CRITICAL CI ERROR: 代码审查任务失败,请自行检查日志");
|
|
276
|
+
exit(code);
|
|
277
|
+
});
|
|
258
278
|
}
|
|
259
279
|
function findCopilopt() {
|
|
260
280
|
let copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
@@ -272,8 +292,8 @@ function findCopilopt() {
|
|
|
272
292
|
if (!binPath) throw new Error("non copilot executable found");
|
|
273
293
|
const copilotVersion = copilotPackage.version || "unknown";
|
|
274
294
|
const copilotPath = join(copilot, binPath);
|
|
275
|
-
console.log(`${
|
|
276
|
-
version: ${
|
|
295
|
+
console.log(`${NAME} server:
|
|
296
|
+
version: ${VERSION}
|
|
277
297
|
path: ${serveFile}
|
|
278
298
|
copilot:
|
|
279
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,9 +18,11 @@
|
|
|
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"
|