{"id":22705120,"url":"https://github.com/mrgnhnt96/hooksman","last_synced_at":"2025-09-09T13:33:03.186Z","repository":{"id":267345018,"uuid":"897627117","full_name":"mrgnhnt96/hooksman","owner":"mrgnhnt96","description":"Create git hooks and tasks using Dart scripts and Shell commands","archived":false,"fork":false,"pushed_at":"2025-03-21T21:35:58.000Z","size":886,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-21T22:28:04.001Z","etag":null,"topics":["git","git-hooks","hooks","script","shell"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrgnhnt96.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":"FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["mrgnhnt96"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2024-12-03T00:33:53.000Z","updated_at":"2025-03-21T21:36:00.000Z","dependencies_parsed_at":"2024-12-09T20:37:05.698Z","dependency_job_id":"bd050b9e-6bc1-4cae-b86e-72e5beb02f86","html_url":"https://github.com/mrgnhnt96/hooksman","commit_stats":null,"previous_names":["mrgnhnt96/hooksman"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrgnhnt96%2Fhooksman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrgnhnt96%2Fhooksman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrgnhnt96%2Fhooksman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrgnhnt96%2Fhooksman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrgnhnt96","download_url":"https://codeload.github.com/mrgnhnt96/hooksman/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246238911,"owners_count":20745620,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","git-hooks","hooks","script","shell"],"created_at":"2024-12-10T09:09:03.369Z","updated_at":"2025-09-09T13:33:03.165Z","avatar_url":"https://github.com/mrgnhnt96.png","language":"Dart","funding_links":["https://github.com/sponsors/mrgnhnt96"],"categories":[],"sub_categories":[],"readme":"# Hooksman (Hooks Manager)\n\n[![Pub Version](https://img.shields.io/pub/v/hooksman)](https://pub.dev/packages/hooksman)\n\n[![GitHub stars](https://img.shields.io/github/stars/mrgnhnt96/hooksman?style=social)](https://github.com/mrgnhnt96/hooksman)\n\n![preview](https://raw.githubusercontent.com/mrgnhnt96/hooksman/main/.github/images/preview.gif)\n\n## Overview\n\nThe `hooksman` package allows you to manage and execute Git hooks using Dart. You can define your hooks as Dart files and register them with Git to run automatically when the corresponding events occur (e.g., pre-commit, post-commit, etc.). Inspired by [lint-staged](https://npm.im/lint-staged) and [husky](https://npm.im/husky), `hooksman` provides a flexible and powerful way to automate tasks during your workflow and share them across your team.\n\nWith `hooksman` you can run shell commands, Dart code, or a combination of both in your hooks to enforce coding standards, run tests, or perform other tasks.\n\nTasks are used to safeguard your codebase, if a task fails, `hooksman` exits with a non-zero status code, preventing the hook from completing (like a pre-commit hook).\n\n\u003e [!TIP]\n\u003e\n\u003e Check out [`sip_cli`](https://pub.dev/packages/sip_cli) for a Dart-based CLI tool to manage mono-repos, maintain project scripts, and run `dart|flutter pub get` across multiple packages.\n\n## Installation\n\nAdd `hooksman` to your `pubspec.yaml`:\n\n```bash\ndart pub add hooksman --dev\n```\n\nThen, run `dart pub get` to install the package.\n\n## Register Hooks\n\nTo register your hooks with Git, run the following command:\n\n```sh\ndart run hooksman\n```\n\nThis command will compile your hooks and copy the executables to the hooks directory.\n\n\u003e [!WARNING]\n\u003e The `hooksman` package will overwrite **all** existing hooks in the `.git/hooks` directory with the new hooks. Make sure to back up any existing hooks before running the `hooksman` command.\n\n## Dart Hooks\n\n### Create Hooks Directory\n\nCreate a `hooks` directory in the root of your project to store your hooks.\n\n```tree\n.\n├── hooks\n├── lib\n│   └── ...\n└── pubspec.yaml\n```\n\n### Create Hook\n\nCreate your hooks as Dart files in the `hooks` directory. Each file should contain a `main` function that returns the `Hook` type, imported from the `hooksman` package.\n\n```dart\n// hooks/pre_commit.dart\n\nimport 'package:hooksman/hooksman.dart';\n\nHook main() {\n    return PreCommitHook(\n        ... // Tasks\n    );\n}\n```\n\nThe pre-defined hooks are:\n\n- `PreCommitHook`: Runs before a commit is made\n- `PrePushHook`: Runs before a push is made\n- `AnyHook`: A general purpose hook that can be used to create custom hooks\n\n\u003e [!NOTE]\n\u003e\n\u003e `hooksman` scans the `hooks` directory for Dart files to use as hooks. You can organize your code by placing additional Dart files in subdirectories within the `hooks` directory. These files can be imported into your hook files and will not be picked up by `hooksman` as hooks.\n\u003e\n\u003e ```bash\n\u003e .\n\u003e └── hooks\n\u003e     ├── tasks\n\u003e     │   ├── some_dart_task.dart # ignored\n\u003e     │   └── ...\n\u003e     └── pre_commit.dart # picked up\n\u003e ```\n\n### Hook Names\n\nThe name of the hook is derived from the file name. For example, a file named `pre_commit.dart` will be registered as the `pre-commit` hook. Be sure to follow the naming convention for Git hooks.\n\n\u003e [!TIP]\n\u003e\n\u003e Look at the git hooks documentation for more information on the available hooks: [Git Hooks Documentation](https://git-scm.com/docs/githooks).\n\n## Shell Hooks\n\nYou can create stand alone shell hooks by creating shell files within the hooks directory. These files will be copied to the `.git/hooks` directory and executed when the corresponding Git hook event occurs.\n\n### Create Shell Hook\n\nCreate a shell file in the `hooks` directory. The file name should match the name of the Git hook you want to use.\n\n```bash\ntouch hooks/post-commit.sh\n```\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e The file extension should be `.sh` to be recognized as a shell hook.\n\n### Shell Hook Content\n\nAdd the shell commands you want to run in the shell file.\n\n```bash\n#!/bin/sh\n\necho \"Running post-commit hook\"\n```\n\n\u003e [!TIP]\n\u003e\n\u003e You can execute the dart hooks from the shell hooks by using the name of the executable file\n\u003e\n\u003e ```bash\n\u003e #!/bin/sh\n\u003e\n\u003e ./pre-commit # Execute the pre-commit dart hook\n\u003e ```\n\n## Tasks\n\nTasks are modular units of work that you define to be executed during specific Git hook events. They allow you to automate checks, validations, or any custom scripts to ensure code quality and consistency across your repository. Tasks are powerful because they can be customized to suit your project's needs while targeting specific file paths or patterns.\n\nAll top level tasks are executed in parallel, while tasks within a group are executed sequentially. This allows you to run multiple tasks concurrently and group related tasks together.\n\n### File Patterns\n\nYou can specify file patterns to include or exclude from a task using any `Pattern` object (`Glob`, `RegExp`, `String`, etc.). Each task can have multiple include and exclude patterns.\n\n\u003e [!TIP]\n\u003e\n\u003e `hooksman` exposes the `Glob` class from the [Glob](https://pub.dev/packages/glob) package to match file paths using glob patterns.\n\u003e\n\u003e `hooksman` also has an `AllFiles` class to match all file paths.\n\n\u003e [!NOTE]\n\u003e\n\u003e `exclude` filters any matching files before `include` is applied.\n\nAfter the filters are applied, the remaining files are passed to the task's `commands` or `run` function.\n\n### Task Naming\n\nEach task is assigned a name that is displayed when the task is executed. This is useful for identifying the task in the output. By default, the name of the task is the pattern(s) used to include files. If you would like to provide a custom name, you can do so by setting the `name` property of the task.\n\n```dart\nShellTask(\n    name: 'Analyze',\n    include: [Glob('**.dart')],\n    exclude: [Glob('**.g.dart')],\n    commands: (filePaths) =\u003e [\n        'dart analyze --fatal-infos ${filePaths.join(' ')}',\n    ],\n),\n```\n\n### Shell Task\n\nA `ShellTask` allows you to run shell commands.\n\n```dart\nShellTask(\n    name: 'Analyze',\n    include: [Glob('**.dart')],\n    exclude: [Glob('**.g.dart')],\n    commands: (filePaths) =\u003e [\n        'dart analyze --fatal-infos ${filePaths.join(' ')}',\n    ],\n),\n```\n\n### Dart Task\n\nA `DartTask` allows you to run Dart code.\n\n```dart\nDartTask(\n    include: [Glob('**.dart')],\n    run: (filePaths) async {\n        print('Running custom task');\n\n        return 0;\n    },\n),\n```\n\n### Sequential Tasks\n\nYou can group tasks together using the `SequentialTasks` class, which runs the tasks sequentially, one after the other.\n\n```dart\nSequentialTasks(\n    tasks: [\n        ShellTask(\n            include: [Glob('**.dart')],\n            commands: (filePaths) =\u003e [\n                'dart format ${filePaths.join(' ')}',\n            ],\n        ),\n        ShellTask(\n            include: [Glob('**.dart')],\n            commands: (filePaths) =\u003e [\n                'sip test --concurrent --bail',\n            ],\n        ),\n    ],\n),\n```\n\n### Parallel Tasks\n\nYou can group tasks together using the `ParallelTasks` class, which runs the tasks in parallel.\n\n```dart\nParallelTasks(\n    tasks: [\n        ShellTask(\n            include: [Glob('**.dart')],\n            commands: (filePaths) =\u003e [\n                'dart format ${filePaths.join(' ')}',\n            ],\n        ),\n        ShellTask(\n            include: [Glob('**.dart')],\n            commands: (filePaths) =\u003e [\n                'sip test --concurrent --bail',\n            ],\n        ),\n    ],\n),\n```\n\n## Predefined Tasks\n\n### ReRegisterHooks\n\nIt can be easy to forget to re-register the hooks with Git after making changes. Re-registering the hooks is necessary to ensure that the changes are applied, since your dart files are compiled into executables then copied to the `.git/hooks` directory.\n\nTo automate this process, you can use the `ReRegisterHooks` task. This task will re-register your hooks with Git whenever any hook files are created, modified, or deleted.\n\n```dart\nHook main() {\n  return Hook(\n    tasks: [\n      ReRegisterHooks(),\n    ],\n  );\n}\n```\n\n\u003e [!TIP]\n\u003e\n\u003e If your `hooks` directory is not found in the root of your project, you can specify the path to the `hooks` directory to the `ReRegisterHooks` task.\n\u003e\n\u003e ```dart\n\u003e ReRegisterHooks(pathToHooksDir: 'path/to/hooks'),\n\u003e ```\n\n## Hook Execution\n\nThe hooks will be executed automatically by Git when the corresponding events occur (e.g., pre-commit, post-commit, etc.).\n\n### Amending to the Commit (PreCommitHook)\n\nAfter `hooksman` executes the tasks, a check will be made to see if any files were created/deleted/modified. If so, the files will be added to the commit.\n\nAn example of this behavior is when you have a `ShellTask` that formats the code using `dart format`. If the code is not formatted correctly, `hooksman` will format the code and add the changes to the commit.\n\n### Error Handling\n\nIf an error occurs during the execution of a task, `hooksman` will stop the execution of the remaining tasks and exit with a non-zero status code. This will prevent the commit from being made, allowing you to fix the issue before committing again.\n\n### `Ctrl+C` (Signal Interruption)\n\nIf the user interrupts the hook execution (e.g., by pressing `Ctrl+C`), `hooksman` will stop the execution of the remaining tasks and exit with a non-zero status code.\n\n## Configuration\n\n### Diff Filters\n\nThe `diffFilters` parameter allows you to specify the statuses of files to include or exclude, such as `added`, `modified`, or `deleted`.\n\n```dart\n\nHook main() {\n  return PreCommitHook(\n    diffFilters: 'AM', // Include added and modified files\n    tasks: [\n      ...\n    ],\n  );\n}\n```\n\n### Diff\n\nThe `diff` parameter allows you to specify how files are compared with the working directory, index (staged), or commit.\n\nThe example below demonstrates how to compare files with the remote branch (e.g., `origin/main`). This could be useful for a `pre-push` hook.\n\n```dart\nHook main() {\n  return PrePushHook(\n    // Compare files with the remote branch\n    diffArgs: ['@{u}', 'HEAD'], // default args\n    tasks: [\n      ...\n    ],\n  );\n}\n```\n\n## Verbose Output\n\nYou can enable verbose output by using the `verbose` constructor on any of the `Hook` classes. This will _slow_ down the execution of the tasks and output detailed information about the tasks being executed. This can be useful to understand the order of execution and the files being processed. This is not intended to be used in non-developing environments.\n\n## Example\n\n```dart\n// hooks/pre_push.dart\n\nimport 'package:hooksman/hooksman.dart';\n\nHook main() {\n  return PrePushHook.verbose(\n    tasks: [\n      ReRegisterHooks(),\n      ShellTask(\n        name: 'Lint \u0026 Format',\n        include: [Glob('**.dart')],\n        exclude: [\n          Glob('**.g.dart'),\n        ],\n        commands: (filePaths) =\u003e [\n          'dart analyze --fatal-infos ${filePaths.join(' ')}',\n          'dart format ${filePaths.join(' ')}',\n        ],\n      ),\n      ShellTask(\n        name: 'Build Runner',\n        include: [Glob('lib/models/**.dart')],\n        exclude: [Glob('**.g.dart')],\n        commands: (filePaths) =\u003e [\n          'sip run build_runner build',\n        ],\n      ),\n      ShellTask(\n        name: 'Tests',\n        include: [Glob('**.dart')],\n        exclude: [Glob('hooks/**')],\n        commands: (filePaths) =\u003e [\n          'sip test --concurrent --bail',\n        ],\n      ),\n    ],\n  );\n}\n```\n\n## License\n\nThis project is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrgnhnt96%2Fhooksman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrgnhnt96%2Fhooksman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrgnhnt96%2Fhooksman/lists"}