git-add-then-commit
TypeScript icon, indicating that this package has built-in type declarations

2.2.6 • Public • Published

Black Lives Matter! Maintenance status Last commit timestamp Open issues Pull requests Codecov Source license NPM version Uses Semantic Release!

git-add-then-commit

A minimalist CLI tool to automate the ↯ git add Xgit commit -m 'Y(Z): W' ↯ workflow and help you compose atomic consistent conventional commits quickly and easily.

Install

npm install --global git-add-then-commit

Usage

gac [path1, path2, ...] commit-type commit-scope commit-message

You can use --help to get help text output, --version to get the current version, and --silent to prevent all output.

For a repository using conventional commits, your commit flow might go something like this:

git add path/to/file2
git commit -m 'feat(file2): add new killer feature'

Where the commit message has:

  • Type: feat
  • Scope: file2
  • Subject (or message): add new killer feature

With git-add-then-commit (gac), this can be simplified to:

git add path/to/file2
gac feat file2 'add new killer feature'

And further simplified to:

gac path/to/file2 feat file2 'add new killer feature'

And even further (using a scope option):

gac path/to/file2 feat -- 'add new killer feature'

And further still:

gac path feat -- 'add new killer feature'

Scope Options

-- as used in the example above is a scope option, which can be used in place of commit-scope.

To maintain scope consistency in generated changelogs with minimal effort, favor the --scope-root and --scope-omit scope options.

Basename

-- (or: --scope-basename) will generate a commit message using the lowercased basename of 1) the first path passed to gac or 2) the first staged path returned by git status. The basename is always lowercased.

If more than one file is staged and no paths are passed to gac, using --scope-basename will cause an ambiguity error.

Omit

- (or: --scope-omit) will generate a commit message with no scope.

Example

Given the following filesystem structure:

.
└── src
    └── index.ts <MODIFIED>

The following are equivalent:

gac src feat - 'add new killer feature'

git add src/index.ts
git commit -m 'feat: add new killer feature'

As-is

-a (or: --scope-as-is) will generate a commit message using the first path passed to gac exactly as typed.

If no paths are passed to gac, using --scope-as-is will cause an ambiguity error.

Example

Given the following filesystem structure:

.
└── src
    ├── iNdex.ts <MODIFIED>
    ├── cli.ts <MODIFIED>
    ├── errors.ts <MODIFIED>
    └── git.ts <MODIFIED>

The following are equivalent:

gac src/iNdex.ts src feat --scope-as-is 'add new killer feature'

git add src/iNdex.ts
git add src/cli.ts
git add src/errors.ts
git add src/git.ts
git commit -m 'feat(src/iNdex.ts): add new killer feature'

Full

-f (or: --scope-full) will generate a commit message using the "full" or absolute path (relative to the repository root) of the first path passed to gac.

If no path arguments are passed, --scope-full will use the full path—including filename and extension—if there is exactly one path or staged file, the deepest common ancestor of all paths/files if there is more than one (or the first path is ambiguous), or fail with an ambiguity error if there is no relative common ancestor.

Regardless, the final commit-scope is always lowercased.

Example

Given the following filesystem structure:

.
├── public
│   └── images
│       ├── favicon.ico <MODIFIED>
│       ├── hero.png
│       └── villain.png
├── src
│   ├── index.ts <MODIFIED>
│   └── interface
│       ├── cli.ts <MODIFIED>
│       └── git.ts
└── test
    ├── units.ts
    └── fixtures
        ├── dummy-1.ts <MODIFIED>
        └── dummy-2.ts <MODIFIED>

The following are equivalent:

gac src feat --scope-full 'add new killer feature'

git add src/index.ts
git add src/interface/cli.ts
git commit -m 'feat(src): add new killer feature'
gac test refactor --scope-full 'update tests for new feature'

git add test/fixtures/dummy-1.ts
git add test/fixtures/dummy-2.ts
git commit -m 'refactor(test/fixtures): update tests for new feature'
gac public style --scope-full 'new favicon'

git add public
git commit -m 'style(public/images/favicon.ico): new favicon'

Root

--- (or: --scope-root) will generate a commit message with a more "photogenic" scope. That is, commit messages derived using this option tend to look nicer in generated changelogs. Specifically:

  • A small, consistently derived set of scopes are used across the lifetime of the repository.
  • Derived scopes are analogous to filesystem structure.
  • Derived scopes tend to be short, sweet, and mostly alphanumeric.

Like --scope-full, --scope-root will derive commit-scope from the first path argument passed to gac.

The path used to derive the commit-scope is referred to below as the selected path.

Unlike --scope-full, only the first directory (left-to-right) in the selected path—rather than the deepest common ancestor—is used to derive commit-scope.

For example, path in selected path path/to/some/file is the first directory.

If no path arguments are passed and there is exactly one staged file, --scope-root will use that file as the selected path. If there is more than one staged file (or the first path is ambiguous) and their paths share a common ancestor directory other than the repository root, the deepest common ancestor becomes the selected path; if there is no valid common ancestor, the operation fails with an ambiguity error.

An ambiguity error using --scope-root is usually a hint to construct a more fine-grain commit.

If the selected path has no first directory, i.e. it points to a file at the root of the repository, the filename is used as commit-scope instead with its file extension removed (see package.json in the examples below).

On the other hand, if the selected path has:

  • A first directory matching commit-type (see test in the examples below):

    • If there is a second directory in the selected path, the second directory is used to derive the commit-scope instead.

      For example, to in path/to/some/file is the second directory.

    • If there is no second directory, the filename (sans extension) is used to derive the commit-scope only if the file is not named "index".

    • If there is no second directory and the file is named "index" (sans extension), commit-scope is omitted.

  • A first directory named "packages" (see Monorepo Pseudo-Pathspecs below):

    • If there is a second directory in the selected path that is a common ancestor, the first and second directories are used to derive the commit-scope instead.

      For example, packages/pkg-1 when committing packages/pkg-1/some/file and packages/pkg-1/some/other/file

    • If "packages" (as the first directory) is the deepest common ancestor in the selected path—i.e. it's a commit spanning multiple monorepo packages—only the first directory is used to derive the commit-scope, which is the normal behavior.

      For example, packages when committing packages/pkg-1/some/file and packages/pkg-TWO/some/file

  • A first directory with a name beginning with "external":

    • commit-scope becomes "externals".

At the end of the process, if it has not already been omitted, commit-scope is lowercased and split on "." with the first element used as the final commit-scope. Finally, if commit-scope matches commit-type, commit-scope is omitted.

Example

Given the following filesystem structure:

.
├── CHANGELOG.md <MODIFIED>
├── CONTRIBUTING.md
├── docs
│   ├── supplementary.md <MODIFIED>
│   └── README.md <MODIFIED>
├── external-scripts
│   └── my-script.ts <MODIFIED>
├── index.ts <MODIFIED>
├── identity.trifold.ts <MODIFIED>
├── lib
│   ├── api
│   │   └── adapter.trifold.ts <MODIFIED>
│   ├── index.ts <MODIFIED>
│   ├── cli.ts <MODIFIED>
│   └── git.ts
├── package-lock.json <MODIFIED>
├── package.json <MODIFIED>
├── README.md
└── test
    ├── index.ts <MODIFIED>
    ├── integrations
    │   ├── browser-tests.ts
    │   ├── e2e-tests.ts <MODIFIED>
    │   └── index.ts <MODIFIED>
    └── units.ts <MODIFIED>

The following are equivalent:

gac identity.trifold.ts feat --- 'added identity trifold subroutine'

git add identity.trifold.ts
git commit -m 'feat(identity): added identity trifold subroutine'
gac lib/index.ts fix --- 'fix bug that caused crash'

git add lib/index.ts
git commit -m 'fix(lib): fix bug that caused crash'
gac lib/api refactor --- 'use updated mongodb trifold driver'

git add lib/api/adapter.trifold.ts
git commit -m 'refactor(lib): use updated mongodb trifold driver'
gac package.json package-lock.json chore --- 'update dependencies'

git add package.json
git add package-lock.json
git commit -m 'chore(package): update dependencies'
git add docs
gac docs --- 'add sections on new killer feature'
# one-liner: gac docs docs --- 'add sections on new killer feature'

git add docs
git commit -m 'docs: add sections on new killer feature'
gac test/integrations/index.ts test --- 'update integration tests'

git add test/integrations/index.ts
git commit -m 'test(integrations): update integration tests'
gac test/integrations style --- 'use emojis in all TODO comments'

git add test/integrations/e2e-tests.ts
git commit -m 'style(test): use emojis in all TODO comments'
gac test/index.ts test --- 'update tooling to use latest features'

git add test/index.ts
git commit -m 'test: update tooling to use latest features'
gac test test --- 'add unit tests for new killer feature'

git add test/units.ts
git commit -m 'test(units): add unit tests for new killer feature'
gac index.ts lib/cli.ts feat --- 'add new killer feature'

git add index.ts
git add lib/cli.ts
git commit -m 'feat(index): add new killer feature'
gac CHANGELOG.md docs --- 'regenerate'

git add CHANGELOG.md
git commit -m 'docs(changelog): regenerate'
gac external-scripts/my-script.ts build --- 'update my-script functionality'

git add external-scripts/my-script.ts
git commit -m 'build(externals/my-script): update my-script functionality'

Monorepo Pseudo-Pathspecs

Along with normal pathspecs, gac also supports a so-called "pseudo-pathspec" syntax for easily referring to package sub-roots in a monorepo.

Given the following filesystem structure:

.
├── CHANGELOG.md
├── CONTRIBUTING.md
├── package-lock.json
├── package.json
├── packages
│   ├── pkg-1
│   │   ├── README.md <MODIFIED>
│   │   └── specific
│   │       └── script.ts <MODIFIED>
│   └── pkg-2
│   │   ├── README.md <MODIFIED>
│       └── src
│           └── index.ts <MODIFIED>
└── README.md

The following are pairs of equivalent commands where :: is the pseudo-pathspec specifier:

gac ::pkg-2 style --- 'cosmetic changes'

git add packages/pkg-2/README.md
git add packages/pkg-2/src/index.ts
git commit -m 'style(packages/pkg-2): cosmetic changes'
gac ::pkg-1/specific/script.ts feat --- 'added something specific to a script'

git add packages/pkg-1/specific/script.ts
git commit -m 'feat(packages/pkg-1): added something specific to a script'
cd packages/pkg-2
gac ::pkg-1 feat --- 'added something specific to a script'

git add ../../packages/pkg-1/README.md
git add ../../packages/pkg-1/specific/script.ts
git commit -m 'feat(packages/pkg-1): added something specific to a script'
cd packages/pkg-1
gac :: refactor --- 'a non-atomic commit with a whole bunch of changes'

git add ../../packages
git commit -m 'refactor(packages): a non-atomic commit with a whole bunch of changes'
gac ::*/README.md docs --- 'add license section to all packages'

git add packages/pkg-1/README.md
git add packages/pkg-2/README.md
git commit -m 'docs(packages): add license section to all packages'

Other Features

  • Use --help for more usage information, including listing all aliases.

  • Use --no-verify to perform an unverified commit.

  • Use --verify=simple to set GAC_VERIFY_SIMPLE=true in the runtime environment, which can be used to skip certain tests in your git hooks based on the presence of the variable.

  • If commit-message describes a breaking change, an exclamation point is prepended to the colon in the final commit message.

  • gac works with both currently staged files and any paths passed as arguments with the latter having precedence. This makes it easy to, for instance, stage files with vscode or git add -p then use gac to quickly compose an atomic conventional commit.

Importing as a Module

This package can be imported and run directly in source without spawning a child process or calling a CLI. This is useful for, for instance, composing multiple yargs-based CLI tools together.

import { configureProgram } from 'git-add-then-commit';

const { program, parse } = configureProgram();
// `program` is a yargs instance
// `parse` is an async function that will (eventually) call program.parse(...)
await parse(['path', 'type', '--no-scope', 'commit message here']);

Documentation

Further documentation can be found under docs/.

This is a dual CJS2/ES module package. That means this package exposes both CJS2 and ESM (treeshakable and non-treeshakable) entry points.

Loading this package via require(...) will cause Node and some bundlers to use the CJS2 bundle entry point. This can reduce the efficacy of tree shaking. Alternatively, loading this package via import { ... } from ... or import(...) will cause Node (and other JS runtimes) to use the non-treeshakable ESM entry point in versions that support it. Modern bundlers like Webpack and Rollup will use the treeshakable ESM entry point. Hence, using the import syntax is the modern, preferred choice.

For backwards compatibility with Node versions < 14, package.json retains the main key, which points to the CJS2 entry point explicitly (using the .js file extension). For Node versions > 14, package.json includes the more modern exports key. For bundlers, package.json includes the bundler-specific module key (eventually superseded by exports['.'].module), which points to ESM source loosely compiled specifically to support tree shaking.

Though package.json includes { "type": "commonjs"}, note that the ESM entry points are ES module (.mjs) files. package.json also includes the sideEffects key, which is false for optimal tree shaking, and the types key, which points to a TypeScript declarations file.

Additionally, this package does not maintain shared state and so does not exhibit the dual package hazard.

License

FOSSA analysis

Contributing and Support

New issues and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Thank you!

See CONTRIBUTING.md and SUPPORT.md for more information.

Package Sidebar

Install

npm i git-add-then-commit

Weekly Downloads

4

Version

2.2.6

License

MIT

Unpacked Size

71.6 kB

Total Files

12

Last publish

Collaborators

  • xunnamius