Skip to content

Commit be03cd0

Browse files
committed
ci and tests and build and release
1 parent d97a634 commit be03cd0

38 files changed

Lines changed: 3726 additions & 193 deletions

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
tags-ignore:
6+
- "v*"
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
- name: Setup Bun
16+
uses: oven-sh/setup-bun@v1
17+
with:
18+
bun-version: "1.3.5"
19+
- name: Install dependencies
20+
run: bun install
21+
- name: Lint
22+
run: bun run lint
23+
- name: Typecheck
24+
run: bun run typecheck
25+
- name: Tests
26+
run: bun test

.github/workflows/release.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
- name: Setup Bun
17+
uses: oven-sh/setup-bun@v1
18+
with:
19+
bun-version: "1.3.5"
20+
- name: Install dependencies
21+
run: bun install
22+
- name: Resolve version
23+
id: version
24+
run: |
25+
VERSION="$(bun -e "const pkg = await Bun.file('package.json').json(); console.log(pkg.version)")"
26+
if [ "v$VERSION" != "$GITHUB_REF_NAME" ]; then
27+
echo "Tag $GITHUB_REF_NAME does not match package.json version $VERSION."
28+
exit 1
29+
fi
30+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
31+
- name: Build release artifacts
32+
run: bun run build:release --version="${{ steps.version.outputs.version }}"
33+
- name: Create GitHub release
34+
uses: softprops/action-gh-release@v2
35+
with:
36+
tag_name: "v${{ steps.version.outputs.version }}"
37+
generate_release_notes: true
38+
files: |
39+
dist/release/hack-${{ steps.version.outputs.version }}-*.tar.gz
40+
dist/release/hack-${{ steps.version.outputs.version }}-install.sh
41+
dist/release/hack-install.sh
42+
dist/release/hack-${{ steps.version.outputs.version }}-release/SHA256SUMS

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
3737
SPECS/
3838
_DOCS
3939
TODO.md
40-
REVIEW.md
40+
REVIEW.md
41+
42+
dist

.husky/commit-msg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname "$0")/_/h"
3+
4+
bunx --no-install commitlint --edit "$1"

.releaserc.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"branches": ["main"],
3+
"tagFormat": "v${version}",
4+
"plugins": [
5+
["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
6+
["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
7+
["@semantic-release/changelog", { "changelogFile": "CHANGELOG.md" }],
8+
[
9+
"@semantic-release/exec",
10+
{ "prepareCmd": "bun run scripts/prepare-release.ts --version=${nextRelease.version}" }
11+
],
12+
[
13+
"@semantic-release/git",
14+
{
15+
"assets": ["CHANGELOG.md", "package.json", "bun.lock"],
16+
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
17+
}
18+
]
19+
]
20+
}

ARCHITECTURE.md

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ Running multiple local projects at the same time is messy when everything wants
1313
## System overview
1414

1515
`hack` is a Bun CLI that writes per-project Compose files under `.hack/` and manages a machine-wide
16-
proxy + logging stack under `~/.hack/`. Caddy routes `*.hack` based on container labels and the
17-
logging stack (Alloy + Loki + Grafana) captures Docker Compose logs.
16+
proxy, DNS helpers, and logging stack under `~/.hack/`.
17+
18+
- **Caddy** (docker-proxy) routes `*.hack` based on container labels.
19+
- **CoreDNS** resolves `*.hack` inside containers to the Caddy IP.
20+
- **Alloy + Loki + Grafana** capture logs and provide history.
21+
- **Schemas** are served by Caddy at `https://schemas.hack`.
1822

1923
```mermaid
20-
flowchart LR
24+
graph LR
2125
CLI["hack CLI (Bun)"]
2226
Browser["Browser"]
2327
2428
subgraph Host["Developer machine"]
2529
subgraph Global["Global infra (~/.hack)"]
2630
Caddy["Caddy docker-proxy"]
31+
CoreDNS["CoreDNS"]
2732
Loki["Loki"]
2833
Grafana["Grafana"]
2934
Alloy["Alloy (Docker logs)"]
@@ -43,20 +48,46 @@ flowchart LR
4348
Services -->|"container logs"| Alloy
4449
Alloy --> Loki --> Grafana
4550
51+
CoreDNS -->|"*.hack → Caddy IP"| Services
4652
Caddy -->|"https://*.hack"| Browser
4753
Grafana -->|"https://logs.hack"| Browser
54+
Caddy -->|"https://schemas.hack"| Browser
4855
```
4956

5057
## Global vs project scope
5158

5259
- Global scope (`~/.hack`)
53-
- Caddy proxy listens on 80/443 and routes `https://*.hack` via Docker labels
54-
- Logging stack captures all Compose logs (Alloy → Loki → Grafana)
60+
- Caddy proxy on 80/443 (routes via Docker labels)
61+
- CoreDNS for container DNS (`*.hack` → Caddy)
62+
- macOS DNS helper: dnsmasq + `/etc/resolver` for `*.hack``127.0.0.1`
63+
- Logging stack (Alloy → Loki → Grafana)
64+
- Schemas hosted under `https://schemas.hack`
5565
- Networks: `hack-dev` (ingress) and `hack-logging`
5666

5767
- Project scope (`.hack`)
58-
- `docker-compose.yml` defines services and optional Caddy labels
68+
- `docker-compose.yml` defines services + Caddy labels
5969
- `hack.config.json` stores project name, dev host, log preferences, OAuth alias
70+
- Optional overrides:
71+
- `.internal/compose.override.yml` (internal DNS/TLS injection)
72+
- `.branch/compose.<branch>.override.yml` (branch builds)
73+
74+
## Internal DNS + TLS (containers)
75+
76+
When `internal.dns` / `internal.tls` are enabled, `hack up` writes a Compose override that:
77+
78+
- sets each service’s DNS to the CoreDNS container
79+
- mounts Caddy’s local CA cert into each service
80+
- sets common SSL env vars (Node, curl, git, requests)
81+
82+
This lets containers use the same `https://*.hack` hostnames as the host machine.
83+
84+
```mermaid
85+
graph LR
86+
Service["Project container"] -->|"DNS query *.hack"| CoreDNS["CoreDNS (hack-dev)"]
87+
CoreDNS -->|"A record = Caddy IP"| Service
88+
Service -->|"HTTPS request"| Caddy["Caddy docker-proxy"]
89+
Caddy -->|"Routes by labels"| Upstream["Service upstream"]
90+
```
6091

6192
## Lifecycle (init → up → logs)
6293

@@ -87,35 +118,57 @@ sequenceDiagram
87118

88119
## Logging pipeline
89120

121+
`hack logs` supports two backends:
122+
123+
- **compose**: fast, direct `docker compose logs`
124+
- **loki**: searchable history + LogQL filters
125+
126+
NDJSON streaming (`hack logs --json`) emits `start`, `log`, and `end` events for MCP/TUI consumers.
127+
90128
```mermaid
91-
flowchart LR
129+
graph LR
92130
Containers["Compose containers"] -->|"stdout/stderr"| Alloy
93131
Alloy -->|"push"| Loki
94132
Loki -->|"query"| CLI["hack logs --loki"]
133+
CLI -->|"NDJSON/pretty"| Terminal
95134
Loki -->|"Explore"| Grafana
96-
CLI -->|"pretty output"| Terminal
97135
```
98136

137+
## Branch builds
138+
139+
`--branch <name>` generates a per-branch Compose override that:
140+
141+
- prefixes hostnames (e.g. `api.myapp.hack``api.<branch>.myapp.hack`)
142+
- prefixes the Compose project name
143+
144+
This enables parallel worktrees without port or hostname collisions.
145+
99146
## Files and directories
100147

101148
- `~/.hack/`
102149
- `caddy/docker-compose.yml`
150+
- `caddy/Corefile` (CoreDNS config)
103151
- `logging/docker-compose.yml`
104152
- `logging/alloy.alloy`
105153
- `logging/loki.yaml`
106154
- `logging/grafana/...`
107155
- `schemas/hack.config.schema.json`
108156
- `schemas/hack.branches.schema.json`
157+
- `certs/` (mkcert output for non-Caddy services)
109158
- `projects.json` (best-effort registry)
110159

111160
- `<repo>/.hack/`
112161
- `docker-compose.yml`
113162
- `hack.config.json`
114163
- `hack.branches.json` (optional)
164+
- `.internal/compose.override.yml`
165+
- `.branch/compose.<branch>.override.yml`
115166

116167
## Key design choices
117168

118169
- Docker Compose is the execution substrate for predictability and portability.
119170
- Caddy routes by container label so there is no per-repo reverse proxy config.
120-
- Logs default to `docker compose logs` for speed, with Loki for history and filtering.
121-
- Config lives alongside each repo in `.hack/` to keep repos isolated and portable.
171+
- CoreDNS gives containers the same `*.hack` namespace as the host.
172+
- Logs default to `docker compose logs` for speed; Loki is used for history and filtering.
173+
- Config lives alongside each repo in `.hack/` to keep repos isolated and portable.
174+
- Schemas are generated from templates and served locally for editor validation.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Changelog

0 commit comments

Comments
 (0)