kclplugin

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

forge:exclude-contract kclplugin is KCL-plugin framework glue (registers a jwk resolver plugin with the KCL runtime), not a contract-shaped service. Opt out of the require-contract rule.

Package kclplugin registers forge's in-process KCL plugin namespace (kcl_plugin.forge.*), letting KCL pull host-runtime values during evaluation instead of forge having to pre-enumerate and inject them.

It provides two port primitives, plus derive_jwk:

  • resolve_port(name, preferred) — a pure-Go free-port allocator. KCL declares `port = forge.resolve_port("reliant-web", 3000)` inline, by any name it likes, and binds it to a variable other declarations reference (the frontend's port, env-var URLs, CORS origins). One declaration, referenced everywhere — forge owns the allocation, KCL owns the plumbing. The allocation is AVAILABILITY-CHECKED (it steps off busy ports), so it suits ports that may float.

  • allocate_port(base, key) — a DETERMINISTIC, memoized keyed allocator for parallel dev stacks: returns base + block(key)*100, where forge assigns a stable small block per key (the index is internal, never surfaced in KCL) and persists it under a file lock so up and deploy agree. key "" ⇒ base unchanged. Unlike resolve_port it does NOT step off busy ports — it must match externally-fixed ports (a k3d pre-mapped host port; the host reliant's listen port). The block engine lives in internal/devstack; this package holds a settable hook (UseBlockAllocator) so the CLI wires the lock-guarded registry in.

The plugin bridge that lets KCL call back into Go is CGO-only (see register_cgo.go / register_nocgo.go). forge's distributed binaries are built with CGO so the namespace is always available; a CGO-free build still compiles (the render path is CGO-free via purego) but Register is a no-op, so KCL importing kcl_plugin.forge would fail to render there.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DeriveES256JWK

func DeriveES256JWK(privateKeyPEM, kid, alg string) (map[string]any, error)

DeriveES256JWK derives the PUBLIC JWK document from an ES256 (P-256) private-key PEM, so a forge.TestJWKS can publish the public half of the exact key its signer uses — the JWKS and the signer can never drift.

Ported from control-plane's e2e deriveTestJWK: parse the EC private key, left-pad the 32-byte P-256 field elements (so leading-zero coordinates still encode to the fixed JWK width), and base64url-encode x/y. kid/alg are caller-supplied; kty/crv/use are fixed for ES256.

Returns a map[string]any (KCL-plugin-friendly) shaped exactly like one entry in a JWKS `keys` array.

func Register

func Register()

Register installs the kcl_plugin.forge namespace into the process-global KCL plugin registry. Idempotent and safe to call before every render.

Requires CGO — KCL's plugin bridge is //go:build cgo. forge ships prebuilt CGO binaries (homebrew + release pipeline), so the namespace is always available in distribution. The CGO-free build gets the no-op Register in register_nocgo.go.

func UseBlockAllocator

func UseBlockAllocator(fn func(base int, key string) (int, error))

UseBlockAllocator arms allocate_port with fn for this process. Call once before rendering, on the up/deploy path. Both `forge up` AND `forge deploy` arm the SAME (lock-guarded, persistent) allocator, so the two commands resolve identical ports for a given key — the up-vs-deploy fix.

func UsePortStore

func UsePortStore(path string) (restore func())

UsePortStore swaps the global resolver for one that persists assignments to path (cross-run port stability), making that file the SINGLE SOURCE OF TRUTH for resolve_port: once allocated (availability-checked), a (role) -> port mapping is read back identically on every subsequent render. Both `forge up` AND `forge deploy` call this with the same instance-scoped path, so the two commands resolve identical ports — the fix for the up-vs-deploy port drift.

Call once before rendering, only on the dev-launch / deploy path — not for read-only renders like `forge ci`, which shouldn't write a ports file. Safe to call repeatedly.

It returns a restore func that reverts the store file to its bytes at call time (or removes it if it didn't exist). A render shifts + persists a port when the preferred one is busy; a caller whose render is then REJECTED (e.g. up's already-running guard) calls restore so the rejected attempt can't drift the stable assignments. Callers that commit the render ignore it.

Types

type PortResolver

type PortResolver struct {
	// contains filtered or unexported fields
}

PortResolver hands out host ports, one per logical name, stable for the life of the resolver: repeated Resolve("x", …) calls — including across the several KCL renders a single forge command performs — return the same port, so a component's bound port and the URLs composed from it never drift.

Allocation order for a never-seen name:

  1. the port it got on a previous run (when a store is configured and that port is still free) — stable dev ports across runs;
  2. the requested `preferred` port, then a short scan upward from it (3000 → 3001 → …) so dev ports stay human-friendly and parity-stable;
  3. an OS-assigned free port as the last resort.

It never hands the same port to two names. Like any probe-then-bind scheme (cf. cloud-dev.sh's _pick_port), there is an inherent TOCTOU window between resolving a port and the launched process binding it; acceptable for the dev loop this serves.

func NewPersistentPortResolver

func NewPersistentPortResolver(path string) *PortResolver

NewPersistentPortResolver remembers assignments in a JSON file at path, so a name reuses the same port across forge runs. Best-effort: a read/write error never fails a render — it degrades to fresh allocation.

func NewPortResolver

func NewPortResolver() *PortResolver

NewPortResolver returns an in-memory PortResolver that allocates ports for the current run only, without persisting assignments across runs.

func (*PortResolver) Resolve

func (r *PortResolver) Resolve(name string, preferred int) (int, error)

Resolve returns the port for name (see allocation order on PortResolver).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL