templates

package
v1.0.13 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package templates owns the embedded forge template tree and renders it into scaffolded code.

The behavioural seam exposed by the package is the Service interface: callers (codegen, generator, packs, docs) hold a Service handle and ask it to render a template by category + name. The data carriers (TemplateCategory, TemplateEngine, *TemplateData structs) are utility types kept on the package surface for backward compatibility with scaffolders that walk individual category trees.

Code generated by forge. DO NOT EDIT. forge:hash=13ca171bf0ba2d2978e75978992669b1690aa557859459efc35316aa6916f5fe forge-owned: regenerated every run — do not edit (forge project disown to take ownership) Source: contract.go in this package.

To customize: edit contract.go (the interface IS the public surface) and re-run "forge generate". This file is regenerated unconditionally.

Canonical import ordering for emitted TypeScript/TSX — the frontend analogue of the gofmt/goimports pass the Tier-1 writer runs over every .go render before stamping it (internal/checksums/goformat.go).

── Why the emitter cannot hand-order these imports ───────────────────

A scaffolded CRUD page emits a CONDITIONAL import set. Whether `lucide-react`, the shared runtime, an enum module, or the debounce hook appears at all depends on the entity's shape — has a Create RPC, has an enum column, has a scalar filter — and two of the specifiers are not known until render time: the service's generated hooks module (`@/hooks/<service>-service-hooks`) and the protobuf-es module the entity's enums live in (`@/gen/services/<service>/v1/<file>_pb`).

Any hand-written line order is therefore correct for exactly the entity shapes its author happened to try and wrong for the rest: `@/hooks/catalog-service-hooks` sorts BEFORE `@/hooks/use-query-resource` and `@/hooks/warehouse-service-hooks` sorts AFTER it, so no static arrangement of template lines satisfies both. Patching the lines that a particular project's entity happened to expose fixes that project and leaves the next one warning.

So the order is DERIVED from the specifiers that were actually emitted: the page writer runs its render through CanonicalTSImportOrder, which reads the leading import block, classifies each statement into eslint's configured groups, sorts within the group, and rewrites the block. The template author lists the imports a page needs and never thinks about order again.

── One source of truth ───────────────────────────────────────────────

The grouping/sorting rules below MIRROR the `import/order` rule and the `import/internal-regex` setting in internal/templates/frontend/nextjs/eslint.config.mjs — the config the scaffold's own `npm run lint` enforces. The config is JavaScript; parsing it to derive these constants would mean shipping a JS parser to read four values. Instead TestTSImportOrderMatchesESLintConfig re-reads the emitted config and FAILS when the two disagree, so a rule change cannot silently leave the emitter behind.

The classification itself mirrors eslint-plugin-import's importType() (lib/core/importType.js) and its alphabetize comparator (getSorter in lib/rules/order.js) — notably the SEGMENT-WISE path comparison, under which "next/link" sorts before "next-auth" even though the raw strings compare the other way.

Index

Constants

View Source
const BufVersion = "1.50.0"

BufVersion is the one buf release forge pins wherever buf's output must be reproducible: the scaffolded ci.yml installs it (bufbuild/buf-setup-action), and `forge ci gate` holds the buf on PATH to it before verify-generated and buf lint run. verify-generated regenerates the tree and demands identical bytes, so buf must not move under it. 1.50.0 is buf-setup-action@v1's own default. TestGateToolPinsMatchTheScaffoldedWorkflow (internal/templates/ci_gate_test.go) fails when the workflow names another.

View Source
const CommitlintVersion = "19.8.1"

CommitlintVersion is the release of @commitlint/cli and @commitlint/config-conventional the scaffolded commit-msg hook installs (.pre-commit-config.yaml's local commitlint hook). The two packages are released in lockstep, so one pin covers both. Exact, for the reason PrettierVersion is: a ruleset that moves on its own rejects a commit message the previous install accepted.

View Source
const GoLicensesVersion = "v1.6.0"

GoLicensesVersion is the go-licenses release `forge ci gate` installs when the one on PATH is not it.

View Source
const GolangciLintVersion = "v2.14.0"

GolangciLintVersion is the one golangci-lint release forge installs and ships: forge's own CI and scripts/bootstrap.sh, and — through the CI template and the scaffolded scripts/bootstrap.sh — every project forge creates. TestGolangciLintVersionIsPinnedEverywhere (internal/generator/golangci_pin_test.go) fails when any of those sites names a different version, `latest`, the v1 module path, or an action major that cannot run v2.

It is a pin, not `latest`, because a linter release adds checks: v2.14.0 turned seven months-old lines red with no commit in between. Bumping it is a deliberate one-line change here plus the four files the test names, made in a PR that also fixes whatever the new release reports.

View Source
const GovulncheckVersion = "v1.7.0"

GovulncheckVersion is the govulncheck release `forge ci gate` installs (into forge's tools cache) when the one on PATH is not it.

It has a FLOOR, not just a ceiling: govulncheck vendors golang.org/x/tools to build the SSA it traces calls through, and a copy older than the toolchain panics on syntax it cannot parse. v1.1.4 vendored x/tools v0.29.0 and died with `panic: unexpected expr: *ast.KeyValueExpr` under Go 1.27 — a crash, not a finding, so the gate reported nothing at all. Keep this at or above the release that supports the newest Go a scaffolded go.mod declares.

View Source
const KCLVersion = "v0.13.2"

KCLVersion is the one KCL release forge renders with and installs: the kcl-lang.io/kcl-go, kpm and lib modules in go.mod (the runtime forge embeds), the KCL CLI forge's own CI and scripts/bootstrap.sh install for the tests that shell out to `kcl`, and the version `forge doctor`'s install hint names. KCL releases all five repositories in lockstep, so one tag covers them. Scaffolded projects install no KCL at all: forge renders through the embedded runtime. TestKCLVersionIsPinnedEverywhere (internal/generator/kcl_pin_test.go) fails when any of those sites differs.

View Source
const PnpmVersion = "12.15.0"

PnpmVersion is the pnpm release a scaffolded frontend pins in package.json `packageManager`, and so the one every install of it runs: corepack and pnpm itself switch to the pinned version, the frontend Dockerfile installs it, and the scaffolded workflows' pnpm/action-setup reads it from the same field. TestPnpmVersionIsPinnedEverywhere (internal/generator/pnpm_pin_test.go) fails when a template or one of forge's own workflows names another.

pnpm, not npm, because its content-addressed store hard-links one copy of a package into every checkout that uses it: on a machine with many worktrees a frontend's node_modules costs a few MiB of its own instead of ~900 MB each. Exact, never a range: pnpm majors change settings' homes and defaults (11 stopped reading `overrides` from package.json; 12 fails an install on an unapproved build script).

View Source
const PrettierVersion = "3.5.3"

PrettierVersion is the one prettier release that formats a scaffolded frontend's sources, and therefore the one every formatter of those sources must run: the frontends' package.json (`npm run format`, and forge's format-at-birth of a scaffolded page, which uses the installed copy), the scaffolded .pre-commit-config.yaml hook, forge's OWN pre-commit hook (which formats pkg/components, the library copied verbatim into projects) and every test that runs prettier to prove a source is clean. TestPrettierVersionIsPinnedEverywhere (internal/generator/prettier_pin_test.go) fails when any of those names another version or a range.

Exact, never a range, for the reason GolangciLintVersion is: prettier minors reformat code. Two pins disagreeing on the same file is a formatting round-trip neither side is wrong about — and was forge main's red pre-commit: the hook ran 3.1.0 over library files #502 had formatted with 3.5.3, the version projects ran. A `^3.5.0` range drifts the same way on its own, one `npm install` at a time.

Variables

This section is empty.

Functions

func CanonicalTSImportOrder added in v0.1.45

func CanonicalTSImportOrder(src []byte) []byte

CanonicalTSImportOrder rewrites the leading import block of TypeScript source so it satisfies the scaffold's eslint `import/order` rule: grouped in the configured order, alphabetised within each group, exactly one blank line between groups and none inside one.

It is a fixed point — running it over its own output changes nothing — and it is conservative: source it does not fully understand (no leading import block, a side-effect import whose evaluation order is semantic, an unterminated statement) is returned verbatim rather than rearranged.

Comments BEFORE the first import belong to the file's banner and stay put; comments BETWEEN imports annotate the import that follows and move with it.

func FrontendTemplateRoots added in v0.1.45

func FrontendTemplateRoots(kind string) []string

FrontendTemplateRoots returns the ordered embedded roots composing the template tree for a frontend kind ("nextjs", "vite-spa", "react-native"). Later roots override earlier ones.

An unrecognised kind composes from that directory alone, so a caller naming a tree this function has not been taught about still gets that tree's own files instead of silently getting nothing.

func FuncMap

func FuncMap() template.FuncMap

FuncMap returns the shared template function map used across all templates.

func ListInternalPackageKindTemplates

func ListInternalPackageKindTemplates(kind string) ([]string, error)

ListInternalPackageKindTemplates lists template files for a specific internal-package kind subdirectory (e.g. "client", "eventbus").

func RenderFromFS

func RenderFromFS(fsys fs.FS, basePath, name string, data interface{}) ([]byte, error)

RenderFromFS renders a template from an arbitrary fs.FS. It reads the file at basePath/name, and if the name has a .tmpl suffix it parses and executes it with the shared FuncMap. Non-.tmpl files are returned as-is. Any //go:build ignore directives are stripped from the output.

This is the canonical template-rendering function used by both the built-in template helpers and the pack system.

func RenderInternalPackageKindTemplate

func RenderInternalPackageKindTemplate(kind, name string, data interface{}) ([]byte, error)

RenderInternalPackageKindTemplate renders a template from a kind subdirectory of the internal-package templates.

func RenderInternalPackageTemplate

func RenderInternalPackageTemplate(name string, data interface{}) ([]byte, error)

RenderInternalPackageTemplate renders a base internal-package template.

Types

type BuildImagesWorkflowData

type BuildImagesWorkflowData struct {
	ProjectName string
	// BuildEnv is the env the once-per-commit image is built for: the first
	// deploy env in promotion order, whose KCL-declared registry the image
	// is pushed to. The workflow names no registry — forge reads it from
	// deploy/kcl/<BuildEnv>/main.k.
	BuildEnv string
	// No frontend fields: frontend IMAGES are per-env (each renders that
	// env's config.js), so deploy.yml builds them, and `forge build <env>
	// --target <project>` builds the project image alone — no frontend's
	// `npm run build` — so this job needs no Node toolchain.
	VulnDocker bool // trivy scanning

	// Hosted says BuildEnv pushes to the PLATFORM registry, which changes
	// what the job has to supply: nothing. forge authenticates that registry
	// with the control-plane credential it already resolves (ADR-0003 F3), so
	// a hosted job carries FORGE_CONTROL_PLANE_TOKEN — which it needs anyway
	// — and no registry credential, no --username and no --password-stdin.
	//
	// It is a per-env fact and not a project-wide one: a project may build
	// its per-commit image for a cluster env and promote releases through
	// hosted ones, and only the env this job builds for decides.
	Hosted bool
}

BuildImagesWorkflowData holds data for the build-images workflow template.

type CIWorkflowData

type CIWorkflowData struct {
	ProjectName  string
	HasFrontends bool
	Frontends    []FrontendCIConfig // from project config
	HasServices  bool

	// Lint
	LintGolangci        bool
	LintBuf             bool
	LintBufBreaking     bool
	LintFrontend        bool
	LintFrontendStyles  bool
	LintMigrationSafety bool

	// Test
	TestRace     bool
	TestCoverage bool

	// Vuln scan
	VulnGo     bool // govulncheck
	VulnDocker bool // trivy
	VulnNPM    bool // npm audit

	// License compliance
	LicenseCheck bool // go-licenses

	// E2E
	E2EEnabled bool
	E2ERuntime string // "docker-compose" or "k3d"

	// Permissions
	PermContents string // default "read"

	// Deploy-related
	HasKCL    bool // validate KCL manifests
	HasDocker bool // emit docker-build job (project has a Dockerfile)
	// VerifyGenerated controls whether the verify-generated job is emitted.
	VerifyGenerated bool
	// RunsBufGenerate is true when `forge generate` runs `buf generate` on
	// this project. verify-generated reruns generate, so it installs buf
	// exactly then. The mapper sets it from the generate pipeline's own
	// gate condition, whatever the project kind.
	RunsBufGenerate bool

	// Environments (for KCL validation)
	Environments []string

	// Legacy fields used by other CI templates (build-images, deploy, dependabot)
	Module       string
	FrontendName string // first frontend name for dependabot

	// GitHubOwner is the inferred GitHub owner for default CODEOWNERS entries.
	// Empty when we couldn't confidently infer one (e.g. non-github module
	// path), in which case the CODEOWNERS template emits no file contents and
	// the generator drops the file. Populated by the generator from the
	// project's module path (e.g. "github.com/example/demo" -> "example").
	GitHubOwner string

	// GolangciLintVersion is the golangci-lint release the lint job pins.
	// Left empty by every caller and filled by withDefaults with
	// GolangciLintVersion, so a new construction site cannot ship `latest`.
	GolangciLintVersion string

	// BufVersion is the buf release the checks job installs. Filled by
	// withDefaults with BufVersion for the same reason.
	BufVersion string
}

CIWorkflowData holds data for the spec-driven CI workflow template.

type DependabotData added in v0.1.45

type DependabotData struct {
	// FrontendName is the first frontend's name. Empty emits no npm entry.
	FrontendName string
	// RunsProtocGenGo is true when `forge generate` runs protoc-gen-go —
	// a service-kind project with codegen on. Only then does a
	// google.golang.org/protobuf bump change generated output, so only
	// then is it kept out of Dependabot.
	RunsProtocGenGo bool
}

DependabotData renders ci/github/dependabot.yml.tmpl.

type DeployEnv

type DeployEnv struct {
	Name       string // "staging", "preprod", "prod"
	Auto       bool   // auto-deploy after image build
	Protection bool   // GitHub environment protection
	URL        string // environment URL
}

DeployEnv represents a single deploy environment (e.g. staging, prod).

type DeployWorkflowData

type DeployWorkflowData struct {
	ProjectName string
	// Environments are the envs the project DECLARES (deploy/kcl/<env>/main.k,
	// dev excluded), ordered along the promotion path. Never a hard-coded
	// list: a job for an env with no main.k fails on every run.
	Environments []DeployEnv
	HasFrontends bool
	// FrontendPath is the first frontend's directory: setup-node reads its
	// package.json `engines.node`, and its frozen install there installs
	// what `forge build <env>` needs to build it.
	FrontendPath string
	// Frontend is that first frontend, for its package manager.
	Frontend         FrontendCIConfig
	FrontendDeploy   string // "firebase", "vercel", "none"
	MigrationTest    bool   // test migrations before deploy
	Concurrency      bool   // per-env concurrency groups
	CancelInProgress bool
}

DeployWorkflowData holds data for the deploy workflow template.

type Deps

type Deps struct{}

Deps is the dependency set for the templates Service. Empty today; the package owns its embedded FS and has no external collaborators.

type E2EWorkflowData

type E2EWorkflowData struct {
	ProjectName  string
	Runtime      string // "docker-compose" (default) or "k3d"
	HasFrontends bool
	// FrontendPath points the setup-node `node-version-file` input at a
	// package.json whose `engines.node` is honored by setup-node@v4. Empty
	// when there are no frontends — the template then falls back to a
	// fixed node-version.
	FrontendPath string
	// Frontend is that first frontend, for its package manager.
	Frontend FrontendCIConfig
}

E2EWorkflowData holds data for the standalone E2E test workflow template.

type EnvTemplateData added in v0.1.45

type EnvTemplateData struct {
	// Roots is a composition-graph project: a workload's config env comes
	// from config_gen.workloadEnvMap, which gives one bound to a root
	// (`root = "server"`) exactly what the root reaches.
	Roots       bool
	ProjectName string
	EnvName     string
	// PrimaryWorkload names the workload holding the dev env's
	// `<project>-<env>-api` port key — the port the frontend's dev config
	// reads. The API, `_api` (codegen.APIWorkloadName), unless a caller says
	// otherwise.
	PrimaryWorkload string
	// IngressEnabled wires the dev env's Gateway. A hosted env has no
	// ingress of its own (the platform allocates every public hostname), so
	// the cloud template only points at its ingress.k for a cluster you
	// operate.
	IngressEnabled bool
	HasFrontend    bool
	// DatabaseDisabled omits database infrastructure in a database-free project.
	DatabaseDisabled bool
	FrontendName     string
	// FrontendIdent is the KCL identifier of that frontend: the cloud env
	// declares it once as `_<ident>_frontend` and binds it on one line
	// (`_hosted_frontend(_<ident>_frontend)`), the line `forge env new
	// --bind` rewrites.
	FrontendIdent  string
	FrontendRoutes []string
	// FrontendPath is the frontend's directory relative to the project root:
	// frontends/<name> for a scaffolded one, "." for a site adopted in place
	// (`forge project new --in-place --kind site`), whose app IS the root.
	FrontendPath string
	// FrontendType is the KCL forge.Frontend.type to declare ("vite",
	// "nextjs"); empty leaves the schema default (nextjs) implicit.
	FrontendType string
	// FrontendPublicDir is where the static build lands, relative to
	// FrontendPath: "out" for a Next.js static export, "dist" for Vite.
	FrontendPublicDir string
	// Bindings is the body of the env's `_workloads = [...]` list: one
	// `        _<binder>(wl.<ident>)` line per workload. EnvBinding renders one.
	Bindings string
	// APIWorkload is a hosted env's `_api` declaration — the project
	// binary's `server` as the one workload serving every service
	// (codegen.APIWorkloadStanza). The cloud template only.
	APIWorkload string
	// Claimable is a prod env scaffolded for an anonymous deploy (`forge
	// project new --claimable`): `control_plane` declares `claimable = True`
	// and the hosted binder sizes services and jobs to the claimable plan.
	// The cloud template only.
	Claimable bool
	// ClaimableResources is the body of that plan's largest shape as a KCL
	// fw.Resources block (deploy.ClaimableLimitsFallback), filled by withDefaults
	// so the template states no number of its own.
	ClaimableResources string
	// ClaimableShape is that shape for prose ("125m / 128 MiB"), and
	// ClaimableDatabaseStorageGiB the database storage it declares.
	ClaimableShape              string
	ClaimableDatabaseStorageGiB int32
	// ClaimableDBConnections is that database's max_connections, and
	// ClaimableDBPool the pool each workload is held to inside it.
	ClaimableDBConnections int
	ClaimableDBPool        int
	// Capacity floor of the cloud envs' (unused as scaffolded) `_on_cluster`
	// binder.
	Replicas         int
	CPURequest       string
	CPULimit         string
	MemoryRequestMiB int
	MemoryLimitMiB   int
}

EnvTemplateData is the payload of the env main.k templates (deploy/kcl/dev/main.k.tmpl for the local loop, deploy/kcl/cloud/main.k.tmpl for staging and prod, which are hosted on the forge control plane). An env is a list of per-workload bindings, so the templates hold every binder and per-env value; this carries only what they cannot know — including Bindings, the one line per workload the generator derives from the project's components.

type FrontendCIConfig

type FrontendCIConfig struct {
	Name string
	Path string
	// PackageManager owns the frontend's install: "pnpm", "npm" or "yarn".
	// Empty means pnpm, the manager a scaffolded frontend pins.
	PackageManager string
}

FrontendCIConfig is a minimal frontend descriptor for CI templates.

func (FrontendCIConfig) FrozenInstall added in v1.0.0

func (f FrontendCIConfig) FrozenInstall() string

FrozenInstall is the lockfile-exact install CI runs in the frontend.

func (FrontendCIConfig) Lockfile added in v1.0.0

func (f FrontendCIConfig) Lockfile() string

Lockfile is the lockfile the frontend's package manager writes.

func (FrontendCIConfig) PM added in v1.0.0

func (f FrontendCIConfig) PM() string

PM is the frontend's package manager, defaulting to pnpm.

type FrontendTemplateData

type FrontendTemplateData struct {
	FrontendName string
	ProjectName  string
	// Platform names the frontend template tree being rendered —
	// "nextjs", "vite-spa" or "react-native". It exists so a template
	// under frontend/shared* (rendered into every tree that claims it)
	// can gate the one or two lines that genuinely differ per platform
	// instead of being forked into N near-identical copies. Today the
	// only gate is the Next.js App Router's `"use client"` prologue.
	//
	// Set by the frontend scaffolder from the resolved tree name, so it
	// cannot drift from the roots being composed. Empty when a caller
	// renders a single per-kind template directly (nav, dashboard,
	// apiurl_gen, …) — those live under one tree and never gate on it.
	Platform string
	// APIURL and APIPackage are exposed to text/template files under
	// internal/templates/frontend/, so their Go field names are the
	// template variable names: a customized .tmpl referencing {{.ApiUrl}}
	// must be updated to {{.APIURL}}. forge carries no backwards
	// compatibility — the rename is the record.
	//
	// (ApiPort was removed when the hand-editable .env.local scaffolds were
	// ripped out — the only consumers were those templates'
	// `http://localhost:{{.ApiPort}}` lines; the regenerated apiurl_gen.ts
	// bakes the full APIURL instead.)
	APIURL string
	Module string
	Pages  []NavPageData
	// HasTypedConfig reports whether this frontend has a config message
	// bound to it by (forge.v1.frontend_config). When true, templates read
	// their settings through the generated src/lib/config_gen.ts — values
	// delivered at RUNTIME by the KCL-rendered config.js — instead of
	// build-time-inlined NEXT_PUBLIC_* / VITE_* env vars.
	//
	// It gates rather than replaces because frontend config is OPT-IN: a
	// project that declares no frontend-bound message keeps the previous
	// env-var behavior byte-for-byte, so adopting this is a proto edit and
	// nothing else.
	HasTypedConfig bool
	// Has<Field> reports which well-known fields that message actually
	// declares. A template cannot reference cfg.MOCK_API when the proto
	// never declared it — the generated module's type would not have the
	// key and `tsc` would fail — so each read is gated on its own field
	// being present.
	//
	// There is no OIDC gate here, and that is deliberate: browser sign-in
	// is NATIVE (credentials POSTed to this app's own API, HttpOnly cookie
	// back, server runs the OIDC flow), so no issuer, client id, redirect
	// URI, scope list or resource indicator is ever delivered to a bundle.
	// Handing the browser those values is what would let a browser-side
	// PKCE flow — and a script-readable token — come back.
	HasMockAPI bool
	// NavHookImports is the per-module aggregation of the list hooks the
	// dashboard tiles consume. Derived from Pages by the nav generator.
	NavHookImports []NavHookImport
	// Workspaces reports whether the project opted into the pnpm-
	// workspaces layout. When true, frontend templates emit imports
	// of APIPackage / HooksPackage instead of relative @/gen and
	// @/hooks paths, and the per-frontend package.json declares
	// "workspace:*" deps on those packages.
	//
	// When false (the default), templates produce the historic
	// single-frontend output — byte-identical to projects scaffolded
	// before workspaces landed. Snapshot tests rely on this.
	Workspaces bool
	// WebRuntimeTypePins are the packages a frontend's tsconfig must pin
	// to its OWN copy, so tsc sees one identity per type.
	//
	// A plain []string, not a type from another package: this struct is a
	// TEMPLATE PAYLOAD, so its fields are the values the templates render
	// and nothing more. Whoever computes the list owns its meaning; this
	// package only writes it out.
	//
	// Left nil by every caller today, and filled by withDefaults. It stays
	// a field rather than a method so a caller CAN pin a different set —
	// which is what makes the default a default and not a wall.
	WebRuntimeTypePins []string
	// WebRuntimePinsHoisted selects WHICH node_modules the pins above point
	// at: the project root's (true) or the frontend's own (false, the
	// default).
	//
	// It exists because a `paths` value for a non-wildcard key must hold
	// EXACTLY ONE element — SWC panics `next build` otherwise — so the
	// emitter cannot list both layouts and let the toolchain choose. See
	// webruntimepeers.TypePinPath.
	//
	// True when an npm workspace hoists the frontend's dependencies to the
	// project root, which is what forge's own dev bridge creates.
	WebRuntimePinsHoisted bool
	// APIPackage is the npm package name for the shared Connect TS
	// clients workspace, e.g. "@myapp/api". Empty when Workspaces is
	// false.
	APIPackage string
	// HooksPackage is the npm package name for the shared React Query
	// hooks workspace, e.g. "@myapp/hooks". Empty when Workspaces is
	// false.
	HooksPackage string
	// UIWebPackage is the npm package name for the shared web UI
	// component workspace, e.g. "@myapp/ui-web". Empty when Workspaces
	// is false. Frontend package.json templates declare a
	// "workspace:*" dep on this, and tsconfig templates redirect
	// `@/components/*` paths into the package via path mapping.
	UIWebPackage string
	// UINativePackage is the npm package name for the shared React
	// Native primitives workspace, e.g. "@myapp/ui-native". Empty
	// when Workspaces is false OR the project has no RN frontend.
	// React Native templates reference this in their package.json
	// workspace deps and example screens.
	UINativePackage string
	// Output selects the Next.js build/runtime shape. Mirrors
	// config.FrontendConfig.Output. Only the nextjs templates read it
	// (`next.config.ts.tmpl`); other template trees ignore it.
	//
	// Valid values rendered by templates:
	//   - "static":     emit `output: "export"` gated on
	//     NODE_ENV=production (the scaffold default; callers resolve it
	//     before rendering). Dev server stays unchanged. The generated
	//     CRUD pages are static routes, so the export builds.
	//   - "standalone": emit `output: "standalone"` unconditionally —
	//     the Node server the standalone Dockerfile runs.
	//   - "server":     omit `output` entirely (full Next.js dev+prod).
	//
	// Empty string renders the standalone arm, which is what an empty
	// forge.yaml `output:` has always meant
	// (config.FrontendConfig.EffectiveOutput). A NEW frontend never
	// reaches the template empty: GenerateFrontendFilesWithOptions
	// resolves "" to config.FrontendOutputScaffoldDefault first.
	Output string
	// BasePath mirrors config.FrontendConfig.BasePath — the URL prefix
	// the frontend is mounted under (e.g. "/admin"), or "" for root.
	// Read by the nextjs templates only:
	//
	//   - next.config.ts.tmpl renders it as the build-time default for
	//     `basePath` + `assetPrefix` (overridable via the single
	//     canonical env var NEXT_PUBLIC_BASE_PATH).
	//   - src/lib/basepath_gen.ts.tmpl bakes it as the fallback for
	//     BASE_PATH / joinBasePath().
	//   - Dockerfile mounts a static export under it, as the StaticSite
	//     deploy does.
	//
	// Already validated by config.LoadStrict (leading "/", no trailing
	// "/", [A-Za-z0-9._-] segments) — templates splice it verbatim into
	// TypeScript string literals.
	BasePath string
	// RepoPath is the frontend's directory relative to the project root
	// ("frontends/web"), slash-separated. The nextjs Dockerfile builds at
	// /src/<RepoPath> so every relative path the committed config carries —
	// tsconfig's `../../node_modules/…` pins above all — means inside the
	// image what it means in the repository. Empty falls back to
	// frontends/<FrontendName>, where the scaffolder writes every frontend.
	RepoPath string
	// Public renders the frontend WITHOUT a sign-in gate (auth_mode: none):
	// providers.tsx / routes.tsx mount no RouteGuard and the Vite tree
	// declares no /auth/sign-in route. See generator.FrontendGenOptions.Public.
	Public bool
}

FrontendTemplateData holds data for frontend template rendering.

func (FrontendTemplateData) TelemetryServiceName added in v1.0.0

func (d FrontendTemplateData) TelemetryServiceName() string

TelemetryServiceName is the service.name this frontend's browser telemetry reports (otel_gen.ts's SERVICE_NAME): naming.FrontendServiceName, the same derivation the generated dashboards and alert defaults filter on.

type FrontendTreeFile added in v0.1.45

type FrontendTreeFile struct {
	// Rel is the path relative to the generated frontend root, with any
	// .tmpl suffix still attached (e.g. "src/lib/events.ts",
	// "src/lib/auth/context.tsx.tmpl").
	Rel string
	// Path is the path within the frontend template category, ready to
	// hand to FrontendTemplates().Render (e.g. "shared/src/lib/events.ts").
	Path string
}

FrontendTreeFile is one file in the composed template tree for a frontend kind.

func ListFrontendTree added in v0.1.45

func ListFrontendTree(kind string) ([]FrontendTreeFile, error)

ListFrontendTree returns every file a frontend of the given kind is scaffolded from, sorted by destination path. Shadowed files appear once, resolved to the winning root.

type MockService

type MockService struct {
	contractkit.Recorder
	RenderCategoryFunc            func(string, string, any) ([]byte, error)
	ListCategoryFunc              func(string, string) ([]string, error)
	RenderInternalPackageKindFunc func(string, string, any) ([]byte, error)
}

MockService is a test mock for the Service interface.

The embedded contractkit.Recorder records every call so tests can assert call counts and captured arguments. Set XxxFunc fields to override per-method behaviour; unset methods return the canonical "MockService.<Method>Func not set" error.

func (*MockService) ListCategory

func (m *MockService) ListCategory(category string, subdir string) ([]string, error)

ListCategory records the call and delegates to ListCategoryFunc when set.

func (*MockService) RenderCategory

func (m *MockService) RenderCategory(category string, name string, data any) ([]byte, error)

RenderCategory records the call and delegates to RenderCategoryFunc when set.

func (*MockService) RenderInternalPackageKind

func (m *MockService) RenderInternalPackageKind(kind string, name string, data any) ([]byte, error)

RenderInternalPackageKind records the call and delegates to RenderInternalPackageKindFunc when set.

type NavHookImport struct {
	Module  string   // "@/hooks/task-service-hooks"
	Symbols []string // ["useListTasks", "useListProjects"] — sorted
}

NavHookImport is one merged import statement the dashboard template emits for list hooks: all hook symbols whose generated hooks file is the same module. Pre-grouped in Go so two entities served by one service don't produce duplicate import statements (import/no-duplicates).

type NavPageData struct {
	Label      string // display name, e.g. "Tasks"
	LabelLower string // lowercase for descriptions, e.g. "tasks"
	Slug       string // URL path segment, e.g. "tasks"
	// LabelSingular is the singular display name ("Task") — used by
	// "Create X" buttons. Derived from the proto entity name, not a
	// strip-the-trailing-s guess ("Categories" → "Category", not
	// "Categorie").
	LabelSingular string
	// HasCreate reports whether the page generator emitted a create page
	// (<slug>/new) for this entity. The dashboard's QuickActions grid
	// gates its "Create X" buttons on this — advertising a create route
	// the page generator never wrote is a guaranteed 404.
	HasCreate bool
	// ListHook is the generated React Query list hook name
	// ("useListTasks"); the dashboard tile uses it to render a real
	// entity count instead of a placeholder.
	ListHook string
	// HooksModule is the import path of the generated hooks file, e.g.
	// "@/hooks/task-service-hooks".
	HooksModule string
	// ItemsField is the camelCase repeated field on the list response
	// holding the entities ("tasks").
	ItemsField string
	// HasTotalCount / TotalCountField expose the List response's server-side
	// total ("totalCount") when the RPC declares one. The dashboard tile
	// counts with THIS, never with the length of the page it fetched: a
	// tile reading `data.tasks.length` reports the page size once the table
	// outgrows one page ("25" forever), and it is forge's own generated
	// exemplar that teaches the habit to everything downstream.
	HasTotalCount   bool
	TotalCountField string
	// HasPageSize reports whether the List request carries page_size. The
	// tile asks for ONE row when it does — the count comes from
	// total_count, so pulling a full page of entities to display a single
	// number is waste on every dashboard load.
	HasPageSize bool
	// ComponentIdent is a valid TS identifier for per-entity generated
	// components ("Tasks" → TasksTile).
	ComponentIdent string
}

NavPageData describes a single page entry for the frontend navigation.

type ReleaseStage added in v0.1.45

type ReleaseStage struct {
	Env DeployEnv
	// Prev is the stage promoted from (empty for the first, which is
	// promoted by version). Its job's promotion id is --from-promotion.
	Prev string
	// Next is the stage after this one (empty for the last). This job
	// captures Next's current promotion BEFORE Next's approval wait, so
	// Next's deploy can assert it with --expect-current.
	Next string
	// Mixed is set for an env that also applies a cluster/compose part from
	// CI. Its stage always deploys client-side — a control plane converges
	// only what it hosts — and writes the kubeconfig that deploy needs.
	Mixed bool
}

ReleaseStage is one hosted env's deploy job in release.yml.

func ReleaseStages added in v0.1.45

func ReleaseStages(envs []DeployEnv, mixed map[string]bool) []ReleaseStage

ReleaseStages chains hosted envs (already in promotion order) into stages. mixed names the envs that also apply a part from CI (see ReleaseStage.Mixed).

type ReleaseWorkflowData added in v0.1.45

type ReleaseWorkflowData struct {
	ProjectName string
	// BuildEnv is the hosted env the release's images are built and cut
	// against (the first in promotion order): its declared registry and
	// control plane are where the bytes and the release live.
	BuildEnv string
	// Stages are the hosted envs, in promotion order, one job each. The
	// first is promoted by version; every later one `--from` the stage
	// before it, so a release reaches prod exactly as it ran on the stage
	// before. Prev/Next are precomputed here rather than derived with index
	// arithmetic in YAML, so the chain is testable Go and the template stays
	// readable.
	Stages []ReleaseStage
}

ReleaseWorkflowData holds data for release.yml and the vendored forge-deploy composite action. Emitted only for a project with at least one HOSTED env — one whose KCL declares forge.ControlPlane and a workload or database the platform runs.

type Service

type Service interface {
	// RenderCategory renders the named template within the given category
	// (e.g. category="project", name="bootstrap.go.tmpl") with data and
	// returns the post-processed bytes.
	RenderCategory(category, name string, data any) ([]byte, error)

	// ListCategory returns all template paths under the named category,
	// optionally restricted to a sub-directory. Recursive walk.
	ListCategory(category, subdir string) ([]string, error)

	// RenderInternalPackageKind renders a template from an internal-package
	// kind sub-directory (e.g. kind="client", name="client.go.tmpl").
	RenderInternalPackageKind(kind, name string, data any) ([]byte, error)
}

Service is the behavioural surface of the templates package.

Operations are intentionally narrow: callers pick a category by name and ask for either a list of templates or a rendered template body. More specialised entry points (TemplateEngine, RenderFromFS) remain as package-level helpers because their inputs (pre-parsed template, custom fs.FS) make a uniform interface awkward.

func New

func New(_ Deps) Service

New constructs a templates.Service.

forge:no-observe Pure compute: empty Deps; the package reads its own embedded FS. There is no I/O boundary worth instrumenting.

type TemplateCategory

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

TemplateCategory provides Get, Render, and List operations for a specific template directory within the embedded filesystem.

func CITemplates

func CITemplates(provider string) TemplateCategory

CITemplates returns a TemplateCategory for a specific CI provider.

func DeployTemplates

func DeployTemplates() TemplateCategory

DeployTemplates returns the category for deploy-scaffold templates.

func FrontendTemplates

func FrontendTemplates() TemplateCategory

FrontendTemplates returns the category for frontend-scaffold templates.

func IngressTemplates

func IngressTemplates() TemplateCategory

IngressTemplates returns the embedded Gateway API install assets (the Envoy Gateway VERSION pins + the vendored `eg` GatewayClass) keyed under `internal/templates/ingress/<provider>/`. The Envoy Gateway controller is installed via `helm` from the pinned gateway-helm chart, and the Gateway API CRDs are fetched from the upstream GitHub release at `forge cluster up` time and cached under ~/.cache/forge/ — see internal/cli/dev_cluster_ingress.go.

func InternalPkgKindTemplates

func InternalPkgKindTemplates(kind string) TemplateCategory

InternalPkgKindTemplates returns a TemplateCategory for a specific internal-package kind subdirectory.

func InternalPkgTemplates

func InternalPkgTemplates() TemplateCategory

InternalPkgTemplates returns the category for shared internal-package templates.

func OperatorTemplates

func OperatorTemplates() TemplateCategory

OperatorTemplates returns the category for operator-scaffold templates.

func ProjectTemplates

func ProjectTemplates() TemplateCategory

ProjectTemplates returns the category for project-scaffold templates.

func ServiceTemplates

func ServiceTemplates() TemplateCategory

ServiceTemplates returns the category for service-scaffold templates.

func TestTemplates

func TestTemplates() TemplateCategory

TestTemplates returns the category for test-scaffold templates.

func WebhookTemplates

func WebhookTemplates() TemplateCategory

WebhookTemplates returns the category for webhook-scaffold templates.

func WorkerCronTemplates

func WorkerCronTemplates() TemplateCategory

WorkerCronTemplates returns the category for worker-cron-scaffold templates.

func WorkerTemplates

func WorkerTemplates() TemplateCategory

WorkerTemplates returns the category for worker-scaffold templates.

func (TemplateCategory) Get

func (c TemplateCategory) Get(name string) ([]byte, error)

Get returns the raw bytes of a template file. Any //go:build ignore directives are stripped from the output.

func (TemplateCategory) List

func (c TemplateCategory) List(subdir string) ([]string, error)

List returns all template names in the category (recursive).

func (TemplateCategory) ListFlat

func (c TemplateCategory) ListFlat(subdir string) ([]string, error)

ListFlat returns only direct children (non-recursive).

func (TemplateCategory) Render

func (c TemplateCategory) Render(name string, data interface{}) ([]byte, error)

Render executes a template with the given data and returns the result.

Deliberately generic: it renders eleven unrelated template categories and knows nothing about any one payload's shape. A type switch here on one specific struct would make the shared renderer a place every domain is tempted to special-case. Payload defaulting belongs to the payload — see FrontendTemplateData.withDefaults.

type TemplateEngine

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

TemplateEngine handles code generation from service/middleware templates. NOTE: TemplateEngine pre-parses templates for reuse via a singleton (see generator/project.go), while the TemplateCategory.Render method parses on each call. Both share FuncMap(). Consider consolidating if this becomes a maintenance burden.

func NewTemplateEngine

func NewTemplateEngine() (*TemplateEngine, error)

NewTemplateEngine creates a new template engine with all service and middleware templates pre-loaded.

func (*TemplateEngine) RenderTemplate

func (e *TemplateEngine) RenderTemplate(name string, data interface{}) (string, error)

RenderTemplate renders a template with the given data.

type WebhookRouteEntryData

type WebhookRouteEntryData struct {
	Name       string // kebab-case name for the URL path (e.g. "stripe")
	PascalName string // PascalCase name for the handler method (e.g. "Stripe")
}

WebhookRouteEntryData holds per-webhook data for route generation.

type WebhookRoutesTemplateData

type WebhookRoutesTemplateData struct {
	Package  string                  // Go package name (e.g. "billing")
	Webhooks []WebhookRouteEntryData // all webhooks for this service
}

WebhookRoutesTemplateData holds data for the webhook_routes_gen.go template.

type WebhookTemplateData

type WebhookTemplateData struct {
	Name           string // webhook name (e.g. "stripe", "github")
	ServiceName    string // target service display name (may contain hyphens)
	ServicePackage string // Go-package-safe form of ServiceName (snake_case)
	Module         string // Go module path
	// ConfigImport is the import path of the config the service's Deps
	// takes (`Config *config.Config`), "" when it takes none; the test
	// passes a Config only then. ConfigImportAlias is "config" when the
	// path's last segment is not, so the test can spell config.Config.
	ConfigImport      string
	ConfigImportAlias string
}

WebhookTemplateData holds data for webhook template rendering.

Jump to

Keyboard shortcuts

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