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.
Files changed (2) hide show
  1. package/dist/index.mjs +398 -57
  2. 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
- return readConfig().pinnedBranches || [];
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 pinnedBranches = config.pinnedBranches || [];
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.pinnedBranches = pinnedBranches;
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 pinnedBranches = config.pinnedBranches || [];
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.pinnedBranches = pinnedBranches;
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
- const pinnedBranchNames = getPinnedBranches();
1272
- const allPinnedBranches = branchInfos.filter((b) => pinnedBranchNames.includes(b.name));
1273
- const regularBranches = branchInfos.filter((b) => !pinnedBranchNames.includes(b.name));
1274
- const pinnedBranches = filterPinned ? [] : allPinnedBranches;
1275
- pinnedBranches.sort((a, b) => {
1276
- return pinnedBranchNames.indexOf(a.name) - pinnedBranchNames.indexOf(b.name);
1277
- });
1278
- regularBranches.sort((a, b) => a.name.localeCompare(b.name));
1279
- const MAX_BRANCHES = 100;
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
- choices.push(new inquirer.Separator(" "));
1293
- }
1294
- if (regularBranches.length > 0) {
1295
- choices.push(new inquirer.Separator(cyan("━━━━━━━━ 🌿 All Branches (Alphabetical) ━━━━━━━━")));
1296
- regularBranches.forEach((branch) => {
1297
- choices.push({
1298
- name: ` ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
1299
- value: branch.name,
1300
- short: branch.name,
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
- choices.push(new inquirer.Separator(" "));
1305
- }
1306
- const searchBranches = async (_answers, input = "") => {
1307
- const lowerInput = input.toLowerCase();
1308
- return choices.filter((choice) => {
1309
- if (!choice.value) return true;
1310
- return choice.value.toLowerCase().includes(lowerInput);
1311
- });
1312
- };
1313
- if (mode === "single") {
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
- default: pinnedBranches.length > 0 ? pinnedBranches[0].name : regularBranches[0]?.name
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
- if (!mergeSourceToMergeBranch(gitInfo.currentBranch)) {
1852
- console.log(yellow("\n⚠️ Merge conflicts detected! Please resolve them manually:"));
1853
- console.log(dim(` 1. Resolve conflicts in your editor`));
1854
- console.log(dim(` 2. Run: git add <resolved-files>`));
1855
- console.log(dim(` 3. Run: git commit`));
1856
- console.log(dim(` 4. Push the merge branch when ready`));
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-beta.1",
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": {