qkpr 1.0.5-beta.1 → 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 +398 -57
- package/package.json +5 -1
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
|
/**
|
|
@@ -339,6 +349,25 @@ function createPullRequest(sourceBranch, targetBranch, remoteUrl) {
|
|
|
339
349
|
const CONFIG_DIR = join(homedir(), ".qkpr");
|
|
340
350
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
341
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
|
+
/**
|
|
342
371
|
* 确保配置目录存在
|
|
343
372
|
*/
|
|
344
373
|
function ensureConfigDir() {
|
|
@@ -393,33 +422,47 @@ function setGeminiModel(model) {
|
|
|
393
422
|
writeConfig(config);
|
|
394
423
|
}
|
|
395
424
|
/**
|
|
396
|
-
* 获取已固定的分支列表
|
|
425
|
+
* 获取已固定的分支列表(仓库级别)
|
|
397
426
|
*/
|
|
398
427
|
function getPinnedBranches() {
|
|
399
|
-
|
|
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 [];
|
|
400
439
|
}
|
|
401
440
|
/**
|
|
402
|
-
* 添加固定分支
|
|
441
|
+
* 添加固定分支(仓库级别)
|
|
403
442
|
*/
|
|
404
443
|
function addPinnedBranch(branch) {
|
|
405
444
|
const config = readConfig();
|
|
406
|
-
const
|
|
445
|
+
const repoId = getRepositoryId();
|
|
446
|
+
if (!config.repositoryPinnedBranches) config.repositoryPinnedBranches = {};
|
|
447
|
+
const pinnedBranches = config.repositoryPinnedBranches[repoId] || [];
|
|
407
448
|
if (!pinnedBranches.includes(branch)) {
|
|
408
449
|
pinnedBranches.push(branch);
|
|
409
|
-
config.
|
|
450
|
+
config.repositoryPinnedBranches[repoId] = pinnedBranches;
|
|
410
451
|
writeConfig(config);
|
|
411
452
|
}
|
|
412
453
|
}
|
|
413
454
|
/**
|
|
414
|
-
* 移除固定分支
|
|
455
|
+
* 移除固定分支(仓库级别)
|
|
415
456
|
*/
|
|
416
457
|
function removePinnedBranch(branch) {
|
|
417
458
|
const config = readConfig();
|
|
418
|
-
const
|
|
459
|
+
const repoId = getRepositoryId();
|
|
460
|
+
if (!config.repositoryPinnedBranches?.[repoId]) return;
|
|
461
|
+
const pinnedBranches = config.repositoryPinnedBranches[repoId];
|
|
419
462
|
const index = pinnedBranches.indexOf(branch);
|
|
420
463
|
if (index > -1) {
|
|
421
464
|
pinnedBranches.splice(index, 1);
|
|
422
|
-
config.
|
|
465
|
+
config.repositoryPinnedBranches[repoId] = pinnedBranches;
|
|
423
466
|
writeConfig(config);
|
|
424
467
|
}
|
|
425
468
|
}
|
|
@@ -1252,10 +1295,262 @@ async function handleConfigPromptsCommand() {
|
|
|
1252
1295
|
}
|
|
1253
1296
|
}
|
|
1254
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
|
+
|
|
1255
1549
|
//#endregion
|
|
1256
1550
|
//#region src/utils/pr-cli.ts
|
|
1257
1551
|
inquirer.registerPrompt("autocomplete", inquirerAutoComplete);
|
|
1258
1552
|
inquirer.registerPrompt("search-checkbox", searchCheckbox);
|
|
1553
|
+
inquirer.registerPrompt("autocomplete-pin", autocomplete_pin_default);
|
|
1259
1554
|
/**
|
|
1260
1555
|
* 通用的分支选择函数,支持单选和多选
|
|
1261
1556
|
*/
|
|
@@ -1268,59 +1563,96 @@ async function promptBranchSelection(branches, options) {
|
|
|
1268
1563
|
return mode === "single" ? "" : [];
|
|
1269
1564
|
}
|
|
1270
1565
|
const branchInfos = getBranchesWithInfo(branches);
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (regularBranches.length > MAX_BRANCHES) regularBranches.splice(MAX_BRANCHES);
|
|
1281
|
-
const choices = [];
|
|
1282
|
-
if (pinnedBranches.length > 0) {
|
|
1283
|
-
choices.push(new inquirer.Separator(magenta("━━━━━━━━ 📌 Pinned Branches ━━━━━━━━")));
|
|
1284
|
-
pinnedBranches.forEach((branch) => {
|
|
1285
|
-
choices.push({
|
|
1286
|
-
name: `📌 ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
|
|
1287
|
-
value: branch.name,
|
|
1288
|
-
short: branch.name,
|
|
1289
|
-
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);
|
|
1290
1575
|
});
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
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
|
+
});
|
|
1302
1586
|
});
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
+
};
|
|
1314
1601
|
const { selectedBranch } = await inquirer.prompt([{
|
|
1315
|
-
type: "autocomplete",
|
|
1602
|
+
type: "autocomplete-pin",
|
|
1316
1603
|
name: "selectedBranch",
|
|
1317
1604
|
message,
|
|
1318
1605
|
source: searchBranches,
|
|
1319
1606
|
pageSize: 20,
|
|
1320
|
-
|
|
1607
|
+
onPin: async (branchName) => {
|
|
1608
|
+
if (getPinnedBranches().includes(branchName)) removePinnedBranch(branchName);
|
|
1609
|
+
else addPinnedBranch(branchName);
|
|
1610
|
+
}
|
|
1321
1611
|
}]);
|
|
1322
1612
|
return selectedBranch;
|
|
1323
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
|
+
}
|
|
1324
1656
|
const { selectedBranches } = await inquirer.prompt([{
|
|
1325
1657
|
type: "search-checkbox",
|
|
1326
1658
|
name: "selectedBranches",
|
|
@@ -1340,6 +1672,10 @@ async function promptTargetBranch(branches, currentBranch) {
|
|
|
1340
1672
|
message: "Select target branch (type to search):",
|
|
1341
1673
|
mode: "single"
|
|
1342
1674
|
});
|
|
1675
|
+
if (targetBranch === "__CANCEL__") {
|
|
1676
|
+
console.log(yellow("\n🚫 PR creation cancelled."));
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1343
1679
|
if (!targetBranch) {
|
|
1344
1680
|
console.log(yellow("⚠️ No branch selected. Using \"main\" as default."));
|
|
1345
1681
|
return "main";
|
|
@@ -1828,6 +2164,7 @@ async function handlePRCommand() {
|
|
|
1828
2164
|
return;
|
|
1829
2165
|
}
|
|
1830
2166
|
const targetBranch = await promptTargetBranch(branches, gitInfo.currentBranch);
|
|
2167
|
+
if (!targetBranch) return;
|
|
1831
2168
|
const prInfo = createPullRequest(gitInfo.currentBranch, targetBranch, gitInfo.remoteUrl);
|
|
1832
2169
|
if (!prInfo) {
|
|
1833
2170
|
console.log(red("❌ Failed to create PR information"));
|
|
@@ -1848,12 +2185,16 @@ async function handlePRCommand() {
|
|
|
1848
2185
|
if (!createMergeBranch(targetBranch, prInfo.mergeBranchName)) return;
|
|
1849
2186
|
if (await promptAutoMergeSource(gitInfo.currentBranch, targetBranch)) {
|
|
1850
2187
|
console.log(yellow(`\n🔄 Merging '${gitInfo.currentBranch}' to detect conflicts...`));
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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;
|
|
1857
2198
|
}
|
|
1858
2199
|
} else {
|
|
1859
2200
|
console.log(green(`\n✅ Merge branch '${prInfo.mergeBranchName}' created without merging.`));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qkpr",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.5
|
|
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": {
|