gitnifty
Version:
A robust, promise-based Git utility for Node.js
674 lines (673 loc) • 26 kB
TypeScript
/**
* 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>;
}