From 3397500701e319e1e77804075d1898efafa430bd Mon Sep 17 00:00:00 2001 From: ize-302 Date: Fri, 27 Sep 2024 18:35:32 +0100 Subject: [PATCH] feat: add amend commit command --- README.md | 31 ++++--- build.mjs | 2 +- bun.lockb | Bin 16750 -> 16769 bytes package.json | 7 +- src/cli.ts | 28 ------ src/constants/flags.js | 8 -- src/gitmo.ts | 83 +++++++++++++++++ src/index.ts | 4 + src/utils/handleCommand.js | 129 --------------------------- src/utils/hasStagedChanges.ts | 22 +++++ src/utils/isConventionalCommit.ts | 34 +++++++ src/utils/onCancel.ts | 6 ++ src/utils/promptContinue.ts | 21 +++++ src/utils/showCommitMessagePrompt.ts | 34 +++++++ 14 files changed, 227 insertions(+), 182 deletions(-) delete mode 100755 src/cli.ts delete mode 100644 src/constants/flags.js create mode 100755 src/gitmo.ts create mode 100644 src/index.ts delete mode 100644 src/utils/handleCommand.js create mode 100644 src/utils/hasStagedChanges.ts create mode 100644 src/utils/isConventionalCommit.ts create mode 100644 src/utils/onCancel.ts create mode 100644 src/utils/promptContinue.ts create mode 100644 src/utils/showCommitMessagePrompt.ts diff --git a/README.md b/README.md index bee8cb3..7d11e29 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,22 @@ gitmo --help ``` ``` - A cli tool that adds appropriate emoji - to your commit message based on conventional commits specification - - Usage - $ gitmo [option] [command] - Options - --commit, -c Add commit using the gitmo - --version, -v Print current installed version - --update, -u Update gitmo cli - Examples - $ gitmo -c +Usage: gitmo [options] [command] + +A cli tool that adds appropriate emoji to your commit message based on conventional commits specification + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + cm [message] Submit commit + ac [message] Ament last commit + update Update gitmo cli + help [command] display help for command + +Example: + gitmo cm "feat: first commit" ``` ## Commit types @@ -60,7 +65,7 @@ gitmo --help ```bash # Note: This should be done after staging your changes -gitmo -c +gitmo cm ``` You get this prompt: @@ -80,7 +85,7 @@ press ENTER bun run build # Run command -./dist/cli.js gitmo -c +./dist/cli.js gitmo cm ``` --- diff --git a/build.mjs b/build.mjs index d2d8911..af596f3 100644 --- a/build.mjs +++ b/build.mjs @@ -1,6 +1,6 @@ await Bun.build({ target: 'node', - entrypoints: ['./src/cli.ts'], + entrypoints: ['./src/index.ts'], outdir: './dist', minify: true, external: ['shelljs'], diff --git a/bun.lockb b/bun.lockb index 5872a041e7a1057aae73c9ec4ec31261eaedb213..e04f6843b21197715850a9c0592fdf6532fec303 100755 GIT binary patch delta 1318 zcmYLIe{54#6n?ku>vsENbqXt`8-r*fF6(RGy3&oY-YpWfOt+K-$sCfDZEkjK#pyKT z53;c=p)({lhYArBU^4{yA)3ZSA?S!k)1gi=BVkJn@YNesjCo;1GLDSyf&eZSAb5Yh$hBJrS}T-gh_IRL}M# zlg{QACP}4|G!Ow=fKLJofSu7-<8SmxQpzAncElaq?wyGR0N)Q(U-N5@uE(2Zz76B)KE?^qy1Rh+-57TA#b^fJ->Yj&}HiYSz;R@SG z38P?1%D`SAZW7E!Vc4CNfW1N)*kaVekG?h5 zurrj%7wm7!z^)7l#DTzAnuRp0(C(c9a#LnrWNt{b&BQvwhp*6T*Jk zYivCgx)gVO_eb5a-);|&O|^|I+5JuJBNzMDe{`d8!_@H`kE>_!JDUh0b~XEJb&{9RdCMmm02ajm*vM z0L!na3f{FY5cy_mP0xW3Yzy^1NQn0OTy9-3&7aT7@BVaabvCbR{Tu1L&&9UTjBk+L zp%+z`TbIKN-YLhdv3bP6b_JCR98#*Jr0Qb*bONk-+^r@}IxDCC`#3slU}gCK+LvX~ zJ#{(D@yE$i;d1M88oO|y|4{7W%jjCA1QgW=ezd}sBq&7)G6tMI&^ADdordoiyI`pi4+ z%bwmB`(iOuP7VGP8>657PBu&T{X2MTyt_Nv)7IWg$v|;>xN@m^`GX~|$NRwdXg8le IzV=J=|DN$r?EnA( delta 1284 zcmYL|ZA@Eb6vvCD1>nT~A|HAVu)5+p2hiRk~f+;Ee7e&^hC zo^x)_c~1WTPrbqqsU?=%=9-1x(4Mtbe_rp7Uflh{Y-)b4_4NfubMLuV>q}cF`4TSHl@AW~^@uXamc@T=1pF-5ovcj2(~|%Yogpv*SgEr`W_;KJ1mC9khc2oCWj1 zT5~4PX5!pcIVESTww+B~QJj9csiQ~FqT$u6c|g5dNmeFXGnfs#V9B1LUA!5fCKykTm@bO3&4w@0-gj5!NDbelqUGs)+5{gex&tnB}HeYUwDXO zCXEkL67mZYW{sOD1i6c1kUvop@_rJs#-E`Oa(U)?3WVFH(5XWFW0gF>u4)C8TmZk9rD4C^+Ds-&W@W}nJ+;r6x zCP}~*}$6@LDWm;hl@EepA8h@08P2*7tK_)0>)5JaW zwK5%rU*ka%c8%|$5afpxvun~lD_yZy@BoQyP2Pa^)=^G&P(BE4M_T(GT6J3UVG3L6 zYEHQsN;vcmUcTa6*6!+2Xl#|7fZi2gj1rpJXKz_b9xX&LL_B&x1S@KT1xo;xHEI z^fyQMo``(cimV$|zv^)#Yok}m#5$truD(7aHw*li?`;3p%suKRy)+x7_q~eWSf{-C zBZrGC#!oK$jfEQ<*`lp}dgF;@&o%lNCyfQwobOsAHy@f^_8Tkw@taMno_Ztl<+4Yi qt-fJ?h<^1I@*n9RUniX}btHyL3oQBiuw71<4yQil#8}x^mj3}t<4+_2 diff --git a/package.json b/package.json index 4976358..813ca29 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "A cli tool that adds appropriate emoji to your commit message based on conventional commits specification", "bin": { - "gitmo": "dist/cli.js" + "gitmo": "./dist/index.js" }, "type": "module", "scripts": { @@ -38,7 +38,7 @@ }, "homepage": "https://github.com/ize-302/gitmo", "dependencies": { - "meow": "^13.2.0", + "commander": "^12.1.0", "prompts": "^2.4.2", "shelljs": "^0.8.5" }, @@ -49,5 +49,6 @@ "@types/shelljs": "^0.8.15", "typescript": "^5.6.2" }, - "module": "index.ts" + "module": "index.ts", + "main": "dist/index.js" } diff --git a/src/cli.ts b/src/cli.ts deleted file mode 100755 index d8c19dd..0000000 --- a/src/cli.ts +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -import FLAGS from "@/constants/flags.js"; -import { handleCommand } from "@/utils/handleCommand.js"; -import meow from "meow"; - -const cli = meow( - ` - Usage - $ gitmo [option] [command] - Options - --${FLAGS.COMMIT}, -c Add commit using the gitmo - --${FLAGS.VERSION}, -v Print current installed version - --${FLAGS.UPDATE}, -u Update gitmo cli - Examples - $ gitmo -c -`, - { - importMeta: import.meta, - flags: { - [FLAGS.COMMIT]: { type: "boolean", shortFlag: "c" }, - [FLAGS.HELP]: { type: "boolean", shortFlag: "h" }, - [FLAGS.UPDATE]: { type: "boolean", shortFlag: "u" }, - [FLAGS.VERSION]: { type: "boolean", shortFlag: "v" }, - }, - }, -); - -handleCommand(cli); diff --git a/src/constants/flags.js b/src/constants/flags.js deleted file mode 100644 index ef107a0..0000000 --- a/src/constants/flags.js +++ /dev/null @@ -1,8 +0,0 @@ -const FLAGS = Object.freeze({ - COMMIT: "commit", - HELP: "help", - UPDATE: "update", - VERSION: "version", -}); - -export default FLAGS; diff --git a/src/gitmo.ts b/src/gitmo.ts new file mode 100755 index 0000000..2e1118b --- /dev/null +++ b/src/gitmo.ts @@ -0,0 +1,83 @@ +import { Command } from "commander"; +import shell from "shelljs"; + +import hasStagedChanges from "@/utils/hasStagedChanges.js"; +import isConventionalCommit from "@/utils/isConventionalCommit.js"; +import onCancel from "@/utils/onCancel.js"; +import promptContinue from "@/utils/promptContinue.js"; +import showCommitMessagePrompt from "@/utils/showCommitMessagePrompt.js"; +import packageJson from "../package.json"; + +shell.config.silent = false; + +const program = new Command(); + +export const init = () => { + try { + program + .name("gitmo") + .description( + "A cli tool that adds appropriate emoji to your commit message based on conventional commits specification", + ) + .version(packageJson.version); + + program + .command("cm [message]") + .description("Submit commit") + .action(async (message) => { + if (!message) { + if (hasStagedChanges()) { + const modifiedMessage = await showCommitMessagePrompt(); + shell.exec(`git commit -m '${modifiedMessage}'`); + } + } else { + const isValidCommit = await isConventionalCommit(message); + if (isValidCommit) { + shell.exec(`git commit -m '${message}'`); + } else { + const response = await promptContinue(); + if (response.continue === "yes") { + shell.exec(`git commit -m '${message}'`); + } else { + onCancel(); + } + } + } + }); + + program + .command("ac [message]") + .description("Ament last commit") + .action(async (message) => { + if (!message) { + const modifiedMessage = await showCommitMessagePrompt(); + shell.exec(`git commit -m '${modifiedMessage}'`); + } else { + const isValidCommit = await isConventionalCommit(message); + if (isValidCommit) { + shell.exec(`git commit --amend -m '${message}'`); + } else { + const response = await promptContinue(); + if (response.continue === "yes") { + shell.exec(`git commit --amend -m '${message}'`); + } else { + onCancel(); + } + } + } + }); + + program + .command("update") + .description("Update gitmo cli") + .action(() => { + shell.exec("npm i -g gitmo"); + }); + + // Parse the command-line arguments + program.parse(process.argv); + } catch (error) { + console.log(error); + process.exit(1); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b97f38a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +#!/usr/bin/env node +import { init } from "./gitmo.js"; + +init(); diff --git a/src/utils/handleCommand.js b/src/utils/handleCommand.js deleted file mode 100644 index 1db6369..0000000 --- a/src/utils/handleCommand.js +++ /dev/null @@ -1,129 +0,0 @@ -import commitTypes from "@/commit-types.json"; -import FLAGS from "@/constants/flags.js"; -import prompts from "prompts"; -import shell from "shelljs"; - -shell.config.silent = false; - -const validateCommand = (cli) => { - const validFlagsArray = Object.values(FLAGS); - const inputFlagsArray = Object.keys(cli.flags); - - const result = inputFlagsArray.filter( - (element) => !validFlagsArray.includes(element), - ); - - if (result.length > 0) { - console.log(`unknown option -${result[0]}`); - cli.showHelp(); - return; - } -}; - -const onCancel = () => { - console.log("Exited Gitmo!"); - process.exit(1); -}; - -const isConventionalCommit = async (commitMessage) => { - if ( - commitMessage.startsWith("feat") || - commitMessage.startsWith("fix") || - commitMessage.startsWith("docs") || - commitMessage.startsWith("style") || - commitMessage.startsWith("refactor") || - commitMessage.startsWith("perf") || - commitMessage.startsWith("test") || - commitMessage.startsWith("ci") || - commitMessage.startsWith("build") || - commitMessage.startsWith("chore") || - commitMessage.startsWith("revert") - ) { - /** - * Here is the structure of a typical conventional commit message - * type(scope): commit message - * - * Note: I want to extract the type i.e the string that comes before "(scope)" - * */ - // Find the index of the opening parenthesis and colon - const parenthesisIndex = commitMessage.indexOf("("); - const colonIndex = commitMessage.indexOf(":"); - // Determine the end index for the substring (whichever comes first) - const endIndex = parenthesisIndex !== -1 ? parenthesisIndex : colonIndex; - // Extract the substring from the start to the end index - const beforeColon = commitMessage.substring(0, endIndex).trim(); - return commitTypes.find((item) => item.name === beforeColon); - } -}; - -const showCommitPrompt = async () => { - // check if there are staged changes - const status = shell.exec("git status --porcelain", { silent: true }).stdout; - // Parse the status output - const stagedChanges = status - .split("\n") - .filter((line) => line.startsWith("A ") || line.startsWith("M ")); - if (stagedChanges.length === 0) { - console.log("You have no changes staged for commit!"); - return; - } - - const response = await prompts( - { - type: "text", - name: "commitMessage", - message: "commit message", - }, - { onCancel }, - ); - const { commitMessage } = response; - if (commitMessage) { - const ctype = await isConventionalCommit(commitMessage); - if (ctype) { - const updatedCommitMessage = `${response.commitMessage} ${ctype?.emoji}`; - console.log("COMMIT MESSAGE:", updatedCommitMessage); - shell.exec(`git commit -m '${updatedCommitMessage}'`); - } else { - console.log( - "Commit message does not align with the specifications of conventional commit", - ); - const response = await prompts( - { - type: "select", - name: "continue", - message: "continue anyway?", - choices: [ - { title: "Yes", value: "yes" }, - { title: "Cancel", value: "cancel" }, - ], - instructions: false, - }, - { onCancel }, - ); - response.continue === "yes" - ? shell.exec(`git commit -m '${commitMessage}'`) - : onCancel(); - } - } else { - console.log("Commit message cant be empty"); - await showCommitPrompt(); - } -}; - -export const handleCommand = (cli) => { - if (!shell.which("git")) { - shell.echo("Sorry, this script requires git"); - shell.exit(1); - } - - validateCommand(cli); - if (cli.flags.version) { - console.log(`${cli.pkg.name} version ${cli.pkg.version}`); - } else if (cli.flags.help) { - cli.showHelp(); - } else if (cli.flags.update) { - shell.exec("npm i -g gitmo"); - } else if (cli.flags.commit) { - showCommitPrompt(); - } -}; diff --git a/src/utils/hasStagedChanges.ts b/src/utils/hasStagedChanges.ts new file mode 100644 index 0000000..99c7018 --- /dev/null +++ b/src/utils/hasStagedChanges.ts @@ -0,0 +1,22 @@ +import shell from "shelljs"; + +// check if there is exisiting staged commit +const hasStagedChanges = () => { + // check if there are staged changes + const gitStatus = shell.exec("git status --porcelain", { + silent: true, + }).stdout; + // Parse the status output + const stagedChanges = gitStatus + .split("\n") + .filter((line) => line.startsWith("A ") || line.startsWith("M ")); + if (stagedChanges.length === 0) { + console.log( + "You have no changes staged for commit. Please stage you commit before continuing!", + ); + } else { + return true; + } +}; + +export default hasStagedChanges; diff --git a/src/utils/isConventionalCommit.ts b/src/utils/isConventionalCommit.ts new file mode 100644 index 0000000..c8e5b40 --- /dev/null +++ b/src/utils/isConventionalCommit.ts @@ -0,0 +1,34 @@ +import commitTypes from "@/commit-types.json"; + +const isConventionalCommit = async (commitMessage: string) => { + if ( + commitMessage.startsWith("feat") || + commitMessage.startsWith("fix") || + commitMessage.startsWith("docs") || + commitMessage.startsWith("style") || + commitMessage.startsWith("refactor") || + commitMessage.startsWith("perf") || + commitMessage.startsWith("test") || + commitMessage.startsWith("ci") || + commitMessage.startsWith("build") || + commitMessage.startsWith("chore") || + commitMessage.startsWith("revert") + ) { + /** + * Here is the structure of a typical conventional commit message + * type(scope): commit message + * + * Note: I want to extract the type i.e the string that comes before "(scope)" + * */ + // Find the index of the opening parenthesis and colon + const parenthesisIndex = commitMessage.indexOf("("); + const colonIndex = commitMessage.indexOf(":"); + // Determine the end index for the substring (whichever comes first) + const endIndex = parenthesisIndex !== -1 ? parenthesisIndex : colonIndex; + // Extract the substring from the start to the end index + const beforeColon = commitMessage.substring(0, endIndex).trim(); + return commitTypes.find((item) => item.name === beforeColon); + } +}; + +export default isConventionalCommit; diff --git a/src/utils/onCancel.ts b/src/utils/onCancel.ts new file mode 100644 index 0000000..a10c2b4 --- /dev/null +++ b/src/utils/onCancel.ts @@ -0,0 +1,6 @@ +const onCancel = () => { + console.log("Exited Gitmo!"); + process.exit(1); +}; + +export default onCancel; diff --git a/src/utils/promptContinue.ts b/src/utils/promptContinue.ts new file mode 100644 index 0000000..5b882f7 --- /dev/null +++ b/src/utils/promptContinue.ts @@ -0,0 +1,21 @@ +import prompts from "prompts"; +import onCancel from "./onCancel.js"; + +const promptContinue = async () => { + const response = await prompts( + { + type: "select", + name: "continue", + message: "continue anyway?", + choices: [ + { title: "Yes", value: "yes" }, + { title: "Cancel", value: "cancel" }, + ], + instructions: false, + }, + { onCancel }, + ); + return response; +}; + +export default promptContinue; diff --git a/src/utils/showCommitMessagePrompt.ts b/src/utils/showCommitMessagePrompt.ts new file mode 100644 index 0000000..2c65269 --- /dev/null +++ b/src/utils/showCommitMessagePrompt.ts @@ -0,0 +1,34 @@ +import prompts from "prompts"; +import isConventionalCommit from "./isConventionalCommit.js"; +import onCancel from "./onCancel.js"; +import promptContinue from "./promptContinue.js"; + +const showCommitMessagePrompt = async () => { + const response = await prompts( + { + type: "text", + name: "commitMessage", + message: "commit message", + }, + { onCancel }, + ); + const { commitMessage } = response; + let updatedCommitMessage = ""; + const isValidCommit = await isConventionalCommit(commitMessage); + if (isValidCommit) { + updatedCommitMessage = `${response.commitMessage} ${isValidCommit?.emoji}`; + } else { + console.log( + "Commit message does not align with the specifications of conventional commit", + ); + const response = await promptContinue(); + if (response.continue === "yes") { + updatedCommitMessage = commitMessage; + } else { + onCancel(); + } + } + return updatedCommitMessage; +}; + +export default showCommitMessagePrompt;