qkpr 1.0.4 → 1.0.5
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/index.mjs +447 -54
- package/dist/pr-C2AR97YR.mjs +3 -0
- package/package.json +5 -1
- package/dist/pr-3u9dEVEc.mjs +0 -3
package/dist/index.mjs
CHANGED
|
@@ -12,8 +12,18 @@ import inquirer from "inquirer";
|
|
|
12
12
|
import inquirerAutoComplete from "inquirer-autocomplete-prompt";
|
|
13
13
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
14
14
|
import ora from "ora";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
15
16
|
import { homedir } from "node:os";
|
|
16
17
|
import searchCheckbox from "inquirer-search-checkbox";
|
|
18
|
+
import ansiEscapes from "ansi-escapes";
|
|
19
|
+
import figures from "figures";
|
|
20
|
+
import Choices from "inquirer/lib/objects/choices.js";
|
|
21
|
+
import Base from "inquirer/lib/prompts/base.js";
|
|
22
|
+
import observe from "inquirer/lib/utils/events.js";
|
|
23
|
+
import Paginator from "inquirer/lib/utils/paginator.js";
|
|
24
|
+
import * as utils from "inquirer/lib/utils/readline.js";
|
|
25
|
+
import runAsync from "run-async";
|
|
26
|
+
import { takeWhile } from "rxjs";
|
|
17
27
|
|
|
18
28
|
//#region src/services/pr.ts
|
|
19
29
|
/**
|
|
@@ -235,6 +245,29 @@ function createMergeBranch(targetBranch, mergeBranchName) {
|
|
|
235
245
|
}
|
|
236
246
|
}
|
|
237
247
|
/**
|
|
248
|
+
* 合并原始分支到合并分支
|
|
249
|
+
*/
|
|
250
|
+
function mergeSourceToMergeBranch(sourceBranch) {
|
|
251
|
+
try {
|
|
252
|
+
console.log(cyan(`\n🔄 Merging source branch '${sourceBranch}' into current merge branch...`));
|
|
253
|
+
execSync(`git merge ${sourceBranch}`, { stdio: "inherit" });
|
|
254
|
+
console.log(green(`✅ Successfully merged '${sourceBranch}' into merge branch`));
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (error.status === 1 && error.stdout?.includes("CONFLICT")) {
|
|
258
|
+
console.log(yellow(`⚠️ Merge conflicts detected!`));
|
|
259
|
+
console.log(dim(` Please resolve conflicts manually and then run:`));
|
|
260
|
+
console.log(dim(` git add <resolved-files>`));
|
|
261
|
+
console.log(dim(` git commit`));
|
|
262
|
+
return false;
|
|
263
|
+
} else {
|
|
264
|
+
console.log(red("❌ Failed to merge source branch"));
|
|
265
|
+
console.log(dim(`Error: ${error.message || "Unknown error"}`));
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
238
271
|
* 复制文本到剪贴板
|
|
239
272
|
*/
|
|
240
273
|
function copyToClipboard(text) {
|
|
@@ -316,6 +349,25 @@ function createPullRequest(sourceBranch, targetBranch, remoteUrl) {
|
|
|
316
349
|
const CONFIG_DIR = join(homedir(), ".qkpr");
|
|
317
350
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
318
351
|
/**
|
|
352
|
+
* 获取当前仓库的唯一标识
|
|
353
|
+
* 使用 git remote URL 的 hash 值作为仓库标识
|
|
354
|
+
*/
|
|
355
|
+
function getRepositoryId() {
|
|
356
|
+
try {
|
|
357
|
+
const normalizedUrl = execSync("git remote get-url origin", {
|
|
358
|
+
encoding: "utf-8",
|
|
359
|
+
stdio: [
|
|
360
|
+
"pipe",
|
|
361
|
+
"pipe",
|
|
362
|
+
"ignore"
|
|
363
|
+
]
|
|
364
|
+
}).trim().replace(/\.git$/, "").replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/:/g, "/").toLowerCase();
|
|
365
|
+
return createHash("md5").update(normalizedUrl).digest("hex").substring(0, 12);
|
|
366
|
+
} catch {
|
|
367
|
+
return createHash("md5").update(process.cwd()).digest("hex").substring(0, 12);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
319
371
|
* 确保配置目录存在
|
|
320
372
|
*/
|
|
321
373
|
function ensureConfigDir() {
|
|
@@ -370,33 +422,47 @@ function setGeminiModel(model) {
|
|
|
370
422
|
writeConfig(config);
|
|
371
423
|
}
|
|
372
424
|
/**
|
|
373
|
-
* 获取已固定的分支列表
|
|
425
|
+
* 获取已固定的分支列表(仓库级别)
|
|
374
426
|
*/
|
|
375
427
|
function getPinnedBranches() {
|
|
376
|
-
|
|
428
|
+
const config = readConfig();
|
|
429
|
+
const repoId = getRepositoryId();
|
|
430
|
+
if (config.repositoryPinnedBranches?.[repoId]) return config.repositoryPinnedBranches[repoId];
|
|
431
|
+
if (config.pinnedBranches && config.pinnedBranches.length > 0) {
|
|
432
|
+
if (!config.repositoryPinnedBranches) config.repositoryPinnedBranches = {};
|
|
433
|
+
config.repositoryPinnedBranches[repoId] = [...config.pinnedBranches];
|
|
434
|
+
delete config.pinnedBranches;
|
|
435
|
+
writeConfig(config);
|
|
436
|
+
return config.repositoryPinnedBranches[repoId];
|
|
437
|
+
}
|
|
438
|
+
return [];
|
|
377
439
|
}
|
|
378
440
|
/**
|
|
379
|
-
* 添加固定分支
|
|
441
|
+
* 添加固定分支(仓库级别)
|
|
380
442
|
*/
|
|
381
443
|
function addPinnedBranch(branch) {
|
|
382
444
|
const config = readConfig();
|
|
383
|
-
const
|
|
445
|
+
const repoId = getRepositoryId();
|
|
446
|
+
if (!config.repositoryPinnedBranches) config.repositoryPinnedBranches = {};
|
|
447
|
+
const pinnedBranches = config.repositoryPinnedBranches[repoId] || [];
|
|
384
448
|
if (!pinnedBranches.includes(branch)) {
|
|
385
449
|
pinnedBranches.push(branch);
|
|
386
|
-
config.
|
|
450
|
+
config.repositoryPinnedBranches[repoId] = pinnedBranches;
|
|
387
451
|
writeConfig(config);
|
|
388
452
|
}
|
|
389
453
|
}
|
|
390
454
|
/**
|
|
391
|
-
* 移除固定分支
|
|
455
|
+
* 移除固定分支(仓库级别)
|
|
392
456
|
*/
|
|
393
457
|
function removePinnedBranch(branch) {
|
|
394
458
|
const config = readConfig();
|
|
395
|
-
const
|
|
459
|
+
const repoId = getRepositoryId();
|
|
460
|
+
if (!config.repositoryPinnedBranches?.[repoId]) return;
|
|
461
|
+
const pinnedBranches = config.repositoryPinnedBranches[repoId];
|
|
396
462
|
const index = pinnedBranches.indexOf(branch);
|
|
397
463
|
if (index > -1) {
|
|
398
464
|
pinnedBranches.splice(index, 1);
|
|
399
|
-
config.
|
|
465
|
+
config.repositoryPinnedBranches[repoId] = pinnedBranches;
|
|
400
466
|
writeConfig(config);
|
|
401
467
|
}
|
|
402
468
|
}
|
|
@@ -1229,10 +1295,262 @@ async function handleConfigPromptsCommand() {
|
|
|
1229
1295
|
}
|
|
1230
1296
|
}
|
|
1231
1297
|
|
|
1298
|
+
//#endregion
|
|
1299
|
+
//#region src/utils/prompts/autocomplete-pin.ts
|
|
1300
|
+
function isSelectable(choice) {
|
|
1301
|
+
return choice.type !== "separator" && !choice.disabled;
|
|
1302
|
+
}
|
|
1303
|
+
var AutocompletePinPrompt = class extends Base {
|
|
1304
|
+
currentChoices;
|
|
1305
|
+
firstRender;
|
|
1306
|
+
selected;
|
|
1307
|
+
initialValue;
|
|
1308
|
+
paginator;
|
|
1309
|
+
searchedOnce;
|
|
1310
|
+
searching;
|
|
1311
|
+
lastSearchTerm;
|
|
1312
|
+
lastPromise;
|
|
1313
|
+
nbChoices;
|
|
1314
|
+
done;
|
|
1315
|
+
answer;
|
|
1316
|
+
answerName;
|
|
1317
|
+
shortAnswer;
|
|
1318
|
+
constructor(questions, rl, answers) {
|
|
1319
|
+
super(questions, rl, answers);
|
|
1320
|
+
const opt = this.opt;
|
|
1321
|
+
if (!opt.source) this.throwParamError("source");
|
|
1322
|
+
this.currentChoices = new Choices([], answers);
|
|
1323
|
+
this.firstRender = true;
|
|
1324
|
+
this.selected = 0;
|
|
1325
|
+
this.initialValue = opt.default;
|
|
1326
|
+
if (!opt.suggestOnly) opt.default = null;
|
|
1327
|
+
const shouldLoop = opt.loop === void 0 ? true : opt.loop;
|
|
1328
|
+
this.paginator = new Paginator(this.screen, { isInfinite: shouldLoop });
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Start the Inquiry session
|
|
1332
|
+
* @param {Function} cb Callback when prompt is done
|
|
1333
|
+
* @return {this}
|
|
1334
|
+
*/
|
|
1335
|
+
_run(cb) {
|
|
1336
|
+
this.done = cb;
|
|
1337
|
+
if (Array.isArray(this.rl.history)) this.rl.history = [];
|
|
1338
|
+
const events = observe(this.rl);
|
|
1339
|
+
const dontHaveAnswer = () => this.answer === void 0;
|
|
1340
|
+
events.line.pipe(takeWhile(dontHaveAnswer)).forEach(this.onSubmit.bind(this));
|
|
1341
|
+
events.keypress.pipe(takeWhile(dontHaveAnswer)).forEach(this.onKeypress.bind(this));
|
|
1342
|
+
this.search(void 0);
|
|
1343
|
+
return this;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Render the prompt to screen
|
|
1347
|
+
* @return {undefined}
|
|
1348
|
+
*/
|
|
1349
|
+
render(error) {
|
|
1350
|
+
let content = this.getQuestion();
|
|
1351
|
+
let bottomContent = "";
|
|
1352
|
+
const opt = this.opt;
|
|
1353
|
+
const suggestText = opt.suggestOnly ? ", tab to autocomplete" : "";
|
|
1354
|
+
content += dim(`(Use arrow keys or type to search${suggestText}, Ctrl+P to Pin)`);
|
|
1355
|
+
if (this.status === "answered") content += cyan(this.shortAnswer || this.answerName || this.answer);
|
|
1356
|
+
else if (this.searching) {
|
|
1357
|
+
content += this.rl.line;
|
|
1358
|
+
bottomContent += ` ${dim(opt.searchText || "Searching...")}`;
|
|
1359
|
+
} else if (this.nbChoices) {
|
|
1360
|
+
const choicesStr = listRender(this.currentChoices, this.selected);
|
|
1361
|
+
content += this.rl.line;
|
|
1362
|
+
const indexPosition = this.selected;
|
|
1363
|
+
let realIndexPosition = 0;
|
|
1364
|
+
this.currentChoices.choices.every((choice, index) => {
|
|
1365
|
+
if (index > indexPosition) return false;
|
|
1366
|
+
const name = choice.name;
|
|
1367
|
+
realIndexPosition += name ? name.split("\n").length : 0;
|
|
1368
|
+
return true;
|
|
1369
|
+
});
|
|
1370
|
+
bottomContent += this.paginator.paginate(choicesStr, realIndexPosition, opt.pageSize);
|
|
1371
|
+
} else {
|
|
1372
|
+
content += this.rl.line;
|
|
1373
|
+
bottomContent += ` ${yellow(opt.emptyText || "No results...")}`;
|
|
1374
|
+
}
|
|
1375
|
+
if (error) bottomContent += `\n${red(">> ")}${error}`;
|
|
1376
|
+
this.firstRender = false;
|
|
1377
|
+
this.screen.render(content, bottomContent);
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* When user press `enter` key
|
|
1381
|
+
*/
|
|
1382
|
+
onSubmit(line) {
|
|
1383
|
+
let lineOrRl = line || this.rl.line;
|
|
1384
|
+
const opt = this.opt;
|
|
1385
|
+
if (opt.suggestOnly && !lineOrRl) lineOrRl = opt.default === null ? "" : opt.default;
|
|
1386
|
+
if (typeof opt.validate === "function") {
|
|
1387
|
+
const checkValidationResult = (validationResult$1) => {
|
|
1388
|
+
if (validationResult$1 !== true) this.render(validationResult$1 || "Enter something, tab to autocomplete!");
|
|
1389
|
+
else this.onSubmitAfterValidation(lineOrRl);
|
|
1390
|
+
};
|
|
1391
|
+
let validationResult;
|
|
1392
|
+
if (opt.suggestOnly) validationResult = opt.validate(lineOrRl, this.answers);
|
|
1393
|
+
else {
|
|
1394
|
+
const choice = this.currentChoices.getChoice(this.selected);
|
|
1395
|
+
validationResult = opt.validate(choice, this.answers);
|
|
1396
|
+
}
|
|
1397
|
+
if (isPromise(validationResult)) validationResult.then(checkValidationResult);
|
|
1398
|
+
else checkValidationResult(validationResult);
|
|
1399
|
+
} else this.onSubmitAfterValidation(lineOrRl);
|
|
1400
|
+
}
|
|
1401
|
+
onSubmitAfterValidation(line) {
|
|
1402
|
+
const opt = this.opt;
|
|
1403
|
+
let choice = {};
|
|
1404
|
+
if (this.nbChoices && this.nbChoices <= this.selected && !opt.suggestOnly) {
|
|
1405
|
+
this.rl.write(line);
|
|
1406
|
+
this.search(line);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (opt.suggestOnly) {
|
|
1410
|
+
choice.value = line || this.rl.line;
|
|
1411
|
+
this.answer = line || this.rl.line;
|
|
1412
|
+
this.answerName = line || this.rl.line;
|
|
1413
|
+
this.shortAnswer = line || this.rl.line;
|
|
1414
|
+
this.rl.line = "";
|
|
1415
|
+
} else if (this.nbChoices) {
|
|
1416
|
+
choice = this.currentChoices.getChoice(this.selected);
|
|
1417
|
+
this.answer = choice.value;
|
|
1418
|
+
this.answerName = choice.name;
|
|
1419
|
+
this.shortAnswer = choice.short;
|
|
1420
|
+
} else {
|
|
1421
|
+
this.rl.write(line);
|
|
1422
|
+
this.search(line);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
runAsync(opt.filter, (_err, value) => {
|
|
1426
|
+
choice.value = value;
|
|
1427
|
+
this.answer = value;
|
|
1428
|
+
if (opt.suggestOnly) this.shortAnswer = value;
|
|
1429
|
+
this.status = "answered";
|
|
1430
|
+
this.render();
|
|
1431
|
+
this.screen.done();
|
|
1432
|
+
this.done(choice.value);
|
|
1433
|
+
})(choice.value);
|
|
1434
|
+
}
|
|
1435
|
+
search(searchTerm) {
|
|
1436
|
+
const opt = this.opt;
|
|
1437
|
+
let currentValue;
|
|
1438
|
+
if (this.currentChoices && this.nbChoices && this.nbChoices > this.selected) {
|
|
1439
|
+
const currentChoice = this.currentChoices.getChoice(this.selected);
|
|
1440
|
+
if (currentChoice) currentValue = currentChoice.value;
|
|
1441
|
+
}
|
|
1442
|
+
this.selected = 0;
|
|
1443
|
+
if (this.searchedOnce) {
|
|
1444
|
+
this.searching = true;
|
|
1445
|
+
this.currentChoices = new Choices([], this.answers);
|
|
1446
|
+
this.render();
|
|
1447
|
+
} else this.searchedOnce = true;
|
|
1448
|
+
this.lastSearchTerm = searchTerm;
|
|
1449
|
+
let thisPromise;
|
|
1450
|
+
try {
|
|
1451
|
+
const result = opt.source(this.answers, searchTerm);
|
|
1452
|
+
thisPromise = Promise.resolve(result);
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
thisPromise = Promise.reject(error);
|
|
1455
|
+
}
|
|
1456
|
+
this.lastPromise = thisPromise;
|
|
1457
|
+
return thisPromise.then((choices) => {
|
|
1458
|
+
if (thisPromise !== this.lastPromise) return;
|
|
1459
|
+
this.currentChoices = new Choices(choices, this.answers);
|
|
1460
|
+
const realChoices = choices.filter((choice) => isSelectable(choice));
|
|
1461
|
+
this.nbChoices = realChoices.length;
|
|
1462
|
+
let selectedIndex = -1;
|
|
1463
|
+
if (currentValue !== void 0) selectedIndex = realChoices.findIndex((choice) => choice === currentValue || choice.value === currentValue);
|
|
1464
|
+
if (selectedIndex === -1) selectedIndex = realChoices.findIndex((choice) => choice === this.initialValue || choice.value === this.initialValue);
|
|
1465
|
+
if (selectedIndex >= 0) this.selected = selectedIndex;
|
|
1466
|
+
this.searching = false;
|
|
1467
|
+
this.render();
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
ensureSelectedInRange() {
|
|
1471
|
+
const selectedIndex = Math.min(this.selected, this.nbChoices);
|
|
1472
|
+
this.selected = Math.max(selectedIndex, 0);
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* When user type
|
|
1476
|
+
*/
|
|
1477
|
+
onKeypress(e) {
|
|
1478
|
+
const opt = this.opt;
|
|
1479
|
+
let len;
|
|
1480
|
+
const keyName = e.key && e.key.name || void 0;
|
|
1481
|
+
if (keyName === "p" && e.key.ctrl) {
|
|
1482
|
+
if (this.nbChoices && opt.onPin) {
|
|
1483
|
+
const choice = this.currentChoices.getChoice(this.selected);
|
|
1484
|
+
if (choice) Promise.resolve(opt.onPin(choice.value, choice)).then(() => {
|
|
1485
|
+
this.search(this.rl.line);
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (keyName === "tab" && opt.suggestOnly) {
|
|
1491
|
+
if (this.currentChoices.getChoice(this.selected)) {
|
|
1492
|
+
this.rl.write(ansiEscapes.cursorLeft);
|
|
1493
|
+
const autoCompleted = this.currentChoices.getChoice(this.selected).value;
|
|
1494
|
+
this.rl.write(ansiEscapes.cursorForward(autoCompleted.length));
|
|
1495
|
+
this.rl.line = autoCompleted;
|
|
1496
|
+
this.render();
|
|
1497
|
+
}
|
|
1498
|
+
} else if (keyName === "down" || keyName === "n" && e.key.ctrl) {
|
|
1499
|
+
len = this.nbChoices;
|
|
1500
|
+
this.selected = this.selected < len - 1 ? this.selected + 1 : 0;
|
|
1501
|
+
this.ensureSelectedInRange();
|
|
1502
|
+
this.render();
|
|
1503
|
+
utils.up(this.rl, 2);
|
|
1504
|
+
} else if (keyName === "up") {
|
|
1505
|
+
len = this.nbChoices;
|
|
1506
|
+
this.selected = this.selected > 0 ? this.selected - 1 : len - 1;
|
|
1507
|
+
this.ensureSelectedInRange();
|
|
1508
|
+
this.render();
|
|
1509
|
+
} else {
|
|
1510
|
+
this.render();
|
|
1511
|
+
if (this.lastSearchTerm !== this.rl.line) this.search(this.rl.line);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
/**
|
|
1516
|
+
* Function for rendering list choices
|
|
1517
|
+
* @param {any} choices The choices to render
|
|
1518
|
+
* @param {number} pointer Position of the pointer
|
|
1519
|
+
* @return {string} Rendered content
|
|
1520
|
+
*/
|
|
1521
|
+
function listRender(choices, pointer) {
|
|
1522
|
+
let output = "";
|
|
1523
|
+
let separatorOffset = 0;
|
|
1524
|
+
choices.forEach((choice, i) => {
|
|
1525
|
+
if (choice.type === "separator") {
|
|
1526
|
+
separatorOffset++;
|
|
1527
|
+
output += ` ${choice}\n`;
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
if (choice.disabled) {
|
|
1531
|
+
separatorOffset++;
|
|
1532
|
+
output += ` - ${choice.name}`;
|
|
1533
|
+
output += ` (${typeof choice.disabled === "string" ? choice.disabled : "Disabled"})`;
|
|
1534
|
+
output += "\n";
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
const isSelected = i - separatorOffset === pointer;
|
|
1538
|
+
let line = (isSelected ? `${figures.pointer} ` : " ") + choice.name;
|
|
1539
|
+
if (isSelected) line = cyan(line);
|
|
1540
|
+
output += `${line} \n`;
|
|
1541
|
+
});
|
|
1542
|
+
return output.replace(/\n$/, "");
|
|
1543
|
+
}
|
|
1544
|
+
function isPromise(value) {
|
|
1545
|
+
return typeof value === "object" && typeof value.then === "function";
|
|
1546
|
+
}
|
|
1547
|
+
var autocomplete_pin_default = AutocompletePinPrompt;
|
|
1548
|
+
|
|
1232
1549
|
//#endregion
|
|
1233
1550
|
//#region src/utils/pr-cli.ts
|
|
1234
1551
|
inquirer.registerPrompt("autocomplete", inquirerAutoComplete);
|
|
1235
1552
|
inquirer.registerPrompt("search-checkbox", searchCheckbox);
|
|
1553
|
+
inquirer.registerPrompt("autocomplete-pin", autocomplete_pin_default);
|
|
1236
1554
|
/**
|
|
1237
1555
|
* 通用的分支选择函数,支持单选和多选
|
|
1238
1556
|
*/
|
|
@@ -1245,59 +1563,96 @@ async function promptBranchSelection(branches, options) {
|
|
|
1245
1563
|
return mode === "single" ? "" : [];
|
|
1246
1564
|
}
|
|
1247
1565
|
const branchInfos = getBranchesWithInfo(branches);
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (regularBranches.length > MAX_BRANCHES) regularBranches.splice(MAX_BRANCHES);
|
|
1258
|
-
const choices = [];
|
|
1259
|
-
if (pinnedBranches.length > 0) {
|
|
1260
|
-
choices.push(new inquirer.Separator(magenta("━━━━━━━━ 📌 Pinned Branches ━━━━━━━━")));
|
|
1261
|
-
pinnedBranches.forEach((branch) => {
|
|
1262
|
-
choices.push({
|
|
1263
|
-
name: `📌 ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
|
|
1264
|
-
value: branch.name,
|
|
1265
|
-
short: branch.name,
|
|
1266
|
-
checked: defaultSelected.includes(branch.name)
|
|
1566
|
+
if (mode === "single") {
|
|
1567
|
+
const sortingPinnedBranches = getPinnedBranches();
|
|
1568
|
+
const searchBranches = async (_answers, input = "") => {
|
|
1569
|
+
const currentPinnedBranches = getPinnedBranches();
|
|
1570
|
+
const topGroupBranches = branchInfos.filter((b) => sortingPinnedBranches.includes(b.name));
|
|
1571
|
+
const bottomGroupBranches = branchInfos.filter((b) => !sortingPinnedBranches.includes(b.name));
|
|
1572
|
+
const effectiveTopGroup = filterPinned ? [] : topGroupBranches;
|
|
1573
|
+
effectiveTopGroup.sort((a, b) => {
|
|
1574
|
+
return sortingPinnedBranches.indexOf(a.name) - sortingPinnedBranches.indexOf(b.name);
|
|
1267
1575
|
});
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
checked: defaultSelected.includes(branch.name)
|
|
1576
|
+
bottomGroupBranches.sort((a, b) => a.name.localeCompare(b.name));
|
|
1577
|
+
const displayBottomGroup = bottomGroupBranches.slice(0, 100);
|
|
1578
|
+
const choices = [];
|
|
1579
|
+
[...effectiveTopGroup, ...displayBottomGroup].forEach((branch) => {
|
|
1580
|
+
const isPinnedNow = currentPinnedBranches.includes(branch.name);
|
|
1581
|
+
choices.push({
|
|
1582
|
+
name: `${isPinnedNow ? "📌" : " "} ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
|
|
1583
|
+
value: branch.name,
|
|
1584
|
+
short: branch.name
|
|
1585
|
+
});
|
|
1279
1586
|
});
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1587
|
+
if (!filterPinned) {
|
|
1588
|
+
choices.push(new inquirer.Separator(" "));
|
|
1589
|
+
choices.push({
|
|
1590
|
+
name: dim(" [Cancel PR creation]"),
|
|
1591
|
+
value: "__CANCEL__",
|
|
1592
|
+
short: "Cancel"
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
const lowerInput = input.toLowerCase();
|
|
1596
|
+
return choices.filter((choice) => {
|
|
1597
|
+
if (!choice.value || choice.value === "__CANCEL__") return true;
|
|
1598
|
+
return choice.value.toLowerCase().includes(lowerInput);
|
|
1599
|
+
});
|
|
1600
|
+
};
|
|
1291
1601
|
const { selectedBranch } = await inquirer.prompt([{
|
|
1292
|
-
type: "autocomplete",
|
|
1602
|
+
type: "autocomplete-pin",
|
|
1293
1603
|
name: "selectedBranch",
|
|
1294
1604
|
message,
|
|
1295
1605
|
source: searchBranches,
|
|
1296
1606
|
pageSize: 20,
|
|
1297
|
-
|
|
1607
|
+
onPin: async (branchName) => {
|
|
1608
|
+
if (getPinnedBranches().includes(branchName)) removePinnedBranch(branchName);
|
|
1609
|
+
else addPinnedBranch(branchName);
|
|
1610
|
+
}
|
|
1298
1611
|
}]);
|
|
1299
1612
|
return selectedBranch;
|
|
1300
1613
|
} else {
|
|
1614
|
+
const pinnedBranchNames = getPinnedBranches();
|
|
1615
|
+
const allPinnedBranches = branchInfos.filter((b) => pinnedBranchNames.includes(b.name));
|
|
1616
|
+
const regularBranches = branchInfos.filter((b) => !pinnedBranchNames.includes(b.name));
|
|
1617
|
+
const pinnedBranches = filterPinned ? [] : allPinnedBranches;
|
|
1618
|
+
const choices = [];
|
|
1619
|
+
if (!filterPinned) {
|
|
1620
|
+
choices.push(new inquirer.Separator(" "));
|
|
1621
|
+
choices.push({
|
|
1622
|
+
name: dim(" [Cancel PR creation]"),
|
|
1623
|
+
value: "__CANCEL__",
|
|
1624
|
+
short: "Cancel"
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
pinnedBranches.sort((a, b) => {
|
|
1628
|
+
return pinnedBranchNames.indexOf(a.name) - pinnedBranchNames.indexOf(b.name);
|
|
1629
|
+
});
|
|
1630
|
+
regularBranches.sort((a, b) => a.name.localeCompare(b.name));
|
|
1631
|
+
if (regularBranches.length > 100) regularBranches.splice(100);
|
|
1632
|
+
if (pinnedBranches.length > 0) {
|
|
1633
|
+
choices.push(new inquirer.Separator(magenta("━━━━━━━━ 📌 Pinned Branches ━━━━━━━━")));
|
|
1634
|
+
pinnedBranches.forEach((branch) => {
|
|
1635
|
+
choices.push({
|
|
1636
|
+
name: `📌 ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
|
|
1637
|
+
value: branch.name,
|
|
1638
|
+
short: branch.name,
|
|
1639
|
+
checked: defaultSelected.includes(branch.name)
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
choices.push(new inquirer.Separator(" "));
|
|
1643
|
+
}
|
|
1644
|
+
if (regularBranches.length > 0) {
|
|
1645
|
+
choices.push(new inquirer.Separator(cyan("━━━━━━━━ 🌿 All Branches (Alphabetical) ━━━━━━━━")));
|
|
1646
|
+
regularBranches.forEach((branch) => {
|
|
1647
|
+
choices.push({
|
|
1648
|
+
name: ` ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
|
|
1649
|
+
value: branch.name,
|
|
1650
|
+
short: branch.name,
|
|
1651
|
+
checked: defaultSelected.includes(branch.name)
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
choices.push(new inquirer.Separator(" "));
|
|
1655
|
+
}
|
|
1301
1656
|
const { selectedBranches } = await inquirer.prompt([{
|
|
1302
1657
|
type: "search-checkbox",
|
|
1303
1658
|
name: "selectedBranches",
|
|
@@ -1317,6 +1672,10 @@ async function promptTargetBranch(branches, currentBranch) {
|
|
|
1317
1672
|
message: "Select target branch (type to search):",
|
|
1318
1673
|
mode: "single"
|
|
1319
1674
|
});
|
|
1675
|
+
if (targetBranch === "__CANCEL__") {
|
|
1676
|
+
console.log(yellow("\n🚫 PR creation cancelled."));
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1320
1679
|
if (!targetBranch) {
|
|
1321
1680
|
console.log(yellow("⚠️ No branch selected. Using \"main\" as default."));
|
|
1322
1681
|
return "main";
|
|
@@ -1333,11 +1692,25 @@ async function promptCreateMergeBranch(mergeBranchName) {
|
|
|
1333
1692
|
type: "confirm",
|
|
1334
1693
|
name: "createMergeBranch",
|
|
1335
1694
|
message: "Do you want to create a merge branch for conflict resolution?",
|
|
1336
|
-
default:
|
|
1695
|
+
default: true
|
|
1337
1696
|
}]);
|
|
1338
1697
|
return createMergeBranch$1;
|
|
1339
1698
|
}
|
|
1340
1699
|
/**
|
|
1700
|
+
* 确认是否自动合并原始分支到合并分支
|
|
1701
|
+
*/
|
|
1702
|
+
async function promptAutoMergeSource(sourceBranch, targetBranch) {
|
|
1703
|
+
console.log(yellow(`\n🔄 Merge branch created successfully!`));
|
|
1704
|
+
console.log(dim(` This branch is based on '${targetBranch}' and can be used to test the merge.`));
|
|
1705
|
+
const { shouldAutoMerge } = await inquirer.prompt([{
|
|
1706
|
+
type: "confirm",
|
|
1707
|
+
name: "shouldAutoMerge",
|
|
1708
|
+
message: `Auto-merge '${sourceBranch}' to detect potential conflicts now?`,
|
|
1709
|
+
default: false
|
|
1710
|
+
}]);
|
|
1711
|
+
return shouldAutoMerge;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1341
1714
|
* 显示 PR 信息
|
|
1342
1715
|
*/
|
|
1343
1716
|
function displayPRInfo(prMessage, prUrl) {
|
|
@@ -1363,7 +1736,7 @@ async function handlePinCommand(branchName) {
|
|
|
1363
1736
|
addPinnedBranch(branchName);
|
|
1364
1737
|
console.log(green(`✅ Branch '${branchName}' has been pinned`));
|
|
1365
1738
|
} else {
|
|
1366
|
-
const { getAllBranches: getAllBranches$1 } = await import("./pr-
|
|
1739
|
+
const { getAllBranches: getAllBranches$1 } = await import("./pr-C2AR97YR.mjs");
|
|
1367
1740
|
const branches = getAllBranches$1();
|
|
1368
1741
|
if (branches.length === 0) {
|
|
1369
1742
|
console.log(yellow("⚠️ No branches found"));
|
|
@@ -1791,6 +2164,7 @@ async function handlePRCommand() {
|
|
|
1791
2164
|
return;
|
|
1792
2165
|
}
|
|
1793
2166
|
const targetBranch = await promptTargetBranch(branches, gitInfo.currentBranch);
|
|
2167
|
+
if (!targetBranch) return;
|
|
1794
2168
|
const prInfo = createPullRequest(gitInfo.currentBranch, targetBranch, gitInfo.remoteUrl);
|
|
1795
2169
|
if (!prInfo) {
|
|
1796
2170
|
console.log(red("❌ Failed to create PR information"));
|
|
@@ -1809,6 +2183,25 @@ async function handlePRCommand() {
|
|
|
1809
2183
|
}
|
|
1810
2184
|
if (await promptCreateMergeBranch(prInfo.mergeBranchName)) {
|
|
1811
2185
|
if (!createMergeBranch(targetBranch, prInfo.mergeBranchName)) return;
|
|
2186
|
+
if (await promptAutoMergeSource(gitInfo.currentBranch, targetBranch)) {
|
|
2187
|
+
console.log(yellow(`\n🔄 Merging '${gitInfo.currentBranch}' to detect conflicts...`));
|
|
2188
|
+
try {
|
|
2189
|
+
if (!mergeSourceToMergeBranch(gitInfo.currentBranch)) {
|
|
2190
|
+
console.log(yellow("\n⚠️ Merge conflicts detected! Please resolve them manually:"));
|
|
2191
|
+
console.log(dim(` 1. Resolve conflicts in your editor`));
|
|
2192
|
+
console.log(dim(` 2. Run: git add <resolved-files>`));
|
|
2193
|
+
console.log(dim(` 3. Run: git commit`));
|
|
2194
|
+
console.log(dim(` 4. Push the merge branch when ready`));
|
|
2195
|
+
}
|
|
2196
|
+
} catch {
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
} else {
|
|
2200
|
+
console.log(green(`\n✅ Merge branch '${prInfo.mergeBranchName}' created without merging.`));
|
|
2201
|
+
console.log(dim(` You can manually merge later when ready:`));
|
|
2202
|
+
console.log(dim(` git checkout ${prInfo.mergeBranchName}`));
|
|
2203
|
+
console.log(dim(` git merge ${gitInfo.currentBranch}`));
|
|
2204
|
+
}
|
|
1812
2205
|
}
|
|
1813
2206
|
console.log(green("\n🎉 PR creation process completed!\n"));
|
|
1814
2207
|
}
|
|
@@ -1857,4 +2250,4 @@ yargs(hideBin(process.argv)).scriptName("qkpr").usage("Usage: $0 <command> [opti
|
|
|
1857
2250
|
}).version(version).alias("v", "version").help("h").alias("h", "help").epilog("For more information, visit https://github.com/KazooTTT/qkpr").argv;
|
|
1858
2251
|
|
|
1859
2252
|
//#endregion
|
|
1860
|
-
export { generatePRMessage as a, getBranchCategory as c, getCommitsBetweenBranches as d, getGitInfo as f, generateMergeBranchName as i, getBranchLastCommitTime as l, createMergeBranch as n, generatePRUrl as o,
|
|
2253
|
+
export { generatePRMessage as a, getBranchCategory as c, getCommitsBetweenBranches as d, getGitInfo as f, generateMergeBranchName as i, getBranchLastCommitTime as l, parseRemoteUrl as m, createMergeBranch as n, generatePRUrl as o, mergeSourceToMergeBranch as p, createPullRequest as r, getAllBranches as s, copyToClipboard as t, getBranchesWithInfo as u };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as generatePRMessage, c as getBranchCategory, d as getCommitsBetweenBranches, f as getGitInfo, i as generateMergeBranchName, l as getBranchLastCommitTime, m as parseRemoteUrl, n as createMergeBranch, o as generatePRUrl, p as mergeSourceToMergeBranch, r as createPullRequest, s as getAllBranches, t as copyToClipboard, u as getBranchesWithInfo } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
export { copyToClipboard, createMergeBranch, createPullRequest, generateMergeBranchName, generatePRMessage, generatePRUrl, getAllBranches, getBranchCategory, getBranchLastCommitTime, getBranchesWithInfo, getCommitsBetweenBranches, getGitInfo, mergeSourceToMergeBranch, parseRemoteUrl };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qkpr",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"description": "Create a Pull Request with interactive branch selection",
|
|
6
6
|
"author": "KazooTTT <work@kazoottt.top>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@google/generative-ai": "^0.24.1",
|
|
37
|
+
"ansi-escapes": "^7.2.0",
|
|
38
|
+
"figures": "^6.1.0",
|
|
37
39
|
"inquirer": "^9.2.20",
|
|
38
40
|
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
39
41
|
"inquirer-search-checkbox": "^1.0.0",
|
|
@@ -41,6 +43,8 @@
|
|
|
41
43
|
"kolorist": "^1.8.0",
|
|
42
44
|
"open": "^8.4.2",
|
|
43
45
|
"ora": "^9.0.0",
|
|
46
|
+
"run-async": "^4.0.6",
|
|
47
|
+
"rxjs": "^7.8.2",
|
|
44
48
|
"yargs": "^17.7.2"
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
package/dist/pr-3u9dEVEc.mjs
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { a as generatePRMessage, c as getBranchCategory, d as getCommitsBetweenBranches, f as getGitInfo, i as generateMergeBranchName, l as getBranchLastCommitTime, n as createMergeBranch, o as generatePRUrl, p as parseRemoteUrl, r as createPullRequest, s as getAllBranches, t as copyToClipboard, u as getBranchesWithInfo } from "./index.mjs";
|
|
2
|
-
|
|
3
|
-
export { copyToClipboard, createMergeBranch, createPullRequest, generateMergeBranchName, generatePRMessage, generatePRUrl, getAllBranches, getBranchCategory, getBranchLastCommitTime, getBranchesWithInfo, getCommitsBetweenBranches, getGitInfo, parseRemoteUrl };
|