@lenne.tech/cli
Version:
lenne.Tech CLI: lt
432 lines (431 loc) • 15.8 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Git = void 0;
/**
* Git functions
*/
class Git {
/**
* Constructor for integration of toolbox
*/
constructor(toolbox) {
this.toolbox = toolbox;
/**
* Cached result of gitInstalled check
*/
this.gitInstalledCache = null;
}
/**
* Ask for reset
*/
askForReset() {
return __awaiter(this, arguments, void 0, function* (options = {}) {
// Process options
const opts = Object.assign({
errorMessage: 'Please commit or stash changes!',
showError: false,
text: 'There are changes, reset?',
}, options);
// Toolbox features
const { print: { error }, prompt, system, } = this.toolbox;
// Check changes in current branch
const changes = yield system.run('git status --porcelain');
if (changes) {
const reset = yield prompt.confirm(opts.text);
if (!reset) {
if (opts.showError) {
error(opts.errorMessage);
}
return false;
}
yield system.run('git reset --hard && git clean -fd');
}
// Return changes
return true;
});
}
/**
* Check if current branch has changes
*/
changes(options) {
return __awaiter(this, void 0, void 0, function* () {
// Process options
const opts = Object.assign({
errorMessage: 'Please commit or stash changes!',
showError: false,
}, options);
// Toolbox features
const { print: { error }, system, } = this.toolbox;
// Check changes
const changes = yield system.run('git status --porcelain');
if (changes && opts.showError) {
error(opts.errorMessage);
}
return changes;
});
}
/**
* Get current branch
*/
currentBranch() {
return __awaiter(this, void 0, void 0, function* () {
// Toolbox features
const { helper: { trim }, system, } = this.toolbox;
return trim(yield system.run('git rev-parse --abbrev-ref HEAD'));
});
}
/**
* Get all relative files paths of files that differ between local branch and origin / other branch
*/
diffFiles(branch, options) {
return __awaiter(this, void 0, void 0, function* () {
// Process options
const opts = Object.assign({
noDiffResult: null,
otherBranch: `origin/${branch}`,
showWarning: false,
}, options);
// Toolbox features
const { print: { warning }, system, } = this.toolbox;
// Get diff
try {
const diff = yield system.run(`git --no-pager diff --name-only ${branch} ${opts.otherBranch}`);
// Return relative file paths as array
return diff.split(/\r?\n/).filter((item) => item);
}
catch (error) {
if (opts.showWarning) {
warning('Branch diff could not be performed!');
}
return opts.noDiffResult;
}
});
}
/**
* Get branches
*/
getBranches() {
return __awaiter(this, void 0, void 0, function* () {
// Prepare results
const result = [];
// Toolbox features
const { system } = this.toolbox;
// Get branches (use short SSH timeout so fetch doesn't hang in offline environments)
const branches = yield system.run('GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="ssh -o ConnectTimeout=5 -o BatchMode=yes" git fetch 2>/dev/null; git show-branch --list');
branches.split('\n').forEach((item) => {
const matches = item.match(/\[(.*?)]/);
if (matches) {
result.push(matches[1]);
}
});
// Return result
return result;
});
}
/**
* Get merge base
*/
getMergeBase() {
return __awaiter(this, arguments, void 0, function* (baseBranch = 'dev') {
// Toolbox features
const { helper: { trim }, system: { run }, } = this.toolbox;
return trim(yield run(`git merge-base HEAD ${baseBranch}`));
});
}
/**
* Get first commit messages from branch
*/
getFirstBranchCommit(branch, baseBranch) {
return __awaiter(this, void 0, void 0, function* () {
if (!baseBranch) {
baseBranch = (yield this.getDefaultBranch()) || 'dev';
}
if (!branch) {
throw new Error('Missing branch');
}
// Toolbox features
const { helper: { trim }, system: { run }, } = this.toolbox;
try {
const logCommand = `git log ${baseBranch}..${branch} --oneline`;
const output = yield run(logCommand);
if (!output) {
throw new Error('No commits found');
}
const commits = output.split('\n').filter((line) => line.trim());
const firstCommit = commits[commits.length - 1];
const messageStart = firstCommit.indexOf(' ');
return trim(firstCommit.slice(messageStart + 1));
}
catch (error) {
throw new Error(`Failed to get first commit message: ${error.message}`);
}
});
}
/**
* Get default branch
*/
getDefaultBranch() {
// Toolbox features
const { system: { run }, } = this.toolbox;
return run('basename $(git symbolic-ref --short refs/remotes/origin/HEAD)');
}
/**
* Get git user
*/
getUser() {
return __awaiter(this, void 0, void 0, function* () {
// Toolbox features
const { helper: { trim }, system: { run }, } = this.toolbox;
// Get data
const user = {
email: trim(yield run('git config user.email')),
name: trim(yield run('git config user.name')),
};
// Return user
return user;
});
}
/**
* Check if git is installed (cached for performance)
*/
gitInstalled() {
return __awaiter(this, void 0, void 0, function* () {
// Return cached result if available
if (this.gitInstalledCache !== null) {
if (!this.gitInstalledCache) {
const { print: { error }, } = this.toolbox;
error('Please install git: https://git-scm.com');
}
return this.gitInstalledCache;
}
// Toolbox features
const { print: { error }, system, } = this.toolbox;
const gitInstalled = !!system.which('git');
this.gitInstalledCache = gitInstalled;
if (!gitInstalled) {
error('Please install git: https://git-scm.com');
return false;
}
return true;
});
}
/**
* Clear the gitInstalled cache (useful for testing)
*/
clearCache() {
this.gitInstalledCache = null;
}
/**
* Get name of a branch
*/
getBranch(branch_1) {
return __awaiter(this, arguments, void 0, function* (branch, options = {}) {
var _a;
// Check branch
if (!branch) {
return;
}
// Process options
const opts = Object.assign({
error: false,
errorText: `Branch ${branch} not found!`,
exact: true,
local: false,
remote: false,
spin: false,
spinText: 'Search branch',
}, options);
// Toolbox features
const { helper: { trim }, print: { error, info, spin }, system, } = this.toolbox;
// Prepare spinner
let searchSpin;
if (opts.spin) {
searchSpin = spin(opts.spinText);
}
// Update infos (use short SSH timeout so fetch doesn't hang in offline environments)
const fetch = yield system.run('GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="ssh -o ConnectTimeout=5 -o BatchMode=yes" git fetch 2>/dev/null || true');
if (fetch.length && !fetch.startsWith('remote')) {
info(`Could not update infos ${fetch.length}`);
}
// Search branch
if (opts.exact) {
if (opts.remote) {
if (!(yield system.run(`git ls-remote --heads origin ${branch}`))) {
branch = null;
}
}
else {
try {
yield system.run(`git rev-parse --verify ${branch}`);
}
catch (e) {
branch = null;
}
}
}
else {
branch = (_a = (yield system.run('git branch -a'))
.split(/\r?\n/)
.map((line) => line.trim())
.find((line) => line.includes(branch))) === null || _a === void 0 ? void 0 : _a.replace(/^.*origin\//, '').replace(/^.*github\//, '').replace(/^\* /, '').trim();
}
if (!branch) {
if (opts.spin) {
searchSpin.fail();
}
if (opts.error) {
error(opts.errorText);
}
return;
}
// Trim branch
branch = trim(branch);
// Check remote, if not done before
if (opts.remote && !opts.exact) {
const remoteBranch = trim(yield system.run(`git ls-remote --heads origin ${branch}`));
if (!remoteBranch) {
if (opts.spin) {
searchSpin.fail();
}
if (opts.error) {
error(opts.errorText);
}
return;
}
}
// Check local
if (opts.local) {
const remoteBranch = trim(yield system.run(`git rev-parse --verify --quiet ${branch}`));
if (!remoteBranch) {
if (opts.spin) {
searchSpin.fail();
}
if (opts.error) {
error(opts.errorText);
}
return;
}
}
// End spinner
if (opts.spin) {
searchSpin.succeed();
}
// Return branch name
return branch;
});
}
/**
* Get last commit from current branch
*/
lastCommitMessage() {
return __awaiter(this, void 0, void 0, function* () {
// Toolbox features
const { helper: { trim }, system: { run }, } = this.toolbox;
return trim(yield run('git show-branch --no-name HEAD'));
});
}
/**
* Reset branch
*/
reset(mergeBase, soft = false) {
// Toolbox features
const { system: { run }, } = this.toolbox;
return run(soft ? `git reset --soft ${mergeBase}` : `git reset ${mergeBase}`);
}
/**
* Select a branch
*/
selectBranch() {
return __awaiter(this, arguments, void 0, function* (options = {}) {
// Process options
const opts = Object.assign({
defaultBranch: 'dev',
text: 'Select branch',
}, options);
// Toolbox features
const { prompt: { ask }, } = this.toolbox;
// Get branches
let branches = yield this.getBranches();
if (!branches || branches.length === 0) {
return;
}
// Check default branch
if (!branches.includes(opts.defaultBranch) && branches.includes('main')) {
opts.defaultBranch = 'main';
}
// Prepare branches
if (branches.includes(opts.defaultBranch)) {
branches = [opts.defaultBranch].concat(branches.filter((item) => item !== opts.defaultBranch));
}
// Select branch
const { branch } = yield ask({
choices: branches,
message: opts.text,
name: 'branch',
type: 'select',
});
// Return selected branch
return branch;
});
}
/**
* Get status
*/
status() {
// Toolbox features
const { system: { run }, } = this.toolbox;
return run('git status');
}
/**
* Display dry-run information for git operations
* Shows what changes would be affected without making changes
*
* @param options - Configuration options
* @returns Formatted dry-run result string or null if no changes
*/
showDryRunInfo(options) {
return __awaiter(this, void 0, void 0, function* () {
const { branch, operation } = options;
const { print: { info, warning }, system: { run }, } = this.toolbox;
warning('DRY-RUN MODE - No changes will be made');
info('');
const status = yield run('git status --porcelain');
if (!(status === null || status === void 0 ? void 0 : status.trim())) {
info('No changes to process.');
return null;
}
const lines = status.trim().split('\n');
const modified = lines.filter((l) => l.startsWith(' M') || l.startsWith('M ')).length;
const added = lines.filter((l) => l.startsWith('A ') || l.startsWith('??')).length;
const deleted = lines.filter((l) => l.startsWith(' D') || l.startsWith('D ')).length;
const branchInfo = branch ? ` on branch "${branch}"` : '';
info(`Would ${operation}${branchInfo}:`);
if (modified > 0)
info(` - ${modified} modified file(s)`);
if (added > 0)
info(` - ${added} untracked/added file(s)`);
if (deleted > 0)
info(` - ${deleted} deleted file(s)`);
info('');
info('Files:');
lines.forEach((line) => info(` ${line}`));
return `dry-run ${operation} ${branch || ''}`.trim();
});
}
}
exports.Git = Git;
/**
* Extend toolbox
*/
exports.default = (toolbox) => {
toolbox.git = new Git(toolbox);
};