Skip to content

Commit 2ba8a32

Browse files
danielewoodclaude
andauthored
refactor: auto-generate flag tables and replace --format with global --json (#80)
* refactor: auto-generate flag tables and replace --format with global --json Add gendocs tool that generates README flag tables from Cobra command definitions via `go generate`, with CI check and pre-commit hook to keep them in sync. Extract ValidationError to dedicated errors.go. Replace per-command `--format text|json` flag on inspect, verify, connect, scan, ocsp, and crl with a global `--json` persistent flag. Rename `convert --to` to `convert --format` for consistency with `bundle --format` (both specify container format). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add global --json flag and restore per-command --format Keep per-command --format on display commands (inspect, verify, connect, scan, ocsp, crl) while adding a global --json persistent flag that works on ALL commands. --json overrides --format when both are set. Add JSON output to keygen, csr, sign (self-signed and csr), bundle, and convert commands. Revert convert --format back to --to. Fix CHANGELOG refs and add missing PR references. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address review findings for --json output consistency - Fix connect JSON sha256_fingerprint to use colon-hex format matching inspect and sha1_fingerprint (CLI-4 consistency) - Fix bundle --json to base64 encode binary p12/jks output instead of casting raw bytes to string (produces valid JSON) - Fix sign --json + -o to write file AND output JSON (matching bundle/convert behavior; previously --json silently skipped file write) - Simplify redundant conditionals in keygen/csr JSON output (omitempty handles empty strings) - Move --json changelog entries from Changed to Added section (CL-2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address gendocs review findings (CS-5, OBS-1) - Add spliceMarkerInput struct for 3-parameter spliceMarker (CS-5) - Replace fmt.Fprintf(os.Stderr) with slog calls for diagnostics (OBS-1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: standardize --json output behavior across all commands - Standardize --json override to use local variable copy instead of mutating package-level format variables (all 6 display commands) - Decouple keygen/csr stderr file messages from --json (always print when -o is set, regardless of --json) - Add file path fields to keygenJSON and csrJSON for script discovery - Fix bundle --json -o with PEM format to emit file metadata instead of full PEM data (matches convert pattern) - Add doc comment on connectCertJSON struct - Add breaking change note for connect sha256_fingerprint format change Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove backtick type placeholders, rename csr --cert to --from-cert pflag's UnquoteUsage consumes backtick-quoted text as type placeholders, corrupting --help output for --format, --trust-store, --log-level, --algorithm, and --curve flags. Remove all backticks from usage strings. Rename csr --cert to --from-cert for clarity — avoids confusion with certificate file arguments used by other commands. Also fix convert --json without -o missing the format field in output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * build: update all Go dependencies - github.com/spf13/pflag v1.0.9 → v1.0.10 - golang.org/x/exp → 2025-02-18 - modernc.org/libc v1.67.6 → v1.68.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use actual file path in gendocs error messages spliceMarker error messages hardcoded "README.md" instead of using the path argument. Include the path field in spliceMarkerInput so errors reference the correct file when gendocs is run with a custom path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23adf10 commit 2ba8a32

24 files changed

Lines changed: 651 additions & 172 deletions

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ jobs:
6464
prettier_paths: "web/**/*.{js,ts,css,html}"
6565
prettier_exclude: "!web/public/wasm_exec.js"
6666

67+
docs:
68+
name: Docs
69+
runs-on: ubuntu-latest
70+
steps:
71+
- uses: actions/checkout@v6
72+
- uses: actions/setup-go@v6
73+
with:
74+
go-version: stable
75+
check-latest: true
76+
cache: true
77+
- name: Verify flag tables are up to date
78+
run: |
79+
go generate ./...
80+
git diff --exit-code README.md
81+
6782
# ── Gate ──────────────────────────────────────────────
6883

6984
ci-ok:
@@ -75,6 +90,7 @@ jobs:
7590
- wasm
7691
- web
7792
- lint
93+
- docs
7894
runs-on: ubuntu-latest
7995
steps:
8096
- name: Check results

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ repos:
2020
- id: go-build
2121
- id: go-test
2222

23+
# ── Docs ──
24+
- repo: local
25+
hooks:
26+
- id: gendocs
27+
name: gendocs
28+
entry: bash -c 'go generate ./... && git diff --exit-code README.md'
29+
language: system
30+
files: (^cmd/certkit/.*\.go$|^README\.md$|^doc\.go$)
31+
pass_filenames: false
32+
2333
# ── JS / TS ──
2434
- repo: local
2535
hooks:

CHANGELOG.md

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Auto-generate CLI flag tables in README from Cobra command definitions via `go generate` ([#80])
13+
- Add `gendocs` pre-commit hook and CI check to verify flag tables stay in sync ([#80])
14+
- Add global `--json` persistent flag — all commands now support JSON output; overrides `--format` when both are set ([#80])
15+
- Add JSON output to `keygen`, `csr`, `sign`, `bundle`, and `convert` commands ([#80])
1216
- `connect` automatically checks OCSP revocation status on the leaf certificate (best-effort; shows "skipped" or "unavailable" when check cannot complete) ([#78])
1317
- Add `--crl` flag to `connect` for opt-in CRL revocation checking via distribution points ([#78])
1418
- Add `FetchCRL` library function for downloading CRLs from HTTP URLs with SSRF validation ([#78])
@@ -30,25 +34,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3034
- Add `ErrUnknownOtherNameType` sentinel error for invalid OtherName type strings ([#74])
3135
- Add `ErrEmptySANExtension` sentinel error for empty SAN extension input ([#74])
3236
- Add `aia_fetched` field to inspect results and "via aia" badge in web UI for AIA-fetched certificates ([#73])
33-
- Add multi-entry JKS support to `convert --key` — when multiple keys match different certificates, JKS output creates a multi-alias keystore with one `PrivateKeyEntry` per match
34-
- Add `EncodeJKSEntries` library function for creating multi-entry JKS keystores with alias sanitization and deduplication
35-
- Add `CollectCertificateSANs` library function for canonical SAN aggregation (DNS, IP, email, URI, OtherName) across all commands
36-
- Add `ParsePEMPrivateKeys` library function for extracting all private keys from a multi-key PEM bundle, skipping non-key blocks
37-
- Add chain diagnostics to `connect` command — detect root certificates in chain (RFC 8446 §4.4.2) and duplicate certificates
38-
- Add AIA walking to `connect` command — automatically fetch missing intermediates when server sends leaf-only chain, with `missing-intermediate` diagnostic warning
39-
- Add mTLS detection to `connect` command — shows whether the server requests a client certificate, acceptable CAs, and accepted signature algorithms
40-
- Add ALPN (negotiated application protocol) to `connect` command output
41-
- Add `--verbose` / `-v` global flag for extended certificate details in `connect`, `verify`, `scan`, and `ocsp` output (serial, key info, signature algorithm, key usage, EKU, fingerprints, SKI/AKI)
42-
- Add CRL number and authority key identifier to `crl` output
43-
- Add `convert` command for converting between PEM, DER, PKCS#12, JKS, and PKCS#7 formats
44-
- Add `sign` command with `self-signed` and `csr` subcommands for certificate signing
45-
- Add `connect` command for TLS connection testing with certificate chain display
46-
- Add `--diagnose` flag to `verify` command for chain failure diagnostics
47-
- Add `ocsp` command for checking certificate revocation status via OCSP
48-
- Add `crl` command for parsing and inspecting Certificate Revocation Lists
37+
- Add multi-entry JKS support to `convert --key` — when multiple keys match different certificates, JKS output creates a multi-alias keystore with one `PrivateKeyEntry` per match ([#75])
38+
- Add `EncodeJKSEntries` library function for creating multi-entry JKS keystores with alias sanitization and deduplication ([#75])
39+
- Add `CollectCertificateSANs` library function for canonical SAN aggregation (DNS, IP, email, URI, OtherName) across all commands ([#75])
40+
- Add `ParsePEMPrivateKeys` library function for extracting all private keys from a multi-key PEM bundle, skipping non-key blocks ([#75])
41+
- Add chain diagnostics to `connect` command — detect root certificates in chain (RFC 8446 §4.4.2) and duplicate certificates ([#75])
42+
- Add AIA walking to `connect` command — automatically fetch missing intermediates when server sends leaf-only chain, with `missing-intermediate` diagnostic warning ([#75])
43+
- Add mTLS detection to `connect` command — shows whether the server requests a client certificate, acceptable CAs, and accepted signature algorithms ([#75])
44+
- Add ALPN (negotiated application protocol) to `connect` command output ([#75])
45+
- Add `--verbose` / `-v` global flag for extended certificate details in `connect`, `verify`, `scan`, and `ocsp` output (serial, key info, signature algorithm, key usage, EKU, fingerprints, SKI/AKI) ([#75])
46+
- Add CRL number and authority key identifier to `crl` output ([#75])
47+
- Add `convert` command for converting between PEM, DER, PKCS#12, JKS, and PKCS#7 formats ([#75])
48+
- Add `sign` command with `self-signed` and `csr` subcommands for certificate signing ([#75])
49+
- Add `connect` command for TLS connection testing with certificate chain display ([#75])
50+
- Add `--diagnose` flag to `verify` command for chain failure diagnostics ([#75])
51+
- Add `ocsp` command for checking certificate revocation status via OCSP ([#75])
52+
- Add `crl` command for parsing and inspecting Certificate Revocation Lists ([#75])
4953

5054
### Changed
5155

56+
- **Breaking:** Rename `csr --cert` flag to `--from-cert` for clarity — avoids confusion with certificate file arguments in other commands ([#80])
57+
- **Breaking:** `connect` JSON `sha256_fingerprint` format changed from lowercase hex to colon-separated uppercase hex for CLI-4 consistency with `inspect` and `sha1_fingerprint` ([#80])
5258
- **Breaking:** Rename `CRLCheckResult.DistributionPoint` to `CRLCheckResult.URL` (JSON: `url`) and `OCSPResult.ResponderURL` to `OCSPResult.URL` (JSON: `url`) — consistent field name for the checked endpoint across both revocation types (CLI-4) ([#78])
5359
- **Breaking:** Rename OCSP JSON field `serial_number` to `serial` for CLI-4 consistency with all other commands ([#78])
5460
- **Breaking:** `FetchCRL` now takes `FetchCRLInput` struct instead of a URL string — enables `AllowPrivateNetworks` for user-provided URLs ([#78])
@@ -63,6 +69,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6369

6470
### Fixed
6571

72+
- Fix backtick-quoted values in flag usage strings being consumed by pflag as type placeholders — all `--format`, `--trust-store`, `--log-level`, `--algorithm`, and `--curve` flags now display correctly in `--help` output ([#80])
73+
- Fix `convert --json` without `-o` missing `format` field in JSON output ([#80])
6674
- Fix data race in `TestCheckLeafCRL` — CRL bytes are now generated before starting the test HTTP server (CC-3) ([#78])
6775
- Fix `CheckLeafCRL` panic on nil `Leaf` or `Issuer` — now returns "unavailable" result instead of panicking ([#78])
6876
- Fix `verify` help text claiming "Exits with code 2 if revoked" — actually exits 2 for any verification error including revocation ([#78])
@@ -96,8 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
96104
- **Breaking:** Rename `CRLContainsCert` to `CRLContainsCertificate` — exported function names must not abbreviate per CS-2 ([#75])
97105
- Fix `verify --diagnose` running chain diagnostics on non-chain errors (key mismatch, expiry warnings) — now gates on `chain_valid == false` only ([#75])
98106
- Fix silent `continue` in `connect` mTLS CA DN parsing when `asn1.Unmarshal` fails — now logs with `slog.Debug` (ERR-5) ([#75])
99-
- Fix `convert --key` only using first key from multi-key PEM file and including all certs in output — now matches the key to its leaf certificate and extracts only the chain for that leaf
100-
- Fix AIA proxy rejecting `cacerts.geotrust.com` and `cacerts.thawte.com` — consolidate all per-host CA entries into suffix matches for broader coverage of CA subdomains
107+
- Fix `convert --key` only using first key from multi-key PEM file and including all certs in output — now matches the key to its leaf certificate and extracts only the chain for that leaf ([#75])
108+
- Fix AIA proxy rejecting `cacerts.geotrust.com` and `cacerts.thawte.com` — consolidate all per-host CA entries into suffix matches for broader coverage of CA subdomains ([#75])
101109
- Fix `marshalOtherNameGN` encoding non-SRV OtherName values as PrintableString instead of UTF8String ([#74])
102110
- Fix `MarshalSANExtension` accepting nil URI entries and invalid IP addresses without validation ([#74])
103111
- Fix `parseOtherNameEntriesFromSANBytes` silently discarding parse errors without logging (ERR-5) ([#74])
@@ -156,12 +164,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
156164
- Add standard SAN type assertions to `TestMarshalSANExtension_CertificateRoundTrip` — DNS, email, IP, URI round-trip per T-6 ([#74])
157165
- Remove T-9-violating key rotation assertion from `TestGenerateCSRFromCSR_PreservesOtherNames` ([#74])
158166
- Add `TestMarshalSANExtension_ValidationErrors` — rejects empty and non-ASCII DNS, email, URI values ([#74])
159-
- Add `TestCreateSelfSigned` and `TestSignCSR` table-driven tests for certificate signing
160-
- Add `TestSignCSR_ChainVerifies` round-trip chain verification test
161-
- Add `TestConnectTLS` with mock TLS server for connection probing
162-
- Add `TestCheckOCSP_MockResponse` table-driven test with mock OCSP server covering good and revoked responses
163-
- Add `TestParseCRL`, `TestCRLContainsCertificate`, and `TestCRLInfoFromList` for CRL handling
164-
- Add `TestDiagnoseChain` table-driven tests for chain diagnostics
167+
- Add `TestCreateSelfSigned` and `TestSignCSR` table-driven tests for certificate signing ([#75])
168+
- Add `TestSignCSR_ChainVerifies` round-trip chain verification test ([#75])
169+
- Add `TestConnectTLS` with mock TLS server for connection probing ([#75])
170+
- Add `TestCheckOCSP_MockResponse` table-driven test with mock OCSP server covering good and revoked responses ([#75])
171+
- Add `TestParseCRL`, `TestCRLContainsCertificate`, and `TestCRLInfoFromList` for CRL handling ([#75])
172+
- Add `TestDiagnoseChain` table-driven tests for chain diagnostics ([#75])
165173

166174
## [0.8.1] - 2026-02-25
167175

@@ -828,6 +836,7 @@ Initial release.
828836
[#75]: https://github.com/sensiblebit/certkit/pull/75
829837
[#76]: https://github.com/sensiblebit/certkit/pull/76
830838
[#78]: https://github.com/sensiblebit/certkit/pull/78
839+
[#80]: https://github.com/sensiblebit/certkit/pull/80
831840
[#73]: https://github.com/sensiblebit/certkit/pull/73
832841
[#64]: https://github.com/sensiblebit/certkit/pull/64
833842
[#63]: https://github.com/sensiblebit/certkit/pull/63

CLAUDE.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,11 @@ Invoke `/ralph` for comprehensive test validation. Full protocol in `.claude/ski
218218

219219
- **CLI-1 (MUST)** Stdout is for data, stderr is for everything else. PEM output, JSON, scan summaries — anything a user might pipe goes to stdout. File paths, progress messages, warnings go to stderr. Follow the OpenSSL convention.
220220
- **CLI-2 (MUST)** Never write files without explicit consent. Commands that produce PEM output print to stdout by default. Files are only written when the user provides `-o`. Export requires `--bundle-path <dir>`. No silent writes to the current directory.
221-
- **CLI-3 (MUST)** Every command that displays certificate/key info must support `--format json`.
221+
- **CLI-3 (MUST)** Every command supports `--json` (global persistent flag). Display commands (`inspect`, `verify`, `connect`, `scan`, `ocsp`, `crl`) also support `--format json`. `--json` overrides `--format` when both are set.
222222
- **CLI-4 (MUST)** JSON field names must be consistent across commands. Same concept uses the same key everywhere (e.g., SKI is always `subject_key_id`).
223223
- **CLI-5 (MUST)** All dates in RFC 3339 format. No RFC 1123, no custom layouts.
224-
- **CLI-6 (MUST)** Exit codes: `0` = success, `1` = general error, `2` = validation failure (chain invalid, key mismatch, expired).
225-
- **CLI-7 (MUST)** JSON output is a single object or array, ending with `\n`. No mixed text/JSON. No log lines on stdout when `--format json` is used.
224+
- **CLI-6 (MUST)** Exit codes: `0` = success, `1` = general error, `2` = validation failure (chain invalid, key mismatch, expired, revoked).
225+
- **CLI-7 (MUST)** JSON output is a single object or array, ending with `\n`. No mixed text/JSON. No log lines on stdout when `--json` is used.
226226

227227
---
228228

@@ -311,7 +311,7 @@ Examples: `feat: add PKCS#7 export`, `fix(jks): handle empty alias`, `ci: add go
311311

312312
### CI checks
313313

314-
Every PR runs 10 parallel checks (`.github/workflows/ci.yml`):
314+
Every PR runs 11 parallel checks (`.github/workflows/ci.yml`):
315315

316316
| Check | What it validates |
317317
|---|---|
@@ -322,6 +322,7 @@ Every PR runs 10 parallel checks (`.github/workflows/ci.yml`):
322322
| Lint (golangci-lint) | errcheck, staticcheck, unused, etc. |
323323
| Vulnerability Check | `govulncheck ./...` |
324324
| WASM Build | `GOOS=js GOARCH=wasm` vet + build |
325+
| Docs | `go generate ./...` + `git diff --exit-code README.md` |
325326
| Web | vitest + wrangler build |
326327
| Lint | prettier + markdownlint |
327328
| CI | Gate — fails if any above failed |
@@ -341,7 +342,7 @@ pre-commit install --hook-type commit-msg
341342
pre-commit run --all-files # Manual run against all files
342343
```
343344

344-
Configured hooks: `no-commit-to-branch`, `branch-name`, `commit-message` (commit-msg stage), `goimports`, `go-fix`, `go-vet`, `golangci-lint`, `wasm`, `go-build`, `go-test`, `prettier`, `vitest`, `wrangler-build`, `markdownlint`.
345+
Configured hooks: `no-commit-to-branch`, `branch-name`, `commit-message` (commit-msg stage), `goimports`, `go-fix`, `go-vet`, `golangci-lint`, `wasm`, `go-build`, `go-test`, `gendocs`, `prettier`, `vitest`, `wrangler-build`, `markdownlint`.
345346

346347
### Tooling gates
347348

@@ -352,6 +353,7 @@ Configured hooks: `no-commit-to-branch`, `branch-name`, `commit-message` (commit
352353
- **G-5 (MUST)** `GOOS=js GOARCH=wasm go vet ./cmd/wasm/` and `go build` pass.
353354
- **G-6 (MUST)** `cd web && npm test` passes (vitest).
354355
- **G-7 (MUST)** `cd web && wrangler pages functions build` compiles (local only, no credentials).
356+
- **G-8 (MUST)** `go generate ./...` followed by `git diff --exit-code README.md` — flag tables in README must match Cobra command definitions. Run `go generate` after changing any CLI flags.
355357

356358
---
357359

EXAMPLES.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,21 +413,21 @@ This creates `key.pem`, `pub.pem`, and `csr.pem` (if a CN is provided) in the sp
413413
You have an existing cert and need to create a CSR to request a renewal from your CA. This copies the subject and SANs from the old cert:
414414

415415
```sh
416-
certkit csr --cert existing-cert.pem
416+
certkit csr --from-cert existing-cert.pem
417417
```
418418

419419
This prints the CSR and a newly generated key to stdout in PEM format. Send the CSR to your CA.
420420

421421
If you want to reuse your existing key:
422422

423423
```sh
424-
certkit csr --cert existing-cert.pem --key existing-key.pem
424+
certkit csr --from-cert existing-cert.pem --key existing-key.pem
425425
```
426426

427427
Write to separate files in a directory instead of stdout:
428428

429429
```sh
430-
certkit csr --cert existing-cert.pem -o ./out
430+
certkit csr --from-cert existing-cert.pem -o ./out
431431
```
432432

433433
---
@@ -578,20 +578,23 @@ certkit uses meaningful exit codes:
578578
| **1** | General error (bad input, missing file, etc.) |
579579
| **2** | Validation failure (chain invalid, key mismatch, expired, revoked) |
580580

581-
Use `--format json` on any command for machine-readable output. Data always goes to stdout, warnings and progress to stderr, so piping works cleanly:
581+
Use `--json` on any command for machine-readable output. Display commands also accept `--format json`. Data always goes to stdout, warnings and progress to stderr, so piping works cleanly:
582582

583583
```sh
584584
# Check cert in CI -- fails with exit code 2 if expiring within 30 days
585585
certkit verify cert.pem --expiry 30d
586586

587587
# Parse cert info programmatically
588-
certkit inspect cert.pem --format json | jq '.subject'
588+
certkit inspect cert.pem --json | jq '.[0].subject'
589589

590590
# Verify and capture result
591591
certkit verify cert.pem --format json > result.json
592592

593593
# Check revocation in a pipeline
594-
certkit ocsp cert.pem --issuer issuer.pem --format json | jq '.status'
594+
certkit ocsp cert.pem --issuer issuer.pem --json | jq '.status'
595+
596+
# Generate a key and capture JSON
597+
certkit keygen --json | jq '.key_pem'
595598
```
596599

597600
### Verbose output

0 commit comments

Comments
 (0)