UNPKG

nhb-scripts

Version:

A collection of Node.js scripts to use in TypeScript & JavaScript projects

213 lines (171 loc) โ€ข 5.39 kB
#!/usr/bin/env node // bin/commit.mjs // @ts-check import { intro, note, outro, select, spinner, text } from '@clack/prompts'; import chalk from 'chalk'; import { execa } from 'execa'; import semver from 'semver'; import { mimicClack, normalizeStringResult, validateStringInput, } from '../lib/clack-utils.mjs'; import { loadUserConfig } from '../lib/config-loader.mjs'; import { parsePackageJson, writeToPackageJson } from '../lib/package-json-utils.mjs'; import { runFormatter } from '../lib/prettier-formatter.mjs'; /** * * Updates version in package.json * @param {string} newVersion */ async function updateVersion(newVersion) { const pkg = parsePackageJson(); pkg.version = newVersion; await writeToPackageJson(pkg); mimicClack(chalk.green(`โœ“ Version updated to ${chalk.yellowBright(newVersion)}`)); } /** * * Git commit and push with message * @param {string} message Commit message * @param {string} version Version string */ export async function commitAndPush(message, version) { const s = spinner(); s.start(chalk.blue('๐Ÿ“ค Committing & pushing changes')); try { await execa('git', ['add', '.']); const { stdout: commitOut } = await execa('git', ['commit', '-m', message]); if (commitOut.trim()) { const commitLines = commitOut .split('\n') .filter(Boolean) .map((line) => chalk.cyan('โ€ข ') + line?.trim()) .join('\n'); note(commitLines, chalk.magenta('โœ“ Commit Summary')); } const { stdout, stderr } = await execa('git', ['push', '--verbose']); const pushOut = (stdout + '\n' + stderr)?.trim(); if (pushOut) { const lines = pushOut ?.split('\n') .filter(Boolean) .map((line) => chalk.cyan('โ€ข ') + line?.trim()) .join('\n'); note(lines, chalk.magenta('โœ“ Git Summary')); } s.stop( chalk.green('โœ… Changes are committed and pushed to the remote repository!') ); outro(chalk.green(`๐Ÿš€ Version ${version} pushed with message: "${message}"`)); } catch (err) { s.stop(chalk.red('๐Ÿ›‘ Commit or push failed!')); console.error(chalk.red(err)); process.exit(0); } } /** * @param {string} newVersion * @param {string} currentVersion */ function isValidVersion(newVersion, currentVersion) { if (newVersion === currentVersion) return true; return semver.valid(newVersion) && semver.gte(newVersion, currentVersion); } /** Prompt flow */ async function finalPush() { intro(chalk.cyan('๐Ÿš€ Commit & Push')); const pkg = parsePackageJson(); const oldVersion = pkg.version || '0.0.0'; const config = (await loadUserConfig()).commit ?? {}; mimicClack(`Current version: ${chalk.yellow(oldVersion)}`); let version = ''; while (true) { const input = normalizeStringResult( await text({ message: `${chalk.cyanBright.bold('Enter new version (press enter to skip):')}`, placeholder: oldVersion, defaultValue: oldVersion, initialValue: oldVersion, }) ); version = (input || '').trim(); if (!version) { version = oldVersion; mimicClack( chalk.cyanBright(`๐Ÿ”„๏ธ Using previous version: ${chalk.yellow(version)}`) ); break; } if (!isValidVersion(version, oldVersion)) { mimicClack( chalk.red('๐Ÿ›‘ Invalid or older version. Use valid semver like 1.2.3') ); continue; } mimicClack(chalk.green(`โœ” Selected version: ${chalk.yellowBright(version)}`)); break; } const typeChoices = [ { value: 'update', label: '๐Ÿ”ง update (default)' }, { value: 'feat', label: 'โœจ feat' }, { value: 'fix', label: '๐Ÿ› fix' }, { value: 'chore', label: '๐Ÿ› ๏ธ chore' }, { value: 'refactor', label: '๐Ÿงผ refactor' }, { value: 'test', label: '๐Ÿงช test' }, { value: 'docs', label: '๐Ÿ“š docs' }, { value: 'style', label: '๐Ÿ’… style' }, { value: 'perf', label: 'โšก perf' }, { value: 'revert', label: '๐Ÿ” revert' }, { value: 'build', label: '๐Ÿงฑ build' }, { value: 'ci', label: '๐Ÿš€ ci' }, { value: 'release', label: '๐Ÿ”– release' }, { value: 'deps', label: '๐Ÿ“ฆ deps' }, { value: 'cleanup', label: '๐Ÿงน cleanup' }, { value: 'merge', label: '๐Ÿงญ merge' }, { value: '__custom__', label: 'โœ Custom...' }, ]; const typeResult = normalizeStringResult( await select({ message: chalk.cyan('Select commit type:'), options: typeChoices, }) ); let finalType = typeResult; if (typeResult === '__custom__') { const customType = normalizeStringResult( await text({ message: chalk.magenta('Enter custom commit type:'), validate: validateStringInput, }) ); finalType = customType; } const scopeResult = normalizeStringResult( await text({ message: chalk.gray('Enter a scope (optional):'), placeholder: 'e.g. api, ui, auth', }) ); const messageResult = normalizeStringResult( await text({ message: chalk.cyan('Enter commit message (required):'), placeholder: 'e.g. added new feature, fixed bug in auth module etc.', validate: validateStringInput, }) ); console.log(chalk.gray('โ”‚')); const formattedMessage = scopeResult ? `${finalType}(${scopeResult}): ${messageResult}` : `${finalType}: ${messageResult}`; if (version !== oldVersion) { await updateVersion(version); } if (config.runFormatter) { await runFormatter(); } await commitAndPush(formattedMessage, version); } finalPush().catch((err) => { console.error(chalk.red('๐Ÿ›‘ Unexpected Error:'), err); process.exit(0); });