diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000000..68678e7e03 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=https://json.schemastore.org/pre-commit-hooks.json + +- id: task + name: task + description: Run a Taskfile task as a pre-commit hook + language: golang + entry: task + pass_filenames: false + require_serial: true + minimum_pre_commit_version: "3.0.0" diff --git a/precommit_test.go b/precommit_test.go new file mode 100644 index 0000000000..65638b81ec --- /dev/null +++ b/precommit_test.go @@ -0,0 +1,48 @@ +package task_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" +) + +type preCommitHook struct { + ID string `yaml:"id"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Language string `yaml:"language"` + Entry string `yaml:"entry"` + PassFilenames *bool `yaml:"pass_filenames"` + RequireSerial *bool `yaml:"require_serial"` + MinimumPreCommitVersion string `yaml:"minimum_pre_commit_version"` + Types []string `yaml:"types"` + Args []string `yaml:"args"` +} + +func TestPreCommitHooksFile(t *testing.T) { + t.Parallel() + + data, err := os.ReadFile(".pre-commit-hooks.yaml") + require.NoError(t, err, ".pre-commit-hooks.yaml should exist") + + var hooks []preCommitHook + err = yaml.Unmarshal(data, &hooks) + require.NoError(t, err, ".pre-commit-hooks.yaml should be valid YAML") + + require.Len(t, hooks, 1, "should define exactly one hook") + + hook := hooks[0] + assert.Equal(t, "task", hook.ID, "hook id should be 'task'") + assert.NotEmpty(t, hook.Name, "hook name should not be empty") + assert.NotEmpty(t, hook.Description, "hook description should not be empty") + assert.Equal(t, "golang", hook.Language, "hook language should be 'golang'") + assert.Equal(t, "task", hook.Entry, "hook entry should be 'task'") + require.NotNil(t, hook.PassFilenames, "pass_filenames should be set") + assert.False(t, *hook.PassFilenames, "pass_filenames should be false") + require.NotNil(t, hook.RequireSerial, "require_serial should be set") + assert.True(t, *hook.RequireSerial, "require_serial should be true") + assert.Equal(t, "3.0.0", hook.MinimumPreCommitVersion, "minimum_pre_commit_version should be '3.0.0'") +} diff --git a/website/src/docs/changelog.md b/website/src/docs/changelog.md index 570e8dff32..853ff7f5e1 100644 --- a/website/src/docs/changelog.md +++ b/website/src/docs/changelog.md @@ -8,6 +8,11 @@ editLink: false ::: v-pre +## next + +- Added official [pre-commit](https://pre-commit.com/) support via a + `.pre-commit-hooks.yaml` at the repository root (#2562). + ## v3.49.1 - 2026-03-08 * Reverted #2632 for now, which caused some regressions. That change will be diff --git a/website/src/docs/integrations.md b/website/src/docs/integrations.md index de9f99521b..dd434ea7d2 100644 --- a/website/src/docs/integrations.md +++ b/website/src/docs/integrations.md @@ -2,7 +2,7 @@ title: Integrations description: Official and community integrations for Task, including VS Code, JSON schemas, - and other tools + pre-commit, and other tools outline: deep --- @@ -106,6 +106,122 @@ These files are automatically generated and kept in sync with the documentation, ensuring AI assistants always have access to the latest Task features and usage patterns. +## pre-commit + +Task has official support for [pre-commit](https://pre-commit.com/), a framework +for managing and maintaining multi-language pre-commit hooks. This allows you to +automatically run Task tasks as part of your Git workflow. + +### Setup + +Add the following to your `.pre-commit-config.yaml`: + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x # Replace with the desired Task version + hooks: + - id: task + args: ['my-task'] +``` + +The hook will install Task via `go install` and run the specified task. You can +use any `task` CLI arguments in the `args` field. + +### Configuration + +The `task` hook supports the following pre-commit options: + +| Option | Default | Description | +| ---------------- | ------- | ----------------------------------------------------------- | +| `args` | `[]` | Arguments passed to `task` (e.g. task name, `--dir`, flags) | +| `files` | `''` | Only run the hook when these files change | + +### Examples + +
+Run a task unconditionally on every commit + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x + hooks: + - id: task + args: ['lint'] +``` + +
+ +
+Run a task only when certain files change + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x + hooks: + - id: task + files: ^docs/ + args: ['generate-docs'] +``` + +
+ +
+Run a task in a subdirectory + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x + hooks: + - id: task + args: ['--dir', 'frontend', 'build'] +``` + +
+ +
+Run multiple tasks with different file triggers + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x + hooks: + - id: task + name: lint + files: \.go$ + args: ['lint'] + - id: task + name: generate + files: \.proto$ + args: ['generate'] +``` + +
+ +
+Run a lightweight task on commit and a heavier task on push + +```yaml +repos: + - repo: https://github.com/go-task/task + rev: v3.x.x + hooks: + - id: task + name: lint + args: ['lint'] + stages: [pre-commit] + - id: task + name: test + args: ['test'] + stages: [pre-push] +``` + +
+ ## Community Integrations In addition to our official integrations, there is an amazing community of