nhb-scripts
Version:
A collection of Node.js scripts to use in TypeScript & JavaScript projects
213 lines (171 loc) โข 5.39 kB
JavaScript
// 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);
});