UNPKG

sexy-commits

Version:

Sexy commits using gitmoji.dev and inquirer-autocomplete-prompt

289 lines (270 loc) 7.31 kB
#!/usr/bin/env node 'use strict' require('dotenv').config() const inquirer = require('inquirer') inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')) const { promisify } = require('util') const chalk = require('chalk') const log = console.log const fs = require('fs') const path = require('path') const packageJson = require(process.env.PWD + '/package.json') const { gitmoji: defaultConfig } = require('./defaultConfig.json') const { exec } = require('child_process') const execAsync = promisify(exec) const writeFileAsync = promisify(fs.writeFile) const yargs = require('yargs/yargs') const { hideBin } = require('yargs/helpers') const argv = yargs(hideBin(process.argv)).argv const _ = require('lodash') /** * Parse argument flag * * @param {*} arg Argument to parse */ function parseArgFlag(arg) { return (arg && arg !== 'false' && arg !== '0') } /** * Parse additional args from `argv` * * @returns additional args from argv */ function parseAdditonalArgs() { const additionalArgs = _.omit(argv, ['_', '$0']) if (additionalArgs['fixesIssue'] != undefined) { additionalArgs.fixesIssue =parseArgFlag(additionalArgs.fixesIssue) } if (additionalArgs['push'] != undefined) { additionalArgs.push = parseArgFlag(additionalArgs.push) } return additionalArgs } /** * Parse args using `argv` * * @param gitmoji - Gitmoji config * * @returns `addPattern`, `commitType`, `message` aswell * as additional args from `parseAdditonalArgs` */ function parseArgs(gitmoji) { try { const types = Object.keys(gitmoji) const [addPattern, commitType, ...message_] = argv._ const message = message_.join(' ') if (!commitType || types.includes(commitType)) { return { ...parseAdditonalArgs(), addPattern, commitType, message, } } const commitType_ = types.find((key) => { const alias = gitmoji[key][2] return alias && Boolean(alias.includes(commitType)) }) if (!commitType_) { log( chalk.yellow( `Commit type ${chalk.cyan(commitType)} not found in your ${chalk.cyan( 'gitmoji' )} config 😞` ) ) process.exit(0) } return { ...parseAdditonalArgs(), addPattern, commitType: commitType_, message } } catch { log(chalk.yellow("We couldn't parse your arguments 😞")) return {} } } /** * Commit changes using arguments and prompts */ async function run() { let { gitmoji } = packageJson if (!gitmoji && fs.existsSync(path.resolve(process.cwd(), '.gitmoji.json'))) { gitmoji = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), '.gitmoji.json'), 'utf8')) } if (!gitmoji) { const { addDefaults, jsonFile } = await inquirer.prompt([ { type: 'confirm', name: 'addDefaults', message: `You don't have ${chalk.cyan( 'gitmoji' )} config in your package.json. Add defaults? 🤝`, default: true }, { type: 'confirm', name: 'jsonFile', message: `Do you want to use a ${chalk.cyan( '.gitmoji.json' )} file instead?`, when: (answers) => !answers.addDefaults } ]) if (!addDefaults && !jsonFile) { process.exit(0) } if (jsonFile) { await writeFileAsync( path.resolve(process.cwd(), '.gitmoji.json'), JSON.stringify(defaultConfig, null, 2) ) } else { const newPackageJson = { ...packageJson, gitmoji: defaultConfig } await writeFileAsync( path.resolve(process.env.PWD, 'package.json'), JSON.stringify(newPackageJson, null, 2) ) } gitmoji = defaultConfig } const types = Object.keys(gitmoji) const args = parseArgs(gitmoji) const autoPush = process.env.SEXY_COMMITS_AUTO_PUSH === '1' const issueRef = process.env.SEXY_COMMITS_ISSUE_REF const fixesIssue = process.env.SEXY_COMMITS_FIXES_ISSUE === '1' || args.fixesIssue const skipCiTag = process.env.SEXY_COMMITS_SKIP_CI_TAG const includeDetails = process.env.SEXY_COMMITS_INCLUDE_DETAILS === '1' const prompts = await inquirer.prompt([ { type: 'input', name: 'addPattern', message: 'What changes do you want to include?', default: 'all', when: !args.addPattern }, { type: 'autocomplete', name: 'commitType', message: 'What did you do?', source: async (_a, input) => { return types .filter((type) => type.toLowerCase().includes((input || '').toLowerCase()) ) .map((type) => ({ value: type, name: gitmoji[type].join('\t') })) }, when: !args.commitType }, { type: 'input', name: 'issueRef', message: 'Do you want to reference an issue in the commit message?', when: !args.issueRef && !issueRef && process.env.SEXY_COMMITS_NO_ISSUE_REF !== '1' }, { type: 'confirm', name: 'fixesIssue', message: (answers) => `Do you want to automatically close #${answers.issueRef ?? args.issueRef} when the commit is pushed?`, when: (answers) => (Boolean(answers.issueRef) || issueRef || args.issueRef) && args.fixesIssue === undefined }, { type: 'input', name: 'message', message: 'A short summary of what you changed:', when: !args.message }, { type: 'input', name: 'details', message: 'Any additional details you want to include in the commit message:', when: !args.details && includeDetails }, { type: 'confirm', name: 'skipCi', message: 'Do you want to skip CI for this commit?', default: false, when: Boolean(skipCiTag) && !args.skipCi }, { type: 'confirm', name: 'push', message: 'Do you want to push the changes right away?', default: true, when: !autoPush && args.push === undefined } ]) const mergedInput = Object.assign({ ...args, push: autoPush || args.push, issueRef: args.issueRef ?? issueRef, fixesIssue }, prompts) let commitMessage = `${mergedInput.commitType}: ${mergedInput.message.toLowerCase()}` try { if (process.env.SEXY_COMMITS_LINT_CMD) { try { console.log('') log(chalk.magenta('Linting your changes with the command specified in your .env file...')) await execAsync(process.env.SEXY_COMMITS_LINT_CMD) } catch { log(chalk.red('An error occured while linting your changes.')) } } await (mergedInput.addPattern === 'all' ? execAsync('git add --all') : execAsync(`git add "*${mergedInput.addPattern}*"`)) if (gitmoji[mergedInput.commitType]) { commitMessage += ` ${gitmoji[mergedInput.commitType][0]}` } if (mergedInput.issueRef) { if (mergedInput.fixesIssue) { commitMessage += ` (closes #${mergedInput.issueRef})` } else { commitMessage += ` #${mergedInput.issueRef}` } } if (mergedInput.skipCi) { commitMessage += ` [${skipCiTag}]` } let commitCmd = `git commit -m "${commitMessage}"` if (mergedInput.details) { commitCmd += ` -m "${mergedInput.details}"` } commitCmd += ' --no-verify' if (mergedInput.amend) { commitCmd += ' --amend' } await execAsync(commitCmd) if (mergedInput.push) { await execAsync('git pull') await execAsync('git push') if (mergedInput.tags) { await execAsync('git push --tags') } } log( chalk.cyan( `\nSuccesfully commited changes with message:\n\n${chalk.white( commitMessage )}\n` ) ) } catch { log(chalk.red('An error occuchalk.red commiting your changes.')) } finally { process.exit(0) } } run()