UNPKG

gitnifty

Version:

A robust, promise-based Git utility for Node.js

674 lines (673 loc) 26 kB
/** * Options for configuring the {@link Git} class. * * @interface GitOptions * * @see {@link Git} - Clever Git, Made Simple */ export interface GitOptions { /** * The working directory for Git commands. Defaults to the current process directory. */ cwd?: string; } /** * Defines the available flags for the `git reset` command. * * These flags control how Git modifies the index and working directory * when resetting to a specific commit. * * - `--soft`: Moves HEAD and keeps all changes staged. * - `--mixed`: Resets index but not the working directory (default). * - `--hard`: Resets index and working directory (⚠ destructive). * - `--merge`: Resets while preserving uncommitted merge changes. * - `--keep`: Similar to merge, but keeps local modifications. * - `--quiet`: Suppresses output messages during reset. * - `--verbose`: Outputs additional information during reset. * * @see {@link Git.reset} */ export type ResetFlag = "--soft" | "--mixed" | "--hard" | "--merge" | "--keep" | "--quiet" | "--verbose"; /** * Represents valid flags for the `git restore` command. * * These flags control what is restored and from where. Useful for staging, * restoring from a specific commit, or restoring only to the working tree. * * - `--staged`: Restore changes to the index (unstages files). * - `--worktree`: Restore only to the working directory. * - `--quiet`: Suppress feedback messages. * - `--progress`: Force progress reporting. * - `--source=<commit-ish>` – Restore from a specific commit, branch, or tag. * * @example * - `--source=HEAD` – Restore from the latest commit on current branch. * - `--source=HEAD~1` – Restore from one commit before HEAD. * - `--source=abc1234` – Restore from a specific commit hash. * - `--source=main` – Restore from a named branch. * * @see {@link Git.restore} */ export type RestoreFlag = "--staged" | "--worktree" | "--quiet" | "--progress" | "--source=HEAD" | `--source=HEAD~${number}` | `--source=${string}`; /** * Represents valid flags for the `git commit` command. * * These flags modify commit behavior, such as bypassing hooks, * allowing empty commits, or including staged changes only. * * - `--amend`: Modify the last commit. * - `--no-edit`: Reuse the previous commit message. * - `--allow-empty`: Create a commit even if there are no changes. * - `--no-verify`: Skip pre-commit and commit-msg hooks. * - `--signoff`: Add a Signed-off-by line at the end of the commit message. * - `--verbose`: Show unified diff in the commit message editor. * * @see {@link Git.commit} */ export type CommitFlag = "--amend" | "--no-edit" | "--allow-empty" | "--no-verify" | "--signoff" | "--verbose"; /** * Flags available for the `git push` command. * * These flags modify the behavior of pushing commits to a remote repository. * * - `--force`: Forces the push, overwriting remote history. * - `--force-with-lease`: Forces push only if remote hasn’t been updated. * - `--tags`: Push all tags to the remote. * - `--follow-tags`: Push annotated tags associated with commits. * - `--set-upstream`: Sets upstream (tracking) for the branch. * - `--dry-run`: Simulates push without making changes. * - `--delete`: Deletes the specified branch from the remote. * - `--all`: Pushes all branches. * * @see {@link Git.push} */ export type PushFlag = "--force" | "--force-with-lease" | "--tags" | "--follow-tags" | "--set-upstream" | "--dry-run" | "--delete" | "--all"; /** * Flags available for the `git tag` command. * * These flags control tag creation, deletion, listing, and annotation. * * - `--annotate`: Creates an annotated tag with metadata. * - `--delete`: Deletes a tag. * - `--force`: Replaces an existing tag with the same name. * - `--list`: Lists all tags. * * @see {@link Git.tag} */ export type TagFlag = "--annotate" | "--delete" | "--force" | "--list"; /** * Flags available for the `git merge` command. * * These flags control how Git performs the merge operation. * * - `--no-ff`: Disables fast-forward merges. * - `--ff-only`: Only allows fast-forward merges. * - `--squash`: Combines changes into a single commit without a merge. * - `--no-commit`: Prevents an automatic commit after merge. * - `--commit`: Forces a commit if merge is successful. * - `--edit`: Opens the commit message editor after merging. * - `--no-edit`: Uses the default commit message without editing. * - `--strategy=ours`: Uses 'ours' strategy to favor current branch. * - `--strategy=recursive`: Uses the default recursive merge strategy. * * @see {@link Git.merge} */ export type MergeFlag = "--no-ff" | "--ff-only" | "--squash" | "--no-commit" | "--commit" | "--edit" | "--no-edit" | "--strategy=ours" | "--strategy=recursive"; /** * Flags available for the `git checkout` command. * * These flags control checkout behavior including branch creation. * * - `-b`: Creates a new branch and checks it out. * - `-B`: Creates or resets a branch. * - `--detach`: Detaches HEAD to checkout a commit. * - `--force`: Forces checkout, discarding local changes. * - `--orphan`: Creates a new branch with no commit history. * * @see {@link Git.checkout} */ export type CheckoutFlag = "-b" | "-B" | "--detach" | "--force" | "--orphan"; /** * Flags available for the `git branch` command. * * These flags allow listing, deleting, renaming, and inspecting branches. * * - `-d`: Deletes a branch (safe, only if fully merged). * - `-D`: Deletes a branch forcefully (even if not merged). * - `-m`: Renames a branch. * - `-M`: Forcefully renames a branch (even if target exists). * - `--list`: Lists all branches. * - `--show-current`: Shows the name of the current branch. * * @see {@link Git.branch} */ export type BranchFlag = "-d" | "-D" | "-m" | "-M" | "--list" | "--show-current"; /** * Flags used with the `git describe` command to modify its behavior. * * These control how Git generates the description of a commit. * * - `--tags`: Use any tag (including lightweight tags). * - `--all`: Use any ref (tags, branches, etc.). * - `--long`: Always output long format (tag + number of commits + hash). * - `--dirty`: Mark the working tree as dirty if it has local changes. * - `--exact-match`: Only output the tag if the commit matches exactly. * - `--always`: Fallback to abbreviated commit hash if no tag is found. * - `--first-parent`: Follow only the first parent upon traversal. * - `--dirty=<mark>`: Use a custom suffix instead of "-dirty". * - `--abbrev=<n>`: Set the hash abbreviation length. * - `--match=<pattern>`: Only consider tags matching the given glob pattern. * - `--candidates=<n>`: Limit the number of candidate tags considered. * * @see {@link Git.describe} */ export type DescribeFlag = "--tags" | "--all" | "--long" | "--dirty" | "--exact-match" | "--always" | "--first-parent" | `--dirty=${string}` | `--abbrev=${number}` | `--match=${string}` | `--candidates=${number}`; /** * A Git reference to identify a specific commit, branch, or tag. * * This can be used to describe a commit other than the current `HEAD`. * * Common examples: * - `"HEAD"`: The current commit. * - `"HEAD~1"`: One commit before HEAD. * - `"HEAD^2"`: Second parent of a merge commit. * - `"main"` or any branch name. * - A full or abbreviated commit SHA. */ export type GitRef = "HEAD" | `${"HEAD~" | "HEAD^"}${number}` | string; /** * A smart, user-friendly Git utility class for Node.js CLIs. * * `Git` provides a high-level interface for interacting with Git repositories using simple, intuitive commands. * Designed for use in GitNifty, it wraps common Git operations like checking repository status, retrieving user info, * detecting upstream branches, and more — all with clean automation and helpful defaults. * * This class is built to make version control effortless for developers who want precision and productivity, * without dealing with complex Git shell commands directly. * * @example * ```ts * import { Git } from "./gitnifty"; * * const git = new Git({ cwd: "/path/to/repo" }); * const username = await git.getUserName(); * const branch = await git.getCurrentBranchName(); * const isClean = await git.isWorkingDirClean(); * ``` * * @remarks * - Built for Node.js CLI tools like GitNifty. * - Uses `child_process.exec` under the hood. * - Handles common Git tasks with automation-friendly methods. * - Falls back gracefully on errors, e.g., `hasUpstreamBranch()` or `isWorkingDirClean()` return `false` instead of throwing. * * @see {@link GitOptions} - Options for configuring the Git class. * @see {@link https://git-scm.com/docs | Git Official Documentation} */ export declare class Git { /** * The current working directory for Git commands. * * @private * */ private cwd; /** * Creates an instance of the Git class. * * @param options - Configuration options for the Git instance. * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * ``` */ constructor(options: GitOptions); /** * Executes a Git command and handles errors gracefully. * * @private * * @template T - The expected return type of the command. * * @param cmd - The Git command to execute. * * @returns A promise that resolves to `true` if the command succeeds, `false` otherwise. */ private tryCommand; /** * Runs a Git command in the specified working directory. * * @private * * @param cmd - The Git command to execute. * * @returns A promise that resolves with the command's stdout. * * @throws Throws an error with the command and stderr if execution fails. */ private runCommand; /** * Normalizes a value into an array. * * @example * toArray("--tags") // ["--tags"] * toArray(["--tags", "--always"]) // ["--tags", "--always"] * * @param input - A single value or an array of values. * @returns The input wrapped in an array, if not already. */ private toArray; /** * Retrieves the configured Git user name. * * @returns A promise that resolves with the user's name. * * @throws Throws an error if the command fails (e.g., Git not installed or user not configured). * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * const username = await git.getUserName(); * console.log(username); // "John Doe" * ``` */ getUserName(): Promise<string>; /** * Sets the global Git user name. * * This command configures the `user.name` value in the global Git configuration. * It wraps `git config --global user.name "<name>"`. * * @param name - The Git user name to set. If it includes spaces, it will be quoted automatically. * * @returns A promise that resolves when the name has been successfully set. * * @example * ```ts * await git.setUserName("John Doe"); * const name = await git.getUserName(); * console.log(name); // "John Doe" * ``` */ setUserName(name: string): Promise<string>; /** * Retrieves the configured Git user email. * * @returns A promise that resolves with the user's email. * * @throws Throws an error if the command fails (e.g., Git not installed or email not configured). * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * const email = await git.getUserEmail(); * console.log(email); // "john.doe@example.com" * ``` */ getUserEmail(): Promise<string>; /** * Sets the global Git user email. * * This command configures the `user.email` value in the global Git configuration. * It wraps `git config --global user.email "<email>"`. * * @param email - The Git user email address to set. * * @returns A promise that resolves when the email has been successfully set. * * @example * ```ts * await git.setUserEmail("john.doe@example.com"); * const email = await git.getUserEmail(); * console.log(email); // "john.doe@example.com" * ``` */ setUserEmail(email: string): Promise<string>; /** * Checks if there are no **unstaged** changes in the working directory. * * @returns A promise that resolves to `true` if there are no unstaged changes, otherwise `false`. * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * const clean = await git.hasNoUnstagedChanges(); * console.log(clean); // true if working directory has no unstaged changes * ``` */ hasNoUnstagedChanges(): Promise<boolean>; /** * Checks if there are no **staged but uncommitted** changes. * * @returns A promise that resolves to `true` if there are no staged changes, otherwise `false`. * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * const clean = await git.hasNoStagedChanges(); * console.log(clean); // true if nothing is staged for commit * ``` */ hasNoStagedChanges(): Promise<boolean>; /** * Checks if the working directory is completely clean i.e., no staged or unstaged changes. * * @returns A promise that resolves to `true` if the working directory is fully clean, otherwise `false`. * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * const isClean = await git.isWorkingDirClean(); * console.log(isClean); // true if no changes, false if dirty * ``` * * @see {@link hasNoUnstagedChanges} To check if working directory has unstaged changes. * @see {@link hasNoStagedChanges} To check if working directory has staged but uncommitted changes. */ isWorkingDirClean(): Promise<boolean>; /** * Checks if the current branch has an upstream branch configured. * * @returns A promise that resolves to `true` if an upstream branch is set, `false` otherwise. * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * const hasUpstream = await git.hasUpstreamBranch(); * console.log(hasUpstream); // true (if upstream is set), false (if not) * ``` */ hasUpstreamBranch(): Promise<boolean>; /** * Retrieves the name of the current branch. * * @returns A promise that resolves with the current branch name. * * @throws Throws an error if the command fails (e.g., not a Git repository). * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * const branch = await git.getCurrentBranchName(); * console.log(branch); // "main" * ``` */ getCurrentBranchName(): Promise<string>; /** * Retrieves the default branch name of the repository (e.g., `main` or `master`). * * Falls back to "main" if the default branch cannot be determined. * * @returns A promise that resolves with the default branch name. * * @throws Throws an error if the command fails (e.g., not a Git repository). * * @example * ```ts * const git = new Git({ cwd: "/path/to/repo" }); * const defaultBranch = await git.getDefaultBranchName(); * console.log(defaultBranch); // "main" or "master" * ``` */ getDefaultBranchName: () => Promise<string>; /** * Stages one or more files or directories for the next commit. * * This method wraps `git add` to prepare specified files or directories for commit. * You can provide a single path or an array of paths. By default, it stages all changes. * * @param path - The file(s) or directory path(s) to stage. * Use `"."` to stage all changes. If an array is provided, all listed paths will be staged. * * @returns A promise that resolves with the command's stdout if successful. * * @throws Throws an error with stderr if the command fails (e.g., invalid path). * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * await git.add("README.md"); // stages a single file * await git.add(["src/", "docs/"]); // stages multiple directories * await git.add(); // stages everything (default ".") * ``` */ add(path?: string | string[]): Promise<string>; /** * Resets the current HEAD to the specified commit hash, with an optional behavior flag. * * This method wraps `git reset <hash> <flag>` and moves the current branch pointer to the given commit. * It does not modify the working directory or the index unless additional flags (e.g., `--hard`, `--soft`) are added manually. * * @param hashValue - The target commit hash to reset to. * This must be a valid Git commit SHA (full or abbreviated). * * @param flag - One or more Git reset flags. * Examples: `"--soft"`, `"--hard"`. * * * @returns A promise that resolves with the command's stdout if the reset succeeds. * * @throws Throws an error if the command fails (e.g., invalid hash or detached HEAD issues). * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * await git.reset("abc1234"); // Moves HEAD to commit abc1234 * await git.reset("abc1234", "--hard"); // Hard reset to commit * ``` */ reset(hashValue: string, flag?: ResetFlag): Promise<string>; /** * Restores working tree files from the index or a specified source. * * This method wraps `git restore` to unstage files or restore their contents * from the index (staged) or from a specific commit/branch. * * @param target - One or more file paths to restore. * Use `"."` to restore all tracked files. When using an array, all paths will be included. * * @param flag - An optional Git restore flag like `--staged` or `--source=<commit>`. * Use this to restore from a specific source or unstage changes. * * @returns A promise that resolves with the command's output on success. * * @throws Throws an error if the command fails (e.g., invalid path or ref). * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * await git.restore("README.md"); // Restore file from index * await git.restore(".", "--staged"); // Unstage all changes * await git.restore(["src/", "docs/"], "--source=HEAD~1"); // Restore from previous commit * ``` * * @see {@link https://git-scm.com/docs/git-restore | git restore - Official Git Docs} */ restore(target?: string | string[], flag?: RestoreFlag): Promise<string>; /** * Commits staged changes to the repository with a custom message and optional flags. * * This method wraps `git commit -m "<message>"` with support for additional commit flags. * It safely escapes double quotes in the commit message to avoid shell issues. * * @param message - The commit message to use. Will be wrapped in quotes and escaped. * @param flags - Optional list of commit flags to customize the commit behavior. * Each flag must be a valid `CommitFlag` value. * * @returns A promise that resolves with the command's stdout if the commit succeeds. * * @throws Throws an error if the commit fails (e.g., nothing staged, invalid flags). * * @example * ```ts * const git = new Git({ cwd: "/repo" }); * await git.commit("feat: add login API"); * await git.commit("fix: typo", ["--amend", "--no-edit"]); * ``` * * @see {@link CommitFlag} for supported commit flags * @see {@link https://git-scm.com/docs/git-commit Git Commit Docs} */ commit(message: string, flags?: CommitFlag | CommitFlag[]): Promise<this>; /** * Initializes a new Git repository in the working directory. * * @returns A promise that resolves to the Git instance. * * @example * ```ts * await git.init(); * await git.clone("https://github.com/repo.git"); * ``` * * @see {@link https://git-scm.com/docs/git-init Git Init Docs} */ init(): Promise<this>; /** * Clones a Git repository into the current directory or specified folder. * * @param url - The Git repository URL to clone. * @param dir - Optional directory to clone into. * * @returns A promise that resolves to the Git instance. * * @example * ```ts * await git.clone("https://github.com/user/repo.git", "my-folder"); * ``` * * @see {@link https://git-scm.com/docs/git-clone Git Clone Docs} */ clone(url: string, dir?: string): Promise<this>; /** * Pushes changes to the specified remote and optionally to a specific branch. * * @param remote The remote name to push to. Defaults to `"origin"`. * @param branch The branch name to push. If not provided, pushes all matching branches. * @param flags Optional push flags to customize behavior. Can be a single flag or array. * * @returns A promise that resolves with the result of the Git command. * * @example * ```ts * git.push(); // git push origin * git.push("origin", "main", "--force"); // git push --force origin main * git.push("origin", "dev", ["--tags", "--set-upstream"]); // git push --tags --set-upstream origin dev * ``` * * @see {@link https://git-scm.com/docs/git-push Git Push Docs} */ push(remote?: string | PushFlag[], branch?: string, flags?: PushFlag | PushFlag[]): Promise<this>; /** * Creates, deletes, or lists Git tags with optional flags. * * @param value The tag name (or value depending on flags). * @param flags Optional tag flags like `--annotate`, `--delete`, etc. * * @returns A promise that resolves with the result of the Git command. * * @example * ```ts * git.tag("v1.0.0"); // git tag v1.0.0 * git.tag("v1.0.0", "--delete"); // git tag --delete v1.0.0 * git.tag("v1.0.0", ["--annotate", "--force"]); // git tag --annotate --force v1.0.0 * ``` * * @see {@link https://git-scm.com/docs/git-tag Git Tag Docs} */ tag(value: string, flags?: TagFlag | TagFlag[]): Promise<string>; /** * Merges the specified branch into the current branch. * * @param branchName The name of the branch to merge. * @param flags Optional merge flags like `--no-ff`, `--squash`, etc. * * @returns A promise that resolves with the result of the Git command. * * @example * ```ts * git.merge("feature-branch"); // git merge feature-branch * git.merge("hotfix", ["--squash", "--no-commit"]); // git merge --squash --no-commit hotfix * ``` * * @see {@link https://git-scm.com/docs/git-merge Git Merge Docs} */ merge(branchName: string, flags?: MergeFlag | MergeFlag[]): Promise<string>; /** * Switches to the given branch or commit, optionally creating it. * * @param target The branch, tag, or commit hash to checkout. * @param flags Optional checkout flags like `-b`, `--orphan`, etc. * * @returns A promise that resolves with the result of the Git command. * * @example * ```ts * git.checkout("main"); // git checkout main * git.checkout("new-branch", "-b"); // git checkout -b new-branch * ``` * * @see {@link https://git-scm.com/docs/git-checkout Git Checkout Docs} */ checkout(target: string, flags?: CheckoutFlag | CheckoutFlag[]): Promise<this>; /** * Creates, deletes, renames, or lists branches. * * @param name Optional branch name. Required for some flags. * @param flags Optional branch flags like `-d`, `-m`, `--list`, etc. * * @returns A promise that resolves with the result of the Git command. * * @example * ```ts * git.branch(); // git branch * git.branch("feature-x"); // git branch feature-x * git.branch("old-branch", ["-d"]); // git branch -d old-branch * git.branch(undefined, "--show-current"); // git branch --show-current * ``` * * @see {@link https://git-scm.com/docs/git-branch Git Branch Docs} */ branch(name?: string, flags?: BranchFlag | BranchFlag[]): Promise<string>; /** * Runs `git describe` to generate a human-readable identifier for a commit. * * This wraps the `git describe` command and returns a string such as * `v1.2.3-2-gabcdef` based on the most recent tag and commit information. * * @param flags - One or more optional `git describe` flags to customize the output. * Can be a single flag or an array of flags. * * @param ref - Optional Git reference to describe (e.g., a branch, tag, or commit hash). * Defaults to `HEAD` if not provided. * * @returns A promise that resolves with the `git describe` output. * * @example * ```ts * await git.describe("--tags"); * await git.describe(["--tags", "--long"], "main"); * await git.describe(["--dirty=*", "--abbrev=10"], "HEAD~2"); * ``` * * @see {@link https://git-scm.com/docs/git-describe Git Describe Docs} */ describe(flags: DescribeFlag | DescribeFlag[], ref?: GitRef): Promise<string>; /** * Retrieves the latest reachable Git tag (e.g., `v1.2.3`) without commit metadata. * * This uses `git describe --tags --abbrev=0` to return only the most recent tag name, * ignoring additional suffixes like commit counts or hashes. * * @returns A promise that resolves with the latest tag as a string. * * @example * ```ts * await git.getLatestTag(); // "v1.2.3" * ``` * * @see {@link Git.describe} */ getLatestTag(): Promise<string>; }