Skip to content

Commit e621377

Browse files
chore: open source readiness improvements
* add LICENSE (MIT), CONTRIBUTING.md, authors, keywords, classifiers * merge auto-tag workflow into ci.yml gated on lint-and-test * fix CI expression injection by moving tag to env block * pin snok/install-poetry to SHA, bump pyrefly-pre-commit to 0.53.0 * tighten branch regex to reject leading hyphens and ".." traversal * handle missing git binary in validate_branch * fail the run when alembic upgrade --sql errors (GenerateSqlError) * document env.py execution and DATABASE_URL fallback in README * rename README title to squawk-pre-commit * add .ruff_cache/ to .gitignore
1 parent 0c48a4e commit e621377

11 files changed

Lines changed: 239 additions & 84 deletions

File tree

.github/workflows/auto-tag.yml

Lines changed: 0 additions & 60 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
python-version: '3.12'
2121

2222
- name: Install Poetry
23-
uses: snok/install-poetry@v1
23+
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1
2424
with:
2525
version: latest
2626
virtualenvs-create: true
@@ -49,3 +49,60 @@ jobs:
4949
5050
- name: Run tests
5151
run: poetry run pytest tests/ -v
52+
53+
auto-tag:
54+
needs: lint-and-test
55+
if: github.event_name == 'push'
56+
runs-on: ubuntu-latest
57+
permissions:
58+
contents: write
59+
outputs:
60+
tag: ${{ steps.tag.outputs.tag }}
61+
created: ${{ steps.tag.outputs.created }}
62+
63+
steps:
64+
- name: Checkout code
65+
uses: actions/checkout@v4
66+
with:
67+
fetch-depth: 0
68+
69+
- name: Create tag if needed
70+
id: tag
71+
run: |
72+
VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
73+
TAG="v${VERSION}"
74+
75+
echo "Detected version: $VERSION"
76+
echo "Tag: $TAG"
77+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
78+
79+
if git rev-parse "$TAG" >/dev/null 2>&1; then
80+
echo "Tag $TAG already exists. Nothing to do."
81+
echo "created=false" >> "$GITHUB_OUTPUT"
82+
exit 0
83+
fi
84+
85+
echo "Creating and pushing tag $TAG"
86+
git tag "$TAG"
87+
git push origin "$TAG"
88+
echo "created=true" >> "$GITHUB_OUTPUT"
89+
90+
release:
91+
needs: auto-tag
92+
if: needs.auto-tag.outputs.created == 'true'
93+
runs-on: ubuntu-latest
94+
permissions:
95+
contents: write
96+
97+
steps:
98+
- name: Checkout code
99+
uses: actions/checkout@v4
100+
101+
- name: Create GitHub Release
102+
env:
103+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
104+
TAG: ${{ needs.auto-tag.outputs.tag }}
105+
run: |
106+
gh release create "$TAG" \
107+
--title "Release $TAG" \
108+
--generate-notes

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ dist/
1212
# Virtual environments
1313
.venv/
1414

15+
# Linting
16+
.ruff_cache/
17+
1518
# Test / coverage
1619
.pytest_cache/
1720
.coverage

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ repos:
3030
hooks:
3131
- id: poetry-check
3232
- repo: https://github.com/facebook/pyrefly-pre-commit
33-
rev: 0.0.1
33+
rev: 0.53.0
3434
hooks:
3535
- id: pyrefly-typecheck-system
3636
name: Pyrefly (type checking)

CONTRIBUTING.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Contributing
2+
3+
We welcome contributions to squawk-pre-commit. Here's how to get started.
4+
5+
## Development Setup
6+
7+
1. Clone the repo and install dependencies:
8+
9+
```bash
10+
git clone https://github.com/kintsugi-tax/squawk-pre-commit.git
11+
cd squawk-pre-commit
12+
poetry install
13+
```
14+
15+
2. Install pre-commit hooks:
16+
17+
```bash
18+
pre-commit install
19+
```
20+
21+
3. Run the test suite:
22+
23+
```bash
24+
poetry run pytest tests/ -v
25+
```
26+
27+
## Submitting Changes
28+
29+
1. Fork the repo and create a branch from `main`
30+
2. Make your changes and add tests where appropriate
31+
3. Ensure all tests pass and pre-commit hooks are clean
32+
4. Open a pull request against `main`
33+
34+
## Reporting Issues
35+
36+
Open an issue on [GitHub](https://github.com/kintsugi-tax/squawk-pre-commit/issues) with a clear description of the problem and steps to reproduce.
37+
38+
## License
39+
40+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Kintsugi
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Kintsugi Squawk
1+
# squawk-pre-commit
22

33
A [pre-commit](https://pre-commit.com/) hook that lints SQL in [Alembic](https://alembic.sqlalchemy.org/) migrations using [squawk](https://squawkhq.com/), a PostgreSQL migration linter.
44

@@ -59,6 +59,13 @@ When pre-commit runs, the hook:
5959

6060
Merge migrations (where `down_revision` is a tuple) are skipped since they produce no DDL.
6161

62+
## Known Limitations
63+
64+
* The hook runs `alembic upgrade --sql`, which executes your project's `env.py` in offline mode. No database connection is made, but the Python code in `env.py` does run.
65+
* If `DATABASE_URL` is not set, the hook provides a dummy fallback (`postgresql://localhost/lint`) so alembic's offline mode can generate SQL without a real connection string.
66+
* If `alembic upgrade --sql` fails for a migration (e.g. due to missing dependencies or env configuration), the hook prints the error to stderr and fails the run.
67+
* Merge migrations (where `down_revision` is a tuple) produce no DDL and are always skipped.
68+
6269
## Squawk Configuration
6370

6471
Squawk reads its configuration from `.squawk.toml` in the consumer repo root. See the [squawk docs](https://squawkhq.com/docs/configuration/) for available options.
@@ -67,7 +74,7 @@ Squawk reads its configuration from `.squawk.toml` in the consumer repo root. Se
6774

6875
**Prerequisites:**
6976

70-
* Python (version 3.12)
77+
* Python 3.10+ (3.12 recommended for development)
7178
* Poetry
7279
* squawk-cli (`pip install squawk-cli`)
7380

@@ -78,6 +85,8 @@ Squawk reads its configuration from `.squawk.toml` in the consumer repo root. Se
7885
3. Install the pre-commit hooks: `pre-commit install`
7986
4. Run tests: `poetry run pytest tests/ -v`
8087

88+
Some integration tests in `tests/test_squawk_config.py` require `squawk` on PATH and are automatically skipped if it is not installed.
89+
8190
To test the hook against a consumer repo locally:
8291

8392
```bash

pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ name = "squawk-alembic"
77
version = "0.3.0"
88
description = "Pre-commit hook to lint Alembic migration SQL with squawk"
99
packages = [{include = "squawk_alembic"}]
10+
readme = "README.md"
11+
homepage = "https://www.trykintsugi.com/"
12+
repository = "https://github.com/kintsugi-tax/squawk-pre-commit"
13+
authors = ["Kintsugi <engineering@trykintsugi.com>"]
14+
license = "MIT"
15+
keywords = ["pre-commit", "squawk", "alembic", "postgresql", "migration", "linter"]
16+
classifiers = [
17+
"Development Status :: 4 - Beta",
18+
"Intended Audience :: Developers",
19+
"Topic :: Software Development :: Quality Assurance",
20+
"Topic :: Database",
21+
"Programming Language :: Python :: 3",
22+
"Programming Language :: Python :: 3.10",
23+
"Programming Language :: Python :: 3.11",
24+
"Programming Language :: Python :: 3.12",
25+
]
1026

1127
[tool.poetry.dependencies]
1228
python = ">=3.10"

squawk_alembic/hook.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from configparser import ConfigParser, NoOptionError, NoSectionError
1111
from pathlib import Path
1212

13-
_BRANCH_RE = re.compile(r"^[a-zA-Z0-9._/\-]+$")
13+
_BRANCH_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._/\-]*$")
1414

1515

1616
def find_migrations_path():
@@ -90,8 +90,16 @@ def extract_revision_info(filepath):
9090
)
9191

9292

93+
class GenerateSqlError(Exception):
94+
"""Raised when alembic upgrade --sql fails."""
95+
96+
9397
def generate_sql(filepath):
94-
"""Run alembic upgrade --sql to generate the complete DDL for a migration."""
98+
"""Run alembic upgrade --sql to generate the complete DDL for a migration.
99+
100+
Returns the SQL string, or None if the file should be skipped (merge migration,
101+
unparseable revision). Raises GenerateSqlError if alembic fails.
102+
"""
95103
info = extract_revision_info(filepath)
96104
if info is None:
97105
return None
@@ -114,34 +122,34 @@ def generate_sql(filepath):
114122
env=env,
115123
)
116124
except FileNotFoundError:
117-
print(
118-
"squawk-alembic: alembic not found. Ensure alembic is installed in your environment.",
119-
file=sys.stderr,
125+
raise GenerateSqlError(
126+
"squawk-alembic: alembic not found. Ensure alembic is installed in your environment."
120127
)
121-
return None
122128

123129
if result.returncode != 0:
124-
print(
125-
f"squawk-alembic: alembic upgrade --sql failed for {filepath}:\n{result.stderr}",
126-
file=sys.stderr,
130+
raise GenerateSqlError(
131+
f"squawk-alembic: alembic upgrade --sql failed for {filepath}:\n{result.stderr}"
127132
)
128-
return None
129133

130134
return result.stdout
131135

132136

133137
def validate_branch(branch):
134138
"""Validate that a branch name is safe and exists in git."""
135-
if not _BRANCH_RE.match(branch):
139+
if not _BRANCH_RE.match(branch) or ".." in branch:
136140
print(
137141
f"squawk-alembic: invalid branch name: {branch!r}",
138142
file=sys.stderr,
139143
)
140144
return False
141-
result = subprocess.run(
142-
["git", "rev-parse", "--verify", branch],
143-
capture_output=True,
144-
)
145+
try:
146+
result = subprocess.run(
147+
["git", "rev-parse", "--verify", branch],
148+
capture_output=True,
149+
)
150+
except FileNotFoundError:
151+
print("squawk-alembic: git not found", file=sys.stderr)
152+
return False
145153
if result.returncode != 0:
146154
print(
147155
f"squawk-alembic: branch '{branch}' not found in git",
@@ -195,7 +203,13 @@ def main():
195203
if args.diff_branch and file_exists_on_branch(filepath, args.diff_branch):
196204
continue
197205

198-
sql = generate_sql(filepath)
206+
try:
207+
sql = generate_sql(filepath)
208+
except GenerateSqlError as exc:
209+
print(str(exc), file=sys.stderr)
210+
exit_code = 1
211+
continue
212+
199213
if not sql:
200214
continue
201215

0 commit comments

Comments
 (0)