Skip to content

Commit 67fb594

Browse files
committed
feat: generate documentation for github actions
Generate terraform-docs style documentation for Github Actions. Initial features: - generate actions documentation - generate workflow documentation - markdown output - provide pre-commit hook
1 parent 6ff48fc commit 67fb594

33 files changed

Lines changed: 64146 additions & 1 deletion

‎.github/workflows/ci.yml‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
17+
18+
jobs:
19+
lint:
20+
name: Lint
21+
runs-on: small
22+
23+
steps:
24+
- name: Checkout Repository
25+
uses: actions/checkout@v7
26+
27+
- name: Install uv
28+
uses: astral-sh/setup-uv@v8.2.0
29+
30+
- name: Set up Python
31+
run: uv python install 3.14
32+
33+
- name: Install dependencies
34+
run: uv sync
35+
36+
- name: Run pre-commit
37+
run: uvx pre-commit run --all-files
38+
39+
test:
40+
name: Test (Python ${{ matrix.python-version }})
41+
runs-on: small
42+
43+
strategy:
44+
fail-fast: false
45+
matrix:
46+
python-version: ['3.11', '3.12', '3.13', '3.14']
47+
48+
steps:
49+
- name: Checkout Repository
50+
uses: actions/checkout@v7
51+
52+
- name: Install uv
53+
uses: astral-sh/setup-uv@v8.2.0
54+
55+
- name: Set up Python
56+
run: uv python install ${{ matrix.python-version }}
57+
58+
- name: Install dependencies
59+
run: uv sync --python ${{ matrix.python-version }}
60+
61+
- name: Run pytest
62+
run: uv run --python ${{ matrix.python-version }} pytest --cov --cov-report=term-missing

‎.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
__pycache__
2+
dist
3+
.coverage
4+
htmlcov/
5+
.mypy_cache/
6+
.ruff_cache/

‎.pre-commit-config.yaml‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
repos:
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v6.0.0
5+
hooks:
6+
- id: end-of-file-fixer
7+
- id: trailing-whitespace
8+
9+
- repo: https://github.com/astral-sh/ruff-pre-commit
10+
rev: v0.16.8
11+
hooks:
12+
- id: ruff-check
13+
args: [--fix, --exit-non-zero-on-fix]
14+
- id: ruff-format
15+
16+
- repo: local
17+
hooks:
18+
- id: mypy
19+
name: mypy
20+
entry: uv run mypy
21+
language: system
22+
types: [python]
23+
pass_filenames: false

‎.pre-commit-hooks.yaml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
- id: github-docs
2+
args: [action]
3+
description: "Automate GitHub Actions documentation."
4+
entry: github-docs
5+
files: >
6+
(?x)^(
7+
action.(yml|yaml)|
8+
\.github/actions/(.+/)?action.(yml|yaml)
9+
)$
10+
language: python
11+
minimum_pre_commit_version: 2.9.2
12+
name: github-docs
13+
require_serial: true
14+
types: [yaml]
15+
# Workflows share one output file, so document the whole directory rather
16+
# than only the changed files.
17+
- id: github-docs-workflow
18+
args: [--output-file, README.md]
19+
description: "Automate GitHub reusable workflow documentation."
20+
entry: github-docs workflow .github/workflows
21+
files: ^\.github/workflows/[^/]+\.ya?ml$
22+
language: python
23+
minimum_pre_commit_version: 2.9.2
24+
name: github-docs-workflow
25+
pass_filenames: false
26+
require_serial: true
27+
types: [yaml]

‎README.md‎

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,108 @@
1-
# github-docs
1+
# github-docs
2+
3+
github-docs is CLI application that automatically generates documentation for your GitHub actions & workflows. It was inspired by [terraform-docs](https://github.com/terraform-docs/terraform-docs).
4+
5+
This was an Everpure Hackathon project by Andrei Gatea, Miroslav Brabenec & [Radek Sprta](https://github.com/radek-sprta).
6+
7+
## Features
8+
9+
- render documentation in Markdown
10+
- print output to stdout or save into file
11+
- inject generated documentation into README.md, or completely replace it
12+
- run as [pre-commit hook](https://pre-commit.com/)
13+
- take one or more files or directories as input (directories are searched recursively)
14+
- shell completion
15+
16+
## Requirements
17+
github-docs requires Python 3.11 or newer to run.
18+
19+
## Installation
20+
You can build and install the package manually. The repository requires SSH
21+
certificate authentication, so the clone URL carries the organisation's SSH
22+
alias and HTTPS will not work:
23+
24+
```sh
25+
git clone git@github.com:PureStorage-OpenConnect/github-docs.git
26+
cd github-docs
27+
uv build
28+
pip install --user dist/*.whl
29+
```
30+
31+
## Usage
32+
33+
For help, run `github-docs --help`. Here are some common patterns:
34+
35+
**Print action docs to stdout**
36+
37+
`github-docs action action.yml`
38+
39+
**Inject actions docs to file**
40+
41+
`github-docs action action.yml --output-file README.md`
42+
43+
Docs will be injected between `<!-- BEGIN_GITHUB_DOCS_BLOCK -->` & `<!-- END_GITHUB_DOCS_BLOCK -->` markers in your file. If the file has neither marker, the block is appended to its end.
44+
45+
**Replace file with workflow docs**
46+
47+
`github-docs workflow .github/workflows --output-file README.md --mode replace`
48+
49+
**Document several definitions at once**
50+
51+
`github-docs action .github/actions --output-file README.md`
52+
53+
You can pass any number of files and directories. `--output-file` is a
54+
**filename**, not a path — it is written beside each definition, in the
55+
directory its source file lives in. Definitions that share a directory share
56+
one document, so the command above writes one `README.md` per action
57+
directory under `.github/actions`, while pointing `workflow` at
58+
`.github/workflows` (where every reusable workflow lives flat in one
59+
directory) writes a single shared `README.md` there. With no `--output-file`,
60+
everything still concatenates to stdout, following the order the paths were
61+
given, with definitions inside a directory sorted by path.
62+
63+
Directories are searched recursively, because the conventional layout puts each
64+
action in its own subdirectory (`.github/actions/<name>/action.yml`); a
65+
non-recursive search of `.github/actions` would find nothing. Overlapping
66+
arguments are de-duplicated, so passing both a directory and a file inside it
67+
documents that definition once.
68+
69+
70+
### Pre-commit hook
71+
72+
To use the with the [pre-commit](https://pre-commit.com) framework, add the following to your `pre-commit-config.yaml`:
73+
74+
The hook rewrites the output file beside each changed definition; pre-commit
75+
fails the commit when any file was modified by the hook. Re-stage the rewritten
76+
files and commit again. Your `args` choose the subcommand and the output
77+
filename.
78+
79+
### Actions
80+
```yaml
81+
---
82+
repos:
83+
- repo: git@github.com:PureStorage-OpenConnect/github-docs.git
84+
rev: pre-commit
85+
hooks:
86+
- id: github-docs
87+
args: [action, --output-file, README.md]
88+
```
89+
90+
### Workflows
91+
```yaml
92+
---
93+
repos:
94+
- repo: git@github.com:PureStorage-OpenConnect/github-docs.git
95+
rev: pre-commit
96+
hooks:
97+
- id: github-docs-workflow
98+
```
99+
100+
The workflow hook documents every reusable workflow in `.github/workflows`
101+
whenever any of them changes, writing `README.md` there by default. Your
102+
`args` replace the default ones, so keep `--output-file` in them, e.g.
103+
`args: [--output-file, README.md, --mode, replace]`; without it the docs only
104+
go to stdout and no file is updated.
105+
106+
107+
### Contact
108+
[rsprta@everpuredata.com](mailto:rsprta@everpuredata.com)

‎justfile‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
set positional-arguments
2+
3+
default:
4+
@just --list
5+
6+
changelog tag:
7+
#!/bin/bash
8+
git cliff --config pyproject.toml --output CHANGELOG.md --tag {{tag}}
9+
commit_msg="chore(release): update CHANGELOG.md for {{tag}}"
10+
git add CHANGELOG.md && git commit -m "$commit_msg"
11+
12+
format:
13+
uvx pre-commit run ruff-format --all-files
14+
15+
lint:
16+
uvx pre-commit run --all-files
17+
18+
record-cassettes *args: (test '--vcr-record=all' args)
19+
20+
release tag: (changelog tag)
21+
git tag v{{tag}} -m "Version {{tag}}"
22+
23+
run *args:
24+
@uv run python3 -m github_docs {{args}}
25+
26+
test *args:
27+
@uv run pytest {{args}}

‎pyproject.toml‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
[tool.git-cliff.changelog]
2+
header = """
3+
# Changelog\n
4+
All notable changes to this project will be documented in this file.
5+
6+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
8+
"""
9+
body = """
10+
{% if version %}\
11+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
12+
{% else %}\
13+
## [Unreleased]
14+
{% endif %}\
15+
{% for group, commits in commits | group_by(attribute="group") %}
16+
### {{ group | upper_first }}
17+
{% for commit in commits %}
18+
- {{ commit.message | upper_first }}\
19+
{% endfor %}
20+
{% endfor %}\n
21+
"""
22+
footer = """
23+
<!-- generated by git-cliff -->
24+
"""
25+
26+
[tool.git-cliff.git]
27+
conventional_commits = true
28+
commit_parsers = [
29+
{ message = "(?i)^.*: (add|support)", group = "Added" },
30+
{ message = "(?i)^.*: (remove|delete)", group = "Removed" },
31+
{ message = "^(fix|test)|^.*: fix", group = "Fixed" },
32+
{ message = "^.*", group = "Changed" },
33+
]
34+
tag_pattern = "v[0-9]*"
35+
skip_tags = "v0.1.0-beta.1"
36+
sort_commits = "oldest"
37+
38+
[project]
39+
name = "github-docs"
40+
version = "0.1.0"
41+
description = "Automatically generate documentation for GitHub Actions."
42+
authors = [
43+
{ name = "Radek Sprta", email = "rsprta@purestorage.com" },
44+
]
45+
license = "Apache-2.0"
46+
license-files = ["LICENSE"]
47+
requires-python = ">=3.11"
48+
dependencies = [
49+
"ruamel.yaml>=0.19.0",
50+
"typer>=0.24.0",
51+
"httpx>=0.28.1,<0.29",
52+
"jinja2>=3.1.6",
53+
"jsonschema>=4.17.3",
54+
]
55+
56+
[project.scripts]
57+
github-docs = "github_docs.__main__:cli"
58+
59+
[dependency-groups]
60+
dev = [
61+
"mypy>=2.3.1",
62+
"pytest>=9.0.0",
63+
"pytest-cov>=7.1.0",
64+
"pytest-vcr>=1.0.2",
65+
"types-jsonschema>=4.26.0.20261006",
66+
]
67+
68+
[tool.uv]
69+
package = true
70+
71+
[build-system]
72+
requires = ["hatchling"]
73+
build-backend = "hatchling.build"
74+
75+
[tool.hatch.build.targets.wheel]
76+
packages = ["src/github_docs"]
77+
78+
[tool.pytest.ini_options]
79+
# As ini options, not addopts: --strict-* via addopts never reach the ini store.
80+
strict_markers = true
81+
strict_config = true
82+
markers = [
83+
"vcr: replay HTTP interactions from a recorded cassette",
84+
]
85+
testpaths = ["tests"]
86+
87+
[tool.coverage.run]
88+
branch = true
89+
source = ["src/github_docs"]
90+
91+
[tool.coverage.report]
92+
exclude_also = ["raise NotImplementedError"]
93+
show_missing = true
94+
fail_under = 96
95+
96+
[tool.mypy]
97+
strict = true
98+
files = ["src/github_docs"]
99+
100+
[tool.ruff]
101+
target-version = "py311"
102+
103+
[tool.ruff.lint]
104+
select = ["E", "F", "I", "UP", "B", "N", "SIM", "RUF"]

‎src/github_docs/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright 2026 Pure Storage Inc.
2+
# SPDX-License-Identifier: Apache-2.0

0 commit comments

Comments
 (0)