UNPKG

@lenne.tech/cli

Version:

lenne.Tech CLI: lt

432 lines (431 loc) 15.8 kB
"use strict"; 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); };