# Devops

[![Hex.pm](https://img.shields.io/hexpm/v/devops.svg)](https://hex.pm/packages/devops)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/devops)
[![CI](https://github.com/wimwian-org/devops/actions/workflows/ci.yml/badge.svg)](https://github.com/wimwian-org/devops/actions/workflows/ci.yml)
[![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](coveralls.json)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

`devops` is a set of Mix tasks that automate the full git-flow / release /
GitHub lifecycle for a **single-package** Elixir project: bootstrapping a new
repository, validating and committing changes with Conventional Commit
messages, running quality gates, and cutting a tagged, changelogged,
hex-published release -- all driven by one small project config file.

It exists so that workflow (branch names, commit conventions, CI gates,
release steps) lives in configuration and this shared library, not copied
and adapted, script by script, into every project that needs it.

## What it automates

* **Bootstrap** (`mix devops.init`) -- `git init`, cut the integration and
  production branches, generate a CI workflow, create the GitHub remote, land
  an initial commit, and lock branch protection to PR-only merges with
  required status checks.
* **Commit** (`mix devops.commit`) -- validate the change, run quality gates,
  stage, produce a Conventional Commit message (AI-drafted with a manual
  fallback), commit, push, open (or reuse) a pull request, wait on CI, and
  fast-forward the integration branch into production once checks pass.
  Every stage (`local`, `push`, `merge`, `ff`, `prune`) is also callable on
  its own, and the whole flow is resumable: a re-run after a crash, a kill,
  slow CI, or a network blip on push picks up exactly where it left off
  instead of redoing finished work or erroring out on a working tree that
  doesn't look "fresh".
* **Quality gates** (`mix devops.quality`, `mix devops.build`) -- format
  check, Credo, tests with 100% coverage, Dialyzer, Doctor, and (when the
  project looks like a Phoenix app) Sobelow, run against the project root.
  `devops.build` additionally fetches and compiles dependencies first;
  `devops.build.dev` / `devops.build.test` pin `MIX_ENV` regardless of what
  the calling shell already exports.
* **Version bumping** (`mix devops.bump_version`) -- reads HEAD's
  Conventional Commit type and bumps `.version` accordingly (a breaking
  marker escalates the bump one level), then amends the bump into HEAD.
  Wired to a project's `post-commit` git hook, it runs automatically after
  every commit.
* **Release** (`mix devops.release`) -- commit, changelog, git tag, GitHub
  release, and `hex.publish`, in that order (least to hardest to undo, since
  a hex publish is only reversible for one hour). Every step re-checks its
  own precondition, so a re-run after a stalled release picks up wherever it
  stopped.
* **Reset** (`mix devops.reset --yes`) -- irreversibly deletes the GitHub
  repo and the local `.git` directory, undoing `devops.init`. Guarded by
  `--yes` plus a second prompt that requires typing the exact `owner/name`
  slug back.

## Installation

Add `devops` to your project's dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:devops, "~> 0.6"}
  ]
end
```

## Configuration

`devops` reads its config from the `:devops` application environment, using
the standard `Config` DSL, imported from your project's own env-specific
config file:

```elixir
# config/devops.exs
import Config

config :devops, :repo, owner: "your-org", name: "your-project", visibility: :private
config :devops, :branches, production: "main"
config :devops, :commit_types, feat: :minor, fix: :patch, chore: :none
```

```elixir
# config/config.exs (or config/dev.exs, etc.)
import Config
import_config "devops.exs"
```

Each section merges independently over its own defaults, so you only need to
set the keys you want to change:

| Section        | Key(s)                                                   | Default                                            |
| -------------- | --------------------------------------------------------- | --------------------------------------------------- |
| `:repo`        | `scs`, `owner`, `name`, `visibility`                       | `scs: :github`, `owner: nil`, `name: nil`, `visibility: :private` |
| `:branches`    | `integration`, `production`, `default`, `remote`, `tag_prefix` | `dev`, `master`, `master`, `origin`, `v`        |
| `:commit_types`| Conventional Commit type -> version-bump effect            | `feat: :minor`, `fix: :patch`, `chore: :none`, etc. |
| `:prehook`     | `tools`, `commit_message`, `ai_backend`                     | `["git", "mix"]`, `Devops.CommitMessage.AI`, `Devops.AI.ClaudeCLI` |
| `:posthook`    | `bump_version`                                              | `true`                                               |
| `:ci`          | `await_timeout_ms`, `poll_interval_ms`                      | `1_800_000`, `15_000`                                |
| `:gates`       | a list or map of `{label, args, where}` gates, or `nil`     | `nil` (computes `Devops.Gates.default_gates/1`)      |

`:gates` is the one section that does not merge key-by-key: leave it unset to
get the built-in gate list, set it to a plain list to replace the default
outright, or set it to a map (`%{label => {args, where}}`) to merge over the
default list, overriding or adding individual gates without respecifying the
rest.

`GITHUB_TOKEN` (read by `Devops.GitHub`) and `HEX_API_KEY` (read by
`Devops.HexPublish`) are expected in the environment -- via a real exported
variable, or a gitignored `.env`/`.env.#{MIX_ENV}` file at the project root,
loaded automatically at boot by the vendored `DotenvParser`.

## Usage

```console
# One-time bootstrap of a brand-new project
$ mix devops.init

# Day to day: validate, commit, push, open a PR, wait on CI, fast-forward
$ mix devops.commit

# Just the quality gates, without fetching/compiling deps
$ mix devops.quality

# Fetch deps, compile, then run quality gates
$ mix devops.build

# Cut a release: commit, tag, changelog, GitHub release, hex.publish
$ mix devops.release
```

Run `mix help` for the full list of `devops.*` tasks, or see each task's own
`@moduledoc` for details.

## Documentation

Full API documentation is generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm/devops).

## Acknowledgements

`lib/vendor/dotenv_parser.ex` is vendored from
[`dotenv_parser`](https://gitlab.com/Nicd/dotenv-parser) by Mikko Ahlroth,
MIT licensed -- see [`lib/vendor/LICENSE`](lib/vendor/LICENSE). It is
vendored rather than taken as a regular dependency so `config/runtime.exs`
can load a project's `.env` file before any dependency's own config-time
code runs.

## License

`devops` is released under the [MIT License](LICENSE).
