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 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
- 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 [];
377
439
  }
378
440
  /**
379
- * 添加固定分支
441
+ * 添加固定分支(仓库级别)
380
442
  */
381
443
  function addPinnedBranch(branch) {
382
444
  const config = readConfig();
383
- const pinnedBranches = config.pinnedBranches || [];
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.pinnedBranches = pinnedBranches;
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 pinnedBranches = config.pinnedBranches || [];
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.pinnedBranches = pinnedBranches;
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
- const pinnedBranchNames = getPinnedBranches();
1249
- const allPinnedBranches = branchInfos.filter((b) => pinnedBranchNames.includes(b.name));
1250
- const regularBranches = branchInfos.filter((b) => !pinnedBranchNames.includes(b.name));
1251
- const pinnedBranches = filterPinned ? [] : allPinnedBranches;
1252
- pinnedBranches.sort((a, b) => {
1253
- return pinnedBranchNames.indexOf(a.name) - pinnedBranchNames.indexOf(b.name);
1254
- });
1255
- regularBranches.sort((a, b) => a.name.localeCompare(b.name));
1256
- const MAX_BRANCHES = 100;
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
- choices.push(new inquirer.Separator(" "));
1270
- }
1271
- if (regularBranches.length > 0) {
1272
- choices.push(new inquirer.Separator(cyan("━━━━━━━━ 🌿 All Branches (Alphabetical) ━━━━━━━━")));
1273
- regularBranches.forEach((branch) => {
1274
- choices.push({
1275
- name: ` ${branch.name.padEnd(45)} ${dim(`(${branch.lastCommitTimeFormatted})`)}`,
1276
- value: branch.name,
1277
- short: branch.name,
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
- choices.push(new inquirer.Separator(" "));
1282
- }
1283
- const searchBranches = async (_answers, input = "") => {
1284
- const lowerInput = input.toLowerCase();
1285
- return choices.filter((choice) => {
1286
- if (!choice.value) return true;
1287
- return choice.value.toLowerCase().includes(lowerInput);
1288
- });
1289
- };
1290
- 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
+ };
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
- 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
+ }
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: false
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-3u9dEVEc.mjs");
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, parseRemoteUrl as p, createPullRequest as r, getAllBranches as s, copyToClipboard as t, getBranchesWithInfo as u };
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",
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": {
@@ -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 };