@nemo-cli/git 0.1.3 → 0.1.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.js CHANGED
@@ -1,5 +1,5 @@
1
- import { addFiles, colors, createCheckbox, createCommand, createConfirm, createHelpExample, createInput, createNote, createOptions, createSearch, createSelect, createSpinner, exit, getCurrentBranch, getGitStatus, intro, isEmpty, isString, loadConfig, log, outro, readPackage, x, xASync } from "@nemo-cli/shared";
2
- import { BigText, ErrorMessage, Message, renderHistViewer, renderStashList, renderStatusViewer } from "@nemo-cli/ui";
1
+ import { addFiles, colors, createCheckbox, createCommand, createConfirm, createHelpExample, createInput, createNote, createOptions, createSelect, createSpinner, exit, getCurrentBranch, getGitStatus, intro, isEmpty, isString, loadConfig, log, outro, readPackage, safeAwait, x, xASync } from "@nemo-cli/shared";
2
+ import { BigText, ErrorMessage, Message, renderBranchViewer, renderCommitDetail, renderCommitViewer, renderDiffViewer, renderHistViewer, renderStashList, renderStatusViewer } from "@nemo-cli/ui";
3
3
  import path, { dirname, join } from "node:path";
4
4
  import readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
@@ -220,7 +220,8 @@ const HELP_MESSAGE$1 = {
220
220
  main: createHelpExample("ng --version", "ng --help", "ng <command> [option]"),
221
221
  branch: createHelpExample("ng branch --version", "ng branch --help", "ng branch <command> [option]"),
222
222
  branchDelete: createHelpExample("ng branch delete --version", "ng branch delete --help", "ng branch delete <command> [option]"),
223
- branchClean: createHelpExample("ng branch clean --version", "ng branch clean --help")
223
+ branchClean: createHelpExample("ng branch clean --version", "ng branch clean --help"),
224
+ branchList: createHelpExample("ng branch list --version", "ng branch list --help", "ng branch list [option]")
224
225
  };
225
226
 
226
227
  //#endregion
@@ -367,7 +368,7 @@ const getRemoteBranches = async () => {
367
368
  ])).stdout.split("\n").filter((line) => line.trim() && !line.includes("->")).map((line) => line.trim().replace(remotePrefix, "")) };
368
369
  };
369
370
  const currentBranchPrefix = /^\* /;
370
- const formatBranch$1 = (branch) => branch?.trim().replace(currentBranchPrefix, "");
371
+ const formatBranch$1 = (branch) => branch.trim().replace(currentBranchPrefix, "");
371
372
  const getLocalBranches = async () => {
372
373
  const list = (await x("git", ["branch", "--sort=-committerdate"])).stdout.split("\n");
373
374
  const currentBranch = list.find((line) => line.includes("*"));
@@ -376,7 +377,7 @@ const getLocalBranches = async () => {
376
377
  currentBranch: formatBranch$1(currentBranch)
377
378
  };
378
379
  };
379
- const getRemoteOptions = async () => {
380
+ const getRemoteBranchOptions = async () => {
380
381
  const { branches } = await getRemoteBranches();
381
382
  const currentBranch = await getCurrentBranch();
382
383
  return {
@@ -388,7 +389,7 @@ const getRemoteOptions = async () => {
388
389
  currentBranch
389
390
  };
390
391
  };
391
- const getLocalOptions = async () => {
392
+ const getLocalBranchOptions = async () => {
392
393
  const { branches, currentBranch } = await getLocalBranches();
393
394
  return {
394
395
  options: branches.map((branch) => ({
@@ -399,7 +400,7 @@ const getLocalOptions = async () => {
399
400
  currentBranch
400
401
  };
401
402
  };
402
- const getRemoteOptionsForRemotes = async () => {
403
+ const getRemoteRepositoryOptions = async () => {
403
404
  const { remotes } = await getRemotes();
404
405
  const validRemotes = remotes.filter((remote) => remote.trim().length > 0);
405
406
  const uniqueRemotes = Array.from(new Set(validRemotes));
@@ -494,18 +495,18 @@ const handleMergeCommit = async () => {
494
495
  }
495
496
  };
496
497
  const handleGitPull = async (branch, options = {}) => {
497
- const { rebase = false } = options;
498
+ const { rebase = false, remote = "origin" } = options;
498
499
  const modeText = rebase ? "rebase" : "merge";
499
500
  log.show(`Pulling from remote (${modeText} mode)...`, { type: "step" });
500
501
  try {
501
502
  const [error, result] = await xASync("git", rebase ? [
502
503
  "pull",
503
504
  "--rebase",
504
- "origin",
505
+ remote,
505
506
  branch
506
507
  ] : [
507
508
  "pull",
508
- "origin",
509
+ remote,
509
510
  branch
510
511
  ], { nodeOptions: { stdio: "inherit" } });
511
512
  if (error) {
@@ -549,7 +550,7 @@ const handleGitStash = async (message, options) => {
549
550
  const { branch = void 0, operation = "manual" } = options || {};
550
551
  const now = /* @__PURE__ */ new Date();
551
552
  let stashName;
552
- if (message && message.trim()) stashName = message.trim();
553
+ if (message?.trim()) stashName = message.trim();
553
554
  else stashName = `${operation}:${currentBranch}@${now.toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
554
555
  const internalId = `${Date.now()}_${operation}_${currentBranch.replace(/[/]/g, "_")}`;
555
556
  const [error, result] = await xASync("git", [
@@ -799,6 +800,29 @@ function branchCommand(command) {
799
800
  }, { isRemote: options.remote ?? false })));
800
801
  Message({ text: "Successfully deleted branches" });
801
802
  });
803
+ subCommand.command("list").alias("ls").description("List git branches").addHelpText("after", HELP_MESSAGE$1.branchList).option("-l, --local", "List local branches").option("-r, --remote", "List remote branches").option("-a, --all", "List all branches", true).option("-n, --number <count>", "Limit number of branches to show").action(async (options) => {
804
+ if (options.all || !options.local && !options.remote) {
805
+ await renderBranchViewer(options.number ? Number.parseInt(options.number, 10) : void 0);
806
+ return;
807
+ }
808
+ if (options.local) {
809
+ const { branches } = await getLocalBranches();
810
+ if (!branches || branches.length === 0) {
811
+ log.error("No local branches found. Please check your git repository.");
812
+ return;
813
+ }
814
+ log.info(`Found ${branches.length} local branches:`);
815
+ for (const branch of branches) log.info(branch);
816
+ } else {
817
+ const { branches } = await getRemoteBranches();
818
+ if (!branches || branches.length === 0) {
819
+ log.error("No remote branches found. Please check your git repository.");
820
+ return;
821
+ }
822
+ log.info(`Found ${branches.length} remote branches:`);
823
+ for (const branch of branches) log.info(branch);
824
+ }
825
+ });
802
826
  }
803
827
 
804
828
  //#endregion
@@ -881,13 +905,13 @@ function checkoutCommand(command) {
881
905
  initialValue: true
882
906
  });
883
907
  if (isLocal) {
884
- const { options } = await getLocalOptions();
908
+ const { options } = await getLocalBranchOptions();
885
909
  handleCheckout(await createSelect({
886
910
  message: `Select the ${colors.bgGreen(" local ")} branch to checkout`,
887
911
  options
888
912
  }));
889
913
  } else {
890
- const { options } = await getRemoteOptions();
914
+ const { options } = await getRemoteBranchOptions();
891
915
  const selectedBranch = await createSelect({
892
916
  message: `Select the ${colors.bgYellow(" remote ")} branch to checkout`,
893
917
  options
@@ -1089,50 +1113,50 @@ const pushInteractive = async () => {
1089
1113
  log.error("No branch selected. Aborting push operation.");
1090
1114
  return;
1091
1115
  }
1092
- let remotes;
1116
+ let repositories;
1093
1117
  try {
1094
- remotes = (await getRemoteOptionsForRemotes()).remotes;
1118
+ repositories = (await getRemoteRepositoryOptions()).remotes;
1095
1119
  } catch (error) {
1096
1120
  log.error(`Failed to get remote repositories: ${error instanceof Error ? error.message : String(error)}`);
1097
1121
  log.show("Hint: Make sure you're in a git repository and have configured remotes.", { type: "info" });
1098
1122
  return;
1099
1123
  }
1100
- if (remotes.length === 0) {
1124
+ if (repositories.length === 0) {
1101
1125
  log.error("No remote repositories found. Aborting push operation.");
1102
1126
  log.show("Hint: Run `git remote add <name> <url>` to add a remote repository.", { type: "info" });
1103
1127
  return;
1104
1128
  }
1105
- let selectedRemote = remotes[0];
1106
- if (remotes.length > 1) selectedRemote = await createSelect({
1129
+ let selectedRepository = repositories[0];
1130
+ if (repositories.length > 1) selectedRepository = await createSelect({
1107
1131
  message: "Select remote repository",
1108
- options: remotes.map((remote) => ({
1109
- label: remote,
1110
- value: remote
1132
+ options: repositories.map((repo) => ({
1133
+ label: repo,
1134
+ value: repo
1111
1135
  })),
1112
- initialValue: remotes[0]
1136
+ initialValue: repositories[0]
1113
1137
  });
1114
- if (await createConfirm({ message: `Do you want to push ${colors.bgGreen(currentBranch)} to ${selectedRemote}?` })) {
1115
- await handlePush(currentBranch, selectedRemote);
1138
+ if (await createConfirm({ message: `Do you want to push ${colors.bgGreen(currentBranch)} to ${selectedRepository}?` })) {
1139
+ await handlePush(currentBranch, selectedRepository);
1116
1140
  return;
1117
1141
  }
1118
- const { options } = await getRemoteOptions();
1142
+ const { options } = await getRemoteBranchOptions();
1119
1143
  const selectedBranch = await createSelect({
1120
1144
  message: "Select the branch to push",
1121
1145
  options,
1122
1146
  initialValue: "main"
1123
1147
  });
1124
- let pushRemote = selectedRemote;
1125
- if (remotes.length > 1) {
1126
- if (!await createConfirm({ message: `Push ${colors.bgGreen(selectedBranch)} to ${selectedRemote}?` })) pushRemote = await createSelect({
1148
+ selectedRepository = repositories[0];
1149
+ if (repositories.length > 1) {
1150
+ if (!await createConfirm({ message: `Push ${colors.bgGreen(selectedBranch)} to ${selectedRepository}?` })) selectedRepository = await createSelect({
1127
1151
  message: "Select remote repository",
1128
- options: remotes.map((remote) => ({
1129
- label: remote,
1130
- value: remote
1152
+ options: repositories.map((repo) => ({
1153
+ label: repo,
1154
+ value: repo
1131
1155
  })),
1132
- initialValue: selectedRemote
1156
+ initialValue: selectedRepository
1133
1157
  });
1134
1158
  }
1135
- await handlePush(selectedBranch, pushRemote);
1159
+ await handlePush(selectedBranch, selectedRepository);
1136
1160
  };
1137
1161
 
1138
1162
  //#endregion
@@ -1712,25 +1736,18 @@ const configCommand = (command) => {
1712
1736
  //#endregion
1713
1737
  //#region src/commands/diff.ts
1714
1738
  const handleDiff = async (branch, { isLocal }) => {
1715
- console.log("🚀 : handleDiff : branch:", branch, isLocal);
1716
1739
  const currentBranch = await getCurrentBranch();
1717
1740
  if (!currentBranch) {
1718
1741
  log.error("Could not determine current branch");
1719
1742
  return;
1720
1743
  }
1721
- const diffArgs = branch === currentBranch ? ["diff"] : ["diff", `${branch}...${currentBranch}`];
1744
+ const targetBranch = branch === currentBranch ? void 0 : branch;
1722
1745
  log.show(`Showing diff between ${branch === currentBranch ? "working directory and HEAD" : `${branch} and ${currentBranch}`}`);
1723
- const process = x("git", diffArgs);
1724
- let hasOutput = false;
1725
- for await (const line of process) {
1726
- hasOutput = true;
1727
- log.show(line);
1746
+ try {
1747
+ await renderDiffViewer(targetBranch);
1748
+ } catch (error) {
1749
+ log.error(`Failed to display diff viewer: ${error instanceof Error ? error.message : "Unknown error"}${targetBranch ? ` (branch: ${targetBranch})` : ""}`);
1728
1750
  }
1729
- const { exitCode, stderr } = await process;
1730
- if (exitCode) {
1731
- log.error(`Failed to diff. Command exited with code ${exitCode}.`);
1732
- if (stderr) log.error(stderr);
1733
- } else if (!hasOutput) log.show("No differences found.", { type: "info" });
1734
1751
  };
1735
1752
  function diffCommand(command) {
1736
1753
  command.command("diff").alias("di").description("Show differences between branches or working directory").option("-l, --local", "Diff local branch", true).option("-r, --remote", "Diff remote branch").action(async (options) => {
@@ -1760,71 +1777,47 @@ const histCommand = (command) => {
1760
1777
  return command;
1761
1778
  };
1762
1779
 
1763
- //#endregion
1764
- //#region src/commands/list.ts
1765
- function listCommand(command) {
1766
- command.command("list").alias("ls").description("List git branches").option("-l, --local", "List local branches").option("-r, --remote", "List remote branches").option("-a, --all", "List all branches", true).action(async (options) => {
1767
- if (options.all) {
1768
- const { branches: localBranches, currentBranch } = await getLocalBranches();
1769
- const { branches: remoteBranches } = await getRemoteBranches();
1770
- if (!localBranches.length && !remoteBranches.length) {
1771
- log.error("No branches found. Please check your git repository.");
1772
- return;
1773
- }
1774
- log.show(`Local ${localBranches.length} branches`, {
1775
- symbol: "🔖",
1776
- colors: colors.bgGreen
1777
- });
1778
- for (const branch of localBranches) if (branch === currentBranch) log.show(`${branch} (current)`, { type: "info" });
1779
- else log.show(branch, { type: "step" });
1780
- log.show(`Remote ${remoteBranches.length} branches`, {
1781
- symbol: "🔖",
1782
- colors: colors.bgYellow
1783
- });
1784
- for (const branch of remoteBranches) if (branch === currentBranch) log.show(`${branch} (current)`, { type: "info" });
1785
- else log.show(branch, { type: "step" });
1786
- } else if (options.local) {
1787
- const { branches } = await getLocalBranches();
1788
- if (!branches || branches.length === 0) {
1789
- log.error("No local branches found. Please check your git repository.");
1790
- return;
1791
- }
1792
- log.info(`Found ${branches.length} local branches:`);
1793
- for (const branch of branches) log.info(branch);
1794
- } else {
1795
- const { branches } = await getRemoteBranches();
1796
- if (!branches || branches.length === 0) {
1797
- log.error("No remote branches found. Please check your git repository.");
1798
- return;
1799
- }
1800
- log.info(`Found ${branches.length} remote branches:`);
1801
- for (const branch of branches) log.info(branch);
1802
- }
1803
- });
1804
- }
1805
-
1806
1780
  //#endregion
1807
1781
  //#region src/commands/merge.ts
1808
1782
  const handleMerge = async (branch) => {
1809
1783
  const spinner = createSpinner(`Merging branch ${branch}...`);
1810
- const args = ["merge", branch];
1784
+ const args = [
1785
+ "merge",
1786
+ "--no-edit",
1787
+ branch
1788
+ ];
1811
1789
  const stashResult = await handleGitStash(void 0, {
1812
1790
  branch,
1813
1791
  operation: "merge"
1814
1792
  });
1793
+ if (!stashResult) {
1794
+ spinner.stop("Cannot proceed with merge: failed to stash changes.");
1795
+ return;
1796
+ }
1815
1797
  try {
1816
- const [error] = await xASync("git", args, { nodeOptions: { stdio: "inherit" } });
1817
- if (error) log.show(`Failed to merge branch ${branch}.`, { type: "error" });
1818
- else spinner.stop(`Successfully merged branch ${branch}.`);
1798
+ const [error, result] = await xASync("git", args, { nodeOptions: { stdio: "inherit" } });
1799
+ if (error) {
1800
+ spinner.stop();
1801
+ const errorMessage = error instanceof Error ? error.message : String(error);
1802
+ throw new Error(`Failed to merge branch ${branch}: ${errorMessage}`);
1803
+ }
1804
+ spinner.stop();
1805
+ if (result.stdout.includes("Merge branch") || result.stdout.includes("Merge made by")) await handleMergeCommit();
1806
+ log.show(colors.green(`Successfully merged branch ${colors.bgGreen(branch)}.`), { type: "success" });
1807
+ } catch (error) {
1808
+ spinner.stop();
1809
+ log.show(`Failed to merge branch ${branch}.`, { type: "error" });
1810
+ log.error(error);
1811
+ throw error;
1819
1812
  } finally {
1820
- stashResult && handleGitPop(stashResult);
1813
+ if (stashResult) await handleGitPop(stashResult);
1821
1814
  }
1822
1815
  };
1823
1816
  function mergeCommand(command) {
1824
1817
  command.command("merge").alias("mg").argument("[branch]", "The branch to merge").option("-l, --local", "Merge a local branch").option("-r, --remote", "Merge a remote branch").option("-b, --branch <branch>", "Create and merge a new branch").description("Merge a branch").action(async (branch, params) => {
1825
1818
  let isLocal = params.local;
1826
1819
  if (branch) {
1827
- handleMerge(branch);
1820
+ await handleMerge(branch);
1828
1821
  return;
1829
1822
  }
1830
1823
  if (isEmpty(params)) isLocal = await createSelect({
@@ -1839,18 +1832,20 @@ function mergeCommand(command) {
1839
1832
  initialValue: false
1840
1833
  });
1841
1834
  if (isLocal) {
1842
- const { options } = await getLocalOptions();
1843
- handleMerge(await createSearch({
1835
+ const { options, currentBranch } = await getLocalBranchOptions();
1836
+ await handleMerge(await createSelect({
1844
1837
  message: "Select the branch to merge",
1845
- options
1838
+ options,
1839
+ initialValue: currentBranch
1846
1840
  }));
1847
1841
  } else {
1848
- const { options } = await getRemoteOptions();
1849
- const selectedBranch = await createSearch({
1842
+ const { options, currentBranch } = await getRemoteBranchOptions();
1843
+ const selectedBranch = await createSelect({
1850
1844
  message: "Select the branch to merge",
1851
- options
1845
+ options,
1846
+ initialValue: currentBranch
1852
1847
  });
1853
- if (await createConfirm({ message: `Do you want to merge ${colors.bgRed(selectedBranch)}?` })) handleMerge(selectedBranch);
1848
+ if (await createConfirm({ message: `Do you want to merge ${colors.bgRed(selectedBranch)}?` })) await handleMerge(selectedBranch);
1854
1849
  }
1855
1850
  });
1856
1851
  }
@@ -1859,7 +1854,40 @@ function mergeCommand(command) {
1859
1854
  //#region src/commands/pull.ts
1860
1855
  function pullCommand(command) {
1861
1856
  command.command("pull").alias("pl").description("Pull git branch").option("-r, --rebase", "Use rebase mode instead of merge").option("-m, --merge", "Use merge mode (default)").action(async (options) => {
1862
- const { options: branchOptions, currentBranch } = await getRemoteOptions();
1857
+ const [error, result] = await safeAwait(getRemoteRepositoryOptions());
1858
+ if (error) {
1859
+ log.error(`Failed to get remote repositories: ${error.message}`);
1860
+ return;
1861
+ }
1862
+ const repositories = result.remotes;
1863
+ if (repositories.length === 0) {
1864
+ log.error("No remote repositories found. Aborting pull operation.");
1865
+ log.show("Hint: Use \"git remote add <name> <url>\" to add a remote repository.", { type: "info" });
1866
+ return;
1867
+ }
1868
+ const selectedRepository = repositories.length > 1 ? (await safeAwait(createSelect({
1869
+ message: "Select remote repository",
1870
+ options: result.options,
1871
+ initialValue: repositories[0]
1872
+ })))[1] : repositories[0];
1873
+ if (!selectedRepository) {
1874
+ log.error("No remote selected. Aborting pull operation.");
1875
+ return;
1876
+ }
1877
+ const spinner = createSpinner("Fetching latest remote branches...");
1878
+ const [fetchError] = await xASync("git", [
1879
+ "fetch",
1880
+ selectedRepository,
1881
+ "--prune"
1882
+ ], { timeout: 3e4 });
1883
+ if (fetchError) {
1884
+ spinner.stop("Failed to fetch latest remote branches");
1885
+ log.error(`Failed to fetch from ${selectedRepository}: ${fetchError.message}`);
1886
+ log.show("Please check your network connection and try again.", { type: "info" });
1887
+ return;
1888
+ }
1889
+ spinner.stop("Successfully fetched latest remote branches");
1890
+ const { options: branchOptions, currentBranch } = await getRemoteBranchOptions();
1863
1891
  if (!branchOptions.length) {
1864
1892
  log.error("No branches found. Please check your git repository.");
1865
1893
  return;
@@ -1879,11 +1907,11 @@ function pullCommand(command) {
1879
1907
  options: [{
1880
1908
  label: "Merge (default)",
1881
1909
  value: "merge",
1882
- hint: "git pull origin <branch>"
1910
+ hint: `git pull ${selectedRepository} ${selectedBranch}`
1883
1911
  }, {
1884
1912
  label: "Rebase",
1885
1913
  value: "rebase",
1886
- hint: "git pull --rebase origin <branch>"
1914
+ hint: `git pull --rebase ${selectedRepository} ${selectedBranch}`
1887
1915
  }],
1888
1916
  initialValue: "merge"
1889
1917
  }) === "rebase";
@@ -1891,11 +1919,27 @@ function pullCommand(command) {
1891
1919
  branch: selectedBranch,
1892
1920
  operation: "pull"
1893
1921
  });
1894
- await handleGitPull(selectedBranch, { rebase: useRebase });
1922
+ await handleGitPull(selectedBranch, {
1923
+ remote: selectedRepository,
1924
+ rebase: useRebase
1925
+ });
1895
1926
  stashResult && handleGitPop(stashResult);
1896
1927
  });
1897
1928
  }
1898
1929
 
1930
+ //#endregion
1931
+ //#region src/commands/show.ts
1932
+ const showCommand = (command) => {
1933
+ command.command("show [hash]").description("Show commit details (interactive viewer)").option("-n, --number <count>", "Limit number of commits to show in selector").action(async (hash, options) => {
1934
+ if (hash) await renderCommitDetail(hash);
1935
+ else {
1936
+ const selectedHash = await renderCommitViewer(options.number ? Number.parseInt(options.number, 10) : void 0);
1937
+ if (selectedHash) await renderCommitDetail(selectedHash);
1938
+ }
1939
+ });
1940
+ return command;
1941
+ };
1942
+
1899
1943
  //#endregion
1900
1944
  //#region src/constants/stash.ts
1901
1945
  const HELP_MESSAGE = {
@@ -2137,7 +2181,6 @@ const pkg = readPackage(import.meta, "..");
2137
2181
  const init = () => {
2138
2182
  const command = createCommand("ng").version(pkg.version).description(`${pkg.name} CLI helper for git`).addHelpText("after", HELP_MESSAGE$1.main);
2139
2183
  pullCommand(command);
2140
- listCommand(command);
2141
2184
  pushCommand(command);
2142
2185
  checkoutCommand(command);
2143
2186
  branchCommand(command);
@@ -2148,6 +2191,7 @@ const init = () => {
2148
2191
  commitCommand(command);
2149
2192
  statusCommand(command);
2150
2193
  histCommand(command);
2194
+ showCommand(command);
2151
2195
  configCommand(command);
2152
2196
  return command;
2153
2197
  };