feat(codegen): add TypeScript CLI output
All checks were successful
Security Scan / security (push) Successful in 8s
Test / test (push) Successful in 52s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 19:52:29 +00:00
parent 12a7d2497b
commit a928d01b9e
3 changed files with 46 additions and 13 deletions

View file

@ -1,14 +1,16 @@
//go:build !js
// Package main provides a build-time CLI for generating Web Component JS bundles.
// Reads a JSON slot map from stdin, writes the generated JS to stdout.
// Package main provides a build-time CLI for generating Web Component bundles.
// Reads a JSON slot map from stdin, writes the generated JS or TypeScript to stdout.
//
// Usage:
//
// echo '{"H":"nav-bar","C":"main-content"}' | go run ./cmd/codegen/ > components.js
// echo '{"H":"nav-bar","C":"main-content"}' | go run ./cmd/codegen/ -types > components.d.ts
package main
import (
"flag"
goio "io"
"os"
@ -18,7 +20,7 @@ import (
log "dappco.re/go/core/log"
)
func run(r goio.Reader, w goio.Writer) error {
func run(r goio.Reader, w goio.Writer, emitTypes bool) error {
data, err := goio.ReadAll(r)
if err != nil {
return log.E("codegen", "reading stdin", err)
@ -30,19 +32,27 @@ func run(r goio.Reader, w goio.Writer) error {
return log.E("codegen", "invalid JSON", err)
}
js, err := codegen.GenerateBundle(slots)
if err != nil {
return log.E("codegen", "generate bundle", err)
out := ""
if emitTypes {
out = codegen.GenerateTypeScriptDefinitions(slots)
} else {
out, err = codegen.GenerateBundle(slots)
if err != nil {
return log.E("codegen", "generate bundle", err)
}
}
_, err = goio.WriteString(w, js)
_, err = goio.WriteString(w, out)
if err != nil {
return log.E("codegen", "writing bundle", err)
return log.E("codegen", "writing output", err)
}
return nil
}
func main() {
emitTypes := flag.Bool("types", false, "emit TypeScript declarations instead of JavaScript")
flag.Parse()
stdin, err := coreio.Local.Open("/dev/stdin")
if err != nil {
log.Error("failed to open stdin", "scope", "codegen.main", "err", log.E("codegen.main", "open stdin", err))
@ -60,7 +70,7 @@ func main() {
_ = stdout.Close()
}()
if err := run(stdin, stdout); err != nil {
if err := run(stdin, stdout, *emitTypes); err != nil {
log.Error("codegen failed", "scope", "codegen.main", "err", err)
os.Exit(1)
}

View file

@ -14,7 +14,7 @@ func TestRun_WritesBundle_Good(t *testing.T) {
input := core.NewReader(`{"H":"nav-bar","C":"main-content"}`)
output := core.NewBuilder()
err := run(input, output)
err := run(input, output, false)
require.NoError(t, err)
js := output.String()
@ -28,7 +28,7 @@ func TestRun_InvalidJSON_Bad(t *testing.T) {
input := core.NewReader(`not json`)
output := core.NewBuilder()
err := run(input, output)
err := run(input, output, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid JSON")
}
@ -37,7 +37,7 @@ func TestRun_InvalidTag_Bad(t *testing.T) {
input := core.NewReader(`{"H":"notag"}`)
output := core.NewBuilder()
err := run(input, output)
err := run(input, output, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "hyphen")
}
@ -46,11 +46,26 @@ func TestRun_EmptySlots_Good(t *testing.T) {
input := core.NewReader(`{}`)
output := core.NewBuilder()
err := run(input, output)
err := run(input, output, false)
require.NoError(t, err)
assert.Empty(t, output.String())
}
func TestRun_WritesTypeScriptDefinitions_Good(t *testing.T) {
input := core.NewReader(`{"H":"nav-bar","C":"main-content"}`)
output := core.NewBuilder()
err := run(input, output, true)
require.NoError(t, err)
dts := output.String()
assert.Contains(t, dts, "declare global")
assert.Contains(t, dts, `"nav-bar": NavBar;`)
assert.Contains(t, dts, `"main-content": MainContent;`)
assert.Contains(t, dts, "export declare class NavBar extends HTMLElement")
assert.Contains(t, dts, "export declare class MainContent extends HTMLElement")
}
func countSubstr(s, substr string) int {
if substr == "" {
return len(s) + 1

View file

@ -145,6 +145,14 @@ echo '{"H":"site-header","C":"app-content","F":"site-footer"}' \
JSON keys are HLCRF slot letters (`H`, `L`, `C`, `R`, `F`). Values are custom element tag names (must contain a hyphen per the Web Components specification). Duplicate tag values are deduplicated.
Pass `-types` to emit ambient TypeScript declarations instead of JavaScript:
```bash
echo '{"H":"site-header","C":"app-content"}' \
| go run ./cmd/codegen/ -types \
> components.d.ts
```
To test the CLI:
```bash