Files
zeekay dd1486b7e2 feat(zapsdk): real Go Tier-1 via oapi-codegen + ZAP header stamps
- Fix zapgen binary path: dist/bin/zapgen.mjs (was .js; tsup outExtension
  emits .mjs and package bin points there) — the driver never found the CLI.
- go emit now produces a compiling Tier-1 HTTP client: zapgen OpenAPI ->
  oapi-codegen (models+client) -> stdlib-only go.mod. No more 'pending' stub.
- Stamp every generated .go/.ts with 'generated from a ZAP schema — DO NOT
  EDIT' + the exact regenerate command; JSON left as-is (no comment syntax).
- Thread a repo-relative schema path into every regenerate hint.
- Preserve emit_docs.
2026-07-05 13:29:04 -07:00

161 lines
6.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# zapsdk — the one and only way to generate a Hanzo client SDK from a .zap
# schema. ZAP-native (primary) + OpenAPI HTTP/WS/SSE (fallback), from one
# schema, per language. See ../README.md (ADR-0001).
#
# zapsdk <lang> <schema.zap> # one language
# zapsdk all # every language x every schema in ./schemas
#
# lang ∈ { go rust python ts cpp ruby php }
set -euo pipefail
GEN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ZAP_JS="${ZAP_JS:-$(cd "$GEN_DIR/../js" && pwd)}"
OUT="${OUT:-$GEN_DIR/out}"
log() { printf 'zapsdk: %s\n' "$*" >&2; }
die() { printf 'zapsdk: error: %s\n' "$*" >&2; exit 1; }
# relpath prints a path relative to GEN_DIR (so the regenerate hint is stable).
relpath() { # <path>
local abs; abs="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
printf '%s' "${abs#"$GEN_DIR"/}"
}
# stamp prepends a ZAP provenance + regenerate line to a generated source file
# that supports `//` comments (.go/.ts). Written once, at the top; JSON outputs
# carry no comment syntax and are left as emitted.
stamp() { # <file> <lang> <schema-rel>
local file="$1" lang="$2" schema="$3" tmp; tmp="$(mktemp)"
{
printf '// Code generated from a ZAP schema (zap-proto/gen) — DO NOT EDIT.\n'
printf '// Regenerate: bin/zapsdk %s %s\n\n' "$lang" "$schema"
cat "$file"
} >"$tmp"
mv "$tmp" "$file"
}
# Build/locate the canonical TS zapgen (dependency-free core; node builtins).
# tsup emits the CLI as dist/bin/zapgen.mjs (see js/tsup.config.ts + package bin).
zapgen() {
local built="$ZAP_JS/dist/bin/zapgen.mjs"
if [ ! -f "$built" ]; then
log "building zapgen in $ZAP_JS (one-time)"
( cd "$ZAP_JS" && { [ -d node_modules ] || pnpm install --frozen-lockfile 2>/dev/null || pnpm install; } && pnpm build )
fi
node "$built" "$@"
}
# Locate (installing once if needed) the oapi-codegen used for the Go Tier-1
# HTTP client. Canonical: github.com/oapi-codegen/oapi-codegen/v2.
oapi_codegen() {
local bin; bin="$(command -v oapi-codegen || true)"
[ -n "$bin" ] || bin="$(go env GOPATH)/bin/oapi-codegen"
if [ ! -x "$bin" ]; then
log "installing oapi-codegen (one-time)"
GOFLAGS=-mod=mod go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
bin="$(go env GOPATH)/bin/oapi-codegen"
fi
"$bin" "$@"
}
# emit_go generates the Tier-1 Go HTTP client: zapgen OpenAPI → oapi-codegen
# (models + client) → stdlib-only module. The generated client speaks
# POST /<service>/<method>, the exact route the ZAP HTTP transport serves.
emit_go() { # <schema> <outdir> <base> <schema-rel>
local schema="$1" outdir="$2" base="$3" rel="$4"
zapgen --emit=openapi -out "$outdir" "$schema"
cat >"$outdir/oapi-codegen.yaml" <<EOF
# Code generated by zap-proto/gen (zapsdk go) — do not hand-edit.
# Regenerate: bin/zapsdk go $rel
package: $base
output: client.gen.go
generate:
models: true
client: true
output-options:
skip-prune: true
EOF
( cd "$outdir" && oapi_codegen -config oapi-codegen.yaml "$base.openapi.json" )
stamp "$outdir/client.gen.go" go "$rel"
# stdlib-only client → the module needs no require directives.
cat >"$outdir/go.mod" <<EOF
// Code generated from a ZAP schema (zap-proto/gen) — DO NOT EDIT.
// Regenerate: bin/zapsdk go $rel
module github.com/hanzoai/go-sdk/$base
go 1.23
EOF
}
emit_native() { # <lang> <schema> <outdir> <base> <schema-rel>
local lang="$1" schema="$2" outdir="$3" base="$4" rel="$5"
case "$lang" in
ts)
# Tier-2 zero-copy native client + Tier-1 OpenAPI, both from one schema.
zapgen --emit=ts,openapi -out "$outdir" "$schema"
stamp "$outdir/${base}_zap.ts" ts "$rel"
;;
go)
emit_go "$schema" "$outdir" "$base" "$rel"
;;
python|rust|cpp|ruby)
# Tier-1 fallback: emit the OpenAPI contract; the per-language HTTP client
# comes from that language's best-in-class generator (openapi-python-client
# / progenitor / openapi-generator). Native ZAP emit backend pending.
zapgen --emit=openapi -out "$outdir" "$schema"
log "$lang: ZAP-native emit backend pending; OpenAPI emitted (Tier-1 fallback client)"
;;
php)
zapgen --emit=openapi -out "$outdir" "$schema"
log "php: fallback-only (no zap-proto/php runtime yet); OpenAPI emitted"
;;
*) die "unknown lang '$lang' (go rust python ts cpp ruby php)" ;;
esac
}
# emit_docs writes the per-language SDK docs into the hanzo-docs reserved slot
# content/docs/sdks/<lang>/ (gitignored there — regenerate, never hand-edit).
# Scope: usage/quickstart/typed-client + transport selection only; it LINKS to
# the canonical API reference (generated from hanzoai/openapi) and never
# re-lists endpoints. Default target: out/docs/<lang> unless DOCS_ROOT points at
# a hanzo-docs .../content/docs/sdks checkout.
emit_docs() { # <lang> <service> <schema-rel>
local lang="$1" svc="$2" rel="$3"
local docdir="${DOCS_ROOT:-$OUT/docs}/$lang"
mkdir -p "$docdir"
{
printf -- '---\ntitle: %s SDK\ndescription: Generated ZAP SDK — quickstart and typed-client usage.\n---\n\n' "$lang"
printf '{/* Generated by zap-proto/gen from %s. DO NOT EDIT — regenerate: bin/zapsdk %s %s */}\n\n' "$rel" "$lang" "$rel"
printf '# %s SDK\n\nZAP-native transport is primary; HTTPS/WS/SSE is the fallback. Install, construct the client, pick a transport, call a method. For the full endpoint reference see the [canonical API reference](/docs/api) — this page does not re-list endpoints.\n' "$lang"
} > "$docdir/${svc}.mdx"
log "docs $lang <- $svc -> ${docdir#"$GEN_DIR"/}/${svc}.mdx"
}
one() { # <lang> <schema>
local lang="$1" schema="$2"
[ -f "$schema" ] || die "schema not found: $schema"
local base outdir rel
base="$(basename "$schema" .zap)"
outdir="$OUT/$lang/$base"
rel="$(relpath "$schema")"
mkdir -p "$outdir"
log "generate $lang <- $rel -> ${outdir#"$GEN_DIR"/}"
emit_native "$lang" "$schema" "$outdir" "$base" "$rel"
emit_docs "$lang" "$base" "$rel"
log "done $lang/$base"
}
main() {
[ $# -ge 1 ] || die "usage: zapsdk <lang> <schema.zap> | zapsdk all"
if [ "$1" = "all" ]; then
for lang in go rust python ts cpp ruby php; do
for s in "$GEN_DIR"/schemas/*.zap; do one "$lang" "$s"; done
done
return
fi
[ $# -eq 2 ] || die "usage: zapsdk <lang> <schema.zap>"
one "$1" "$2"
}
main "$@"