@dev-angsu/cli 1.0.0 → 1.0.2
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/index.js +13 -2
- package/package.json +9 -3
- package/src/utils/engine.js +144 -0
package/bin/index.js
CHANGED
|
@@ -10,13 +10,15 @@ import chalk from "chalk";
|
|
|
10
10
|
import ora from "ora";
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
|
|
13
|
+
import { run as review } from "../src/utils/engine.js";
|
|
14
|
+
|
|
13
15
|
const program = new Command();
|
|
14
16
|
|
|
15
17
|
// 1. Metadata
|
|
16
18
|
program
|
|
17
19
|
.name("cli")
|
|
18
|
-
.description("A CLI to demonstrate industry
|
|
19
|
-
.version("1.0.
|
|
20
|
+
.description("A CLI to demonstrate industry")
|
|
21
|
+
.version("1.0.1");
|
|
20
22
|
|
|
21
23
|
// 2. Define a Command
|
|
22
24
|
program
|
|
@@ -73,5 +75,14 @@ program
|
|
|
73
75
|
}
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
// 3. Review Command
|
|
79
|
+
program
|
|
80
|
+
.command("review")
|
|
81
|
+
.alias("r")
|
|
82
|
+
.description("Review staged changes using AI")
|
|
83
|
+
.action(async () => {
|
|
84
|
+
await review();
|
|
85
|
+
});
|
|
86
|
+
|
|
76
87
|
// 6. Parse Arguments
|
|
77
88
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-angsu/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"
|
|
10
|
+
"dev-angsu": "./bin/index.js",
|
|
11
|
+
"review-code": "./src/utils/engine.js"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [],
|
|
13
14
|
"author": "",
|
|
14
15
|
"license": "ISC",
|
|
15
16
|
"type": "module",
|
|
16
17
|
"dependencies": {
|
|
18
|
+
"@actions/core": "^2.0.1",
|
|
19
|
+
"@actions/github": "^6.0.1",
|
|
17
20
|
"chalk": "^5.6.2",
|
|
18
21
|
"commander": "^14.0.2",
|
|
22
|
+
"dotenv": "^17.2.3",
|
|
19
23
|
"inquirer": "^13.1.0",
|
|
20
|
-
"
|
|
24
|
+
"openai": "^6.15.0",
|
|
25
|
+
"ora": "^9.0.0",
|
|
26
|
+
"simple-git": "^3.30.0"
|
|
21
27
|
},
|
|
22
28
|
"files": [
|
|
23
29
|
"bin",
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
import OpenAI from "openai";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import "dotenv/config"; // Loads .env file automatically
|
|
7
|
+
|
|
8
|
+
import * as core from "@actions/core";
|
|
9
|
+
import * as github from "@actions/github";
|
|
10
|
+
|
|
11
|
+
const git = simpleGit();
|
|
12
|
+
|
|
13
|
+
// async function getStagedDiff() {
|
|
14
|
+
// const diff = await git.diff(["--staged"]);
|
|
15
|
+
// return diff;
|
|
16
|
+
// }
|
|
17
|
+
// STRATEGY 1: Local Development
|
|
18
|
+
async function getLocalDiff() {
|
|
19
|
+
console.log("💻 Running in Local Mode...");
|
|
20
|
+
const git = simpleGit();
|
|
21
|
+
const diff = await git.diff(["--staged"]);
|
|
22
|
+
return diff;
|
|
23
|
+
}
|
|
24
|
+
// STRATEGY 2: GitHub Actions (CI/CD)
|
|
25
|
+
async function getPRDiff() {
|
|
26
|
+
console.log("☁️ Running in GitHub Actions Mode...");
|
|
27
|
+
|
|
28
|
+
// The 'GITHUB_TOKEN' is automatically provided by the workflow
|
|
29
|
+
const token = process.env.GITHUB_TOKEN;
|
|
30
|
+
const octokit = github.getOctokit(token);
|
|
31
|
+
|
|
32
|
+
const context = github.context;
|
|
33
|
+
const prNumber = context.payload.pull_request?.number;
|
|
34
|
+
|
|
35
|
+
if (!prNumber) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"❌ No Pull Request found in context. Are you running this on push?"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fetch the diff specifically
|
|
42
|
+
const { data: diff } = await octokit.rest.pulls.get({
|
|
43
|
+
owner: context.repo.owner,
|
|
44
|
+
repo: context.repo.repo,
|
|
45
|
+
pull_number: prNumber,
|
|
46
|
+
mediaType: {
|
|
47
|
+
format: "diff", // Ask GitHub to return the raw diff text
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return diff;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function generateReview(diff) {
|
|
55
|
+
// 1. Token Safety: Truncate if too huge (basic safety mechanism)
|
|
56
|
+
const MAX_CHARS = 15000;
|
|
57
|
+
const processedDiff =
|
|
58
|
+
diff.length > MAX_CHARS
|
|
59
|
+
? diff.substring(0, MAX_CHARS) + "\n...[Diff Truncated due to size]..."
|
|
60
|
+
: diff;
|
|
61
|
+
|
|
62
|
+
console.log("🤔 Analyzing changes...");
|
|
63
|
+
|
|
64
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
65
|
+
throw new Error("❌ Error: OPENAI_API_KEY is missing in .env file.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const openai = new OpenAI({
|
|
69
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
70
|
+
baseURL: process.env.AI_BASE_URL || "https://api.z.ai/api/paas/v4/",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const response = await openai.chat.completions.create({
|
|
74
|
+
model: process.env.AI_MODEL || "glm-4.6v-flash",
|
|
75
|
+
messages: [
|
|
76
|
+
{
|
|
77
|
+
role: "system",
|
|
78
|
+
content: `You are a Senior Software Engineer doing a Code Review.
|
|
79
|
+
|
|
80
|
+
Rules:
|
|
81
|
+
1. Summarize the changes in 1 sentence.
|
|
82
|
+
2. Identify any critical bugs or security risks (SQL injection, hardcoded secrets).
|
|
83
|
+
3. Suggest code style improvements (focus on readability).
|
|
84
|
+
4. Generate a concise, conventional commit message for these changes.
|
|
85
|
+
5. If the code looks good, output "LGTM" (Looks Good To Me) with a thumbs up.
|
|
86
|
+
|
|
87
|
+
Format your response in nice Markdown.`,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
role: "user",
|
|
91
|
+
content: `Here is the git diff of the changes:\n\n${processedDiff}`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return response.choices[0].message.content;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Main Execution
|
|
100
|
+
export async function run() {
|
|
101
|
+
try {
|
|
102
|
+
// const diff = await getStagedDiff();
|
|
103
|
+
// 1. SELECT STRATEGY
|
|
104
|
+
let diff;
|
|
105
|
+
if (process.env.CI) {
|
|
106
|
+
diff = await getPRDiff();
|
|
107
|
+
} else {
|
|
108
|
+
diff = await getLocalDiff();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!diff) {
|
|
112
|
+
console.log("⚠️ No staged changes found. Did you run 'git add'?");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const review = await generateReview(diff);
|
|
117
|
+
if (process.env.CI) {
|
|
118
|
+
// In CI, we post a comment back to the PR
|
|
119
|
+
const token = process.env.GITHUB_TOKEN;
|
|
120
|
+
const octokit = github.getOctokit(token);
|
|
121
|
+
const context = github.context;
|
|
122
|
+
|
|
123
|
+
await octokit.rest.issues.createComment({
|
|
124
|
+
owner: context.repo.owner,
|
|
125
|
+
repo: context.repo.repo,
|
|
126
|
+
issue_number: context.payload.pull_request.number,
|
|
127
|
+
body: review,
|
|
128
|
+
});
|
|
129
|
+
console.log("✅ Review posted to GitHub PR!");
|
|
130
|
+
} else {
|
|
131
|
+
// Locally, we just print it
|
|
132
|
+
console.log("\n================ 🤖 AI CODE REVIEW ================ \n");
|
|
133
|
+
console.log(review);
|
|
134
|
+
console.log("\n=================================================== \n");
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error("Error:", error.message);
|
|
138
|
+
if (process.env.CI) core.setFailed(error.message); // Fail the pipeline
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
143
|
+
run();
|
|
144
|
+
}
|