@review-my-code/rmcode 0.1.6 → 0.1.7
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/dist/cli.js +117 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const path_1 = require("path");
|
|
|
9
9
|
const readline_1 = require("readline");
|
|
10
10
|
const API_URL = process.env.RMC_API_URL || "https://review-my-code.com";
|
|
11
11
|
const APP_URL = process.env.RMC_APP_URL || "https://review-my-code.com";
|
|
12
|
-
const VERSION = "0.1.
|
|
12
|
+
const VERSION = "0.1.7";
|
|
13
13
|
const FREE_PLAN_CREDITS_PER_MONTH = 30;
|
|
14
14
|
const REQUEST_TIMEOUT_MS = 290_000;
|
|
15
15
|
const POLL_TIMEOUT_MS = 10 * 60_000;
|
|
@@ -97,6 +97,62 @@ function gitQuietIn(cwd, args) {
|
|
|
97
97
|
stdio: ["ignore", "pipe", "ignore"],
|
|
98
98
|
}).trim();
|
|
99
99
|
}
|
|
100
|
+
function runQuietAsync(cmd, args, options = {}) {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
let settled = false;
|
|
103
|
+
let stdout = "";
|
|
104
|
+
let stderr = "";
|
|
105
|
+
const child = (0, child_process_1.spawn)(cmd, args, {
|
|
106
|
+
cwd: options.cwd,
|
|
107
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
108
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
109
|
+
});
|
|
110
|
+
const timer = options.timeoutMs && options.timeoutMs > 0
|
|
111
|
+
? setTimeout(() => {
|
|
112
|
+
child.kill("SIGTERM");
|
|
113
|
+
finish(new Error(`${cmd} ${args.join(" ")} timed out after ${formatElapsed(options.timeoutMs)}.`));
|
|
114
|
+
}, options.timeoutMs)
|
|
115
|
+
: null;
|
|
116
|
+
function finish(err, output = "") {
|
|
117
|
+
if (settled)
|
|
118
|
+
return;
|
|
119
|
+
settled = true;
|
|
120
|
+
if (timer)
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
if (err)
|
|
123
|
+
reject(err);
|
|
124
|
+
else
|
|
125
|
+
resolve(output.trim());
|
|
126
|
+
}
|
|
127
|
+
child.stdout.setEncoding("utf8");
|
|
128
|
+
child.stderr.setEncoding("utf8");
|
|
129
|
+
child.stdout.on("data", (chunk) => {
|
|
130
|
+
stdout += chunk;
|
|
131
|
+
});
|
|
132
|
+
child.stderr.on("data", (chunk) => {
|
|
133
|
+
stderr += chunk;
|
|
134
|
+
});
|
|
135
|
+
child.on("error", (err) => finish(err));
|
|
136
|
+
child.on("close", (code, signal) => {
|
|
137
|
+
if (code === 0) {
|
|
138
|
+
finish(undefined, stdout);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
finish(new Error(`${cmd} ${args.join(" ")} failed${signal ? ` with signal ${signal}` : ` with exit code ${code}`}. ${stderr.slice(0, 240).trim()}`.trim()));
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function gitQuietInAsync(cwd, args) {
|
|
146
|
+
return runQuietAsync("git", ["-C", cwd, ...args], {
|
|
147
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function gitQuietInAsyncWithEnv(cwd, args, env) {
|
|
151
|
+
return runQuietAsync("git", ["-C", cwd, ...args], {
|
|
152
|
+
env,
|
|
153
|
+
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
100
156
|
function isGitRepo() {
|
|
101
157
|
try {
|
|
102
158
|
git(["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -239,21 +295,42 @@ function parsePrReference(value) {
|
|
|
239
295
|
number: Number.parseInt(match[2], 10),
|
|
240
296
|
};
|
|
241
297
|
}
|
|
298
|
+
let cachedGithubToken;
|
|
242
299
|
function githubToken() {
|
|
300
|
+
if (cachedGithubToken !== undefined)
|
|
301
|
+
return cachedGithubToken || undefined;
|
|
243
302
|
const envToken = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
244
|
-
if (envToken?.trim())
|
|
245
|
-
|
|
303
|
+
if (envToken?.trim()) {
|
|
304
|
+
cachedGithubToken = envToken.trim();
|
|
305
|
+
return cachedGithubToken;
|
|
306
|
+
}
|
|
246
307
|
try {
|
|
247
|
-
|
|
308
|
+
const token = (0, child_process_1.execFileSync)("gh", ["auth", "token"], {
|
|
248
309
|
encoding: "utf-8",
|
|
249
310
|
stdio: ["ignore", "pipe", "ignore"],
|
|
250
311
|
timeout: 5_000,
|
|
251
312
|
}).trim();
|
|
313
|
+
cachedGithubToken = token || null;
|
|
314
|
+
return cachedGithubToken || undefined;
|
|
252
315
|
}
|
|
253
316
|
catch {
|
|
317
|
+
cachedGithubToken = null;
|
|
254
318
|
return undefined;
|
|
255
319
|
}
|
|
256
320
|
}
|
|
321
|
+
function githubGitAuthEnv(cloneUrl) {
|
|
322
|
+
if (!/^https:\/\/github\.com\//i.test(cloneUrl))
|
|
323
|
+
return undefined;
|
|
324
|
+
const token = githubToken();
|
|
325
|
+
if (!token)
|
|
326
|
+
return undefined;
|
|
327
|
+
const basicToken = Buffer.from(`x-access-token:${token}`).toString("base64");
|
|
328
|
+
return {
|
|
329
|
+
GIT_CONFIG_COUNT: "1",
|
|
330
|
+
GIT_CONFIG_KEY_0: "http.https://github.com/.extraheader",
|
|
331
|
+
GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${basicToken}`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
257
334
|
function githubHeaders(accept) {
|
|
258
335
|
const headers = {
|
|
259
336
|
Accept: accept,
|
|
@@ -320,39 +397,63 @@ function prCacheRoot() {
|
|
|
320
397
|
function safeCacheName(ownerRepo) {
|
|
321
398
|
return ownerRepo.toLowerCase().replace(/[^a-z0-9._-]+/g, "__");
|
|
322
399
|
}
|
|
323
|
-
function ensurePrCacheDir(metadata) {
|
|
400
|
+
async function ensurePrCacheDir(metadata) {
|
|
324
401
|
const root = prCacheRoot();
|
|
325
402
|
(0, fs_1.mkdirSync)(root, { recursive: true });
|
|
326
403
|
const cacheDir = (0, path_1.join)(root, safeCacheName(metadata.baseRepo));
|
|
404
|
+
const gitAuthEnv = githubGitAuthEnv(metadata.cloneUrl);
|
|
327
405
|
if (!(0, fs_1.existsSync)((0, path_1.join)(cacheDir, ".git"))) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
406
|
+
if ((0, fs_1.existsSync)(cacheDir))
|
|
407
|
+
(0, fs_1.rmSync)(cacheDir, { recursive: true, force: true });
|
|
408
|
+
try {
|
|
409
|
+
await runQuietAsync("git", ["clone", "--no-checkout", metadata.cloneUrl, cacheDir], { env: gitAuthEnv, timeoutMs: REQUEST_TIMEOUT_MS });
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
throw new Error(`Could not clone ${metadata.baseRepo} into rmcode cache. Make sure GitHub CLI is authenticated with access to the repository. ${err.message}`);
|
|
331
413
|
}
|
|
332
414
|
}
|
|
333
415
|
else {
|
|
334
416
|
try {
|
|
335
|
-
|
|
417
|
+
await gitQuietInAsync(cacheDir, [
|
|
418
|
+
"remote",
|
|
419
|
+
"set-url",
|
|
420
|
+
"origin",
|
|
421
|
+
metadata.cloneUrl,
|
|
422
|
+
]);
|
|
336
423
|
}
|
|
337
424
|
catch {
|
|
338
425
|
// Keep the existing remote; the next fetch will report any real issue.
|
|
339
426
|
}
|
|
340
427
|
}
|
|
341
428
|
try {
|
|
342
|
-
|
|
429
|
+
await gitQuietInAsyncWithEnv(cacheDir, [
|
|
430
|
+
"fetch",
|
|
431
|
+
"--no-tags",
|
|
432
|
+
"origin",
|
|
433
|
+
metadata.baseSha,
|
|
434
|
+
], gitAuthEnv);
|
|
343
435
|
}
|
|
344
436
|
catch {
|
|
345
|
-
|
|
437
|
+
await gitQuietInAsyncWithEnv(cacheDir, [
|
|
438
|
+
"fetch",
|
|
439
|
+
"--no-tags",
|
|
440
|
+
"origin",
|
|
441
|
+
metadata.baseRef,
|
|
442
|
+
], gitAuthEnv);
|
|
346
443
|
}
|
|
347
444
|
try {
|
|
348
|
-
|
|
445
|
+
await gitQuietInAsync(cacheDir, [
|
|
446
|
+
"rev-parse",
|
|
447
|
+
"--verify",
|
|
448
|
+
metadata.baseSha,
|
|
449
|
+
]);
|
|
349
450
|
}
|
|
350
451
|
catch {
|
|
351
452
|
throw new Error(`Could not fetch PR base commit ${metadata.baseSha} into the rmcode cache.`);
|
|
352
453
|
}
|
|
353
454
|
return cacheDir;
|
|
354
455
|
}
|
|
355
|
-
async function diffFromPullRequest(value) {
|
|
456
|
+
async function diffFromPullRequest(value, progress) {
|
|
356
457
|
const ref = typeof value === "string" ? parsePrReference(value) : value;
|
|
357
458
|
const [metadata, diff] = await Promise.all([
|
|
358
459
|
fetchPrMetadata(ref),
|
|
@@ -361,7 +462,8 @@ async function diffFromPullRequest(value) {
|
|
|
361
462
|
if (!diff.trim()) {
|
|
362
463
|
throw new Error(`GitHub returned an empty diff for ${metadata.htmlUrl}.`);
|
|
363
464
|
}
|
|
364
|
-
|
|
465
|
+
progress?.update(`Preparing PR cache for ${metadata.baseRepo}`);
|
|
466
|
+
const cacheDir = await ensurePrCacheDir(metadata);
|
|
365
467
|
return {
|
|
366
468
|
diff,
|
|
367
469
|
files: filesFromDiff(diff),
|
|
@@ -1047,7 +1149,7 @@ async function runPullRequestReview(prValue, jsonMode, failOnFindings, options)
|
|
|
1047
1149
|
}
|
|
1048
1150
|
const spinner = createSpinner(`Fetching GitHub PR ${prValue}`);
|
|
1049
1151
|
try {
|
|
1050
|
-
const source = await diffFromPullRequest(ref);
|
|
1152
|
+
const source = await diffFromPullRequest(ref, spinner);
|
|
1051
1153
|
spinner.stop(`Fetched ${source.metadata.baseRepo}#${source.metadata.number}`);
|
|
1052
1154
|
const contextOptions = {
|
|
1053
1155
|
prTitle: firstNonEmpty(options.title, process.env.RMC_PR_TITLE, source.metadata.title),
|
package/package.json
CHANGED