From 801fc72c2e1e31823b85bf551f560dd9d42b1575 Mon Sep 17 00:00:00 2001 From: ize-302 Date: Fri, 13 Sep 2024 10:12:18 +0100 Subject: [PATCH] :tada: Initial commit --- .github/actions/release.yml | 32 ++++++++++ .gitignore | 8 +++ .releaserc | 5 ++ LICENSE | 21 +++++++ README.md | 55 +++++++++++++++++ build.mjs | 7 +++ bun.lockb | Bin 0 -> 13264 bytes jsonconfig.json | 8 +++ package.json | 50 +++++++++++++++ src/cli.ts | 28 +++++++++ src/commit-types.json | 57 +++++++++++++++++ src/constants/flags.js | 8 +++ src/utils/handleCommand.js | 120 ++++++++++++++++++++++++++++++++++++ tsconfig.json | 19 ++++++ 14 files changed, 418 insertions(+) create mode 100644 .github/actions/release.yml create mode 100644 .gitignore create mode 100644 .releaserc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.mjs create mode 100755 bun.lockb create mode 100644 jsonconfig.json create mode 100644 package.json create mode 100755 src/cli.ts create mode 100644 src/commit-types.json create mode 100644 src/constants/flags.js create mode 100644 src/utils/handleCommand.js create mode 100644 tsconfig.json diff --git a/.github/actions/release.yml b/.github/actions/release.yml new file mode 100644 index 0000000..fe965de --- /dev/null +++ b/.github/actions/release.yml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + branches: + - main + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - id: setup-bun + name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - run: bun install + - run: bun run build + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e9f79d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +*.log +node_modules/ +dist/ +bin/ + +# VS Code settings +.vscode/ \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..49bdcbe --- /dev/null +++ b/.releaserc @@ -0,0 +1,5 @@ +{ + "branches": [ + "main" + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..980bada --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Adavize Hassan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..be3154b --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# gitmo + +> A cli tool that adds appropriate emoji to your commit messages based on conventional commits specification + +## About + +This cli was built as a simpler alternative to [gitmoji-cli](https://github.com/carloscuesta/gitmoji-cli). There is no step to pick an emoji, we simply determine the appropriate emoji to use based on your commit message and include it in your message + +## Install + +### npm + +```bash +npm i -g gitmo +``` + +## Usage + +```bash +gitmo --help +``` + +``` + An 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 +``` + +## How to commit + +```bash +# Note: This should be done after staging your changes +gitmo -c +``` + +You get this prompt: + +``` +? commit message › ENTER COMMIT MESSAGE HERE +``` + +--- + +### Resources used + +- [Conventional commit types](https://github.com/pvdlg/conventional-commit-types) +- [gitmoji-cli](https://github.com/carloscuesta/gitmoji-cli) diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..d2d8911 --- /dev/null +++ b/build.mjs @@ -0,0 +1,7 @@ +await Bun.build({ + target: 'node', + entrypoints: ['./src/cli.ts'], + outdir: './dist', + minify: true, + external: ['shelljs'], +}) \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..57f3c87f24a32785fb94d330be0cdd643eee2aad GIT binary patch literal 13264 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" + }, + "files": [ + "dist" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/ize-302/gitmo.git" + }, + "keywords": [ + "emoji", + "conventional commit" + ], + "author": { + "name": "ize-302", + "email": "adavize302@gmail.com", + "url": "https://ize-302.dev" + }, + "bugs": { + "url": "https://github.com/ize-302/gitmo/issues" + }, + "homepage": "https://github.com/ize-302/gitmo", + "dependencies": { + "meow": "^13.2.0", + "prompts": "^2.4.2", + "shelljs": "^0.8.5" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/prompts": "^2.4.9", + "@types/shelljs": "^0.8.15", + "typescript": "^5.6.2" + }, + "module": "index.ts" +} diff --git a/src/cli.ts b/src/cli.ts new file mode 100755 index 0000000..079f247 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,28 @@ +#!/usr/bin/env node +import meow from "meow"; +import FLAGS from "@/constants/flags.js"; +import { handleCommand } from "@/utils/handleCommand.js"; + +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/commit-types.json b/src/commit-types.json new file mode 100644 index 0000000..19a8a23 --- /dev/null +++ b/src/commit-types.json @@ -0,0 +1,57 @@ +[ + { + "name": "feat", + "emoji": "✨", + "description": "A new feature" + }, + { + "name": "fix", + "emoji": "🐛", + "description": "A bug Fix" + }, + { + "name": "docs", + "emoji": "📚", + "description": "Documentation only changes" + }, + { + "name": "style", + "emoji": "💄", + "description": "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)" + }, + { + "name": "refactor", + "emoji": "📦", + "description": "A code change that neither fixes a bug nor adds a feature" + }, + { + "name": "perf", + "emoji": "🚀", + "description": "A code change that improves performance" + }, + { + "name": "test", + "emoji": "🚨", + "description": "Adding missing tests or correcting existing tests" + }, + { + "name": "ci", + "emoji": "⚙️", + "description": "Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)" + }, + { + "name": "build", + "emoji": "🛠", + "description": "Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)" + }, + { + "name": "chore", + "emoji": "♻️", + "description": "Other changes that don't modify src or test files" + }, + { + "name": "revert", + "emoji": "🗑", + "description": "Reverts a previous commit" + } +] diff --git a/src/constants/flags.js b/src/constants/flags.js new file mode 100644 index 0000000..9b68ffa --- /dev/null +++ b/src/constants/flags.js @@ -0,0 +1,8 @@ +const FLAGS = Object.freeze({ + COMMIT: 'commit', + HELP: 'help', + UPDATE: 'update', + VERSION: 'version', +}) + +export default FLAGS \ No newline at end of file diff --git a/src/utils/handleCommand.js b/src/utils/handleCommand.js new file mode 100644 index 0000000..0e25afb --- /dev/null +++ b/src/utils/handleCommand.js @@ -0,0 +1,120 @@ +import prompts from "prompts"; +import FLAGS from "@/constants/flags.js"; +import commitTypes from "@/commit-types.json"; +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); + } else { + return false; + } +}; + +const showCommitPrompt = async () => { + 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 = ctype?.emoji + " " + response.commitMessage; + 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) { + console.log("update cli"); + } else if (cli.flags.commit) { + showCommitPrompt(); + } +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..27dbfd7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "node16", + "moduleResolution": "node16", + "baseUrl": "./", + "paths": { + "@/*": ["./src/*"] + }, + "resolveJsonModule": true, + "allowJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "outDir": "dist" + } +}