Skip to content

feat(wfctl): add wfctl api extract and wfctl ui scaffold commands#165

Merged
intel352 merged 5 commits intomainfrom
feat/wfctl-api-extract
Feb 25, 2026
Merged

feat(wfctl): add wfctl api extract and wfctl ui scaffold commands#165
intel352 merged 5 commits intomainfrom
feat/wfctl-api-extract

Conversation

@intel352
Copy link
Contributor

@intel352 intel352 commented Feb 25, 2026

Summary

This PR adds two new wfctl commands:

  1. wfctl api extract — Extract an OpenAPI 3.0 spec from a workflow config file (offline, no server needed)
  2. wfctl ui scaffold — Generate a complete Vite+React+TypeScript SPA wired to the backend API from an OpenAPI 3.0 spec

wfctl ui scaffold

Reads an OpenAPI 3.0 spec (JSON or YAML, file or stdin) and generates an immediately-buildable Vite+React+TS SPA:

ui/
├── package.json           # vite, react, react-dom, react-router-dom, @types/*
├── tsconfig.json
├── vite.config.ts         # proxy /api → localhost:8080 for dev
├── index.html
└── src/
    ├── main.tsx           # React entry, BrowserRouter, optional AuthProvider
    ├── App.tsx            # Routes, PrivateRoute if auth detected
    ├── api.ts             # Typed API client generated from OpenAPI operations
    ├── auth.tsx           # AuthProvider + useAuth (only if auth endpoints detected)
    ├── components/
    │   ├── Layout.tsx     # Sidebar nav + Outlet
    │   ├── DataTable.tsx  # Reusable generic table
    │   └── FormField.tsx  # text/email/password/number/select input
    └── pages/
        ├── LoginPage.tsx      # If auth detected
        ├── RegisterPage.tsx   # If auth detected
        ├── DashboardPage.tsx
        └── [Resource]Page.tsx # One per resource group (list + create + detail + delete)

Usage:

wfctl ui scaffold [options]
  -spec string    Path to OpenAPI spec file (default: stdin)
  -output string  Output directory (default: ./ui)
  -title string   App title (extracted from spec if not set)
  -auth           Force auth pages even if not auto-detected
  -theme string   Color theme: light, dark, auto (default: auto)

The existing wfctl build-ui command is also available as wfctl ui build via the new ui parent command.

Test plan

  • go build ./cmd/wfctl/ compiles cleanly
  • go test ./cmd/wfctl/... — all tests pass (27 new scaffold tests + existing tests)
  • Smoke test: scaffold generates all expected files with correct content
  • API client exports typed functions per operation with path template literals
  • Auth pages auto-detected from /auth/login and /auth/register paths
  • Auth pages suppressed when spec has no auth endpoints
  • Form fields inferred from requestBody schema (text/email/password/number/select)
  • package.json is valid JSON with correct deps
  • vite.config.ts proxies /api to localhost:8080

🤖 Generated with Claude Code

intel352 and others added 4 commits February 25, 2026 00:38
Implements step.build_from_config (Phase 5.1 roadmap) — a pipeline step
that assembles a self-contained Docker image from a workflow config YAML
file, a server binary, and optional plugin binaries.

- Creates a temp build context, copies config + server + plugin binaries
- Generates a Dockerfile with correct ENTRYPOINT/CMD for workflow server
- Executes docker build (and optional docker push) via exec.Command
- exec.Command is injectable for deterministic unit testing
- 17 tests cover factory validation, Dockerfile generation, error paths,
  push flag, plugin inclusion, and build context file layout
- Registers step.build_from_config in plugins/cicd manifest and factory map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…line steps

Implements two new pipeline steps for interacting with state machine
workflow instances directly from pipeline execution:

- step.statemachine_transition: triggers a named transition on a workflow
  instance, supports data templates, fail_on_error flag, and the
  TransitionTrigger interface for testability via mocks.
- step.statemachine_get: reads the current state of a workflow instance.

Both steps resolve entity_id and data fields from Go templates using the
existing TemplateEngine, look up the named StateMachineEngine by service
name from the app registry, and return structured output (transition_ok,
new_state, current_state, entity_id).

Steps are registered in plugins/statemachine with StepFactories() and
declared in the manifest's StepTypes list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…teps

Implements Redis-backed caching for the workflow engine:

- module/cache_redis.go: CacheModule interface + RedisCache module (cache.redis type)
  with Get/Set/Delete ops, key prefixing, default TTL, and RedisClient interface for testability
- module/pipeline_step_cache_get.go: step.cache_get — reads from cache with template key,
  configurable output field, miss_ok flag
- module/pipeline_step_cache_set.go: step.cache_set — writes to cache with template key+value,
  optional TTL override
- module/pipeline_step_cache_delete.go: step.cache_delete — removes a key from cache
- Full test coverage using miniredis (already a project dependency) for the Redis module,
  and a mockCacheModule for the pipeline step tests
- plugins/storage/plugin.go: registers cache.redis module factory and schema (7 module types)
- plugins/pipelinesteps/plugin.go: registers step.cache_get/set/delete factories (21 step types)

All existing tests continue to pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ec generation

Adds a new `wfctl api extract` subcommand that parses a workflow YAML config
file offline (no server startup) and emits a valid OpenAPI 3.0.3 specification
covering all HTTP endpoints from two sources:
- workflow routes (workflows.<name>.routes)
- pipeline HTTP triggers (pipelines.<name>.trigger.type == "http")

Schema inference from step types is supported: step.validate rules map to
request body properties, step.user_register/user_login produce typed
request/response schemas, step.json_response infers response status and body,
and step.auth_required hints 401/403 responses.

Output format is JSON (default) or YAML, configurable via -format. Output goes
to stdout or a file via -output. Supports -title, -version, and repeatable
-server flags. Registers the command as "api" in main.go dispatch.

20 tests added covering all flags, both endpoint sources, schema inference,
error paths, and stdout output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 06:45
if err != nil {
return fmt.Errorf("create %q: %w", dst, err)
}
defer out.Close()
…SPA generation

Adds `wfctl ui scaffold` which reads an OpenAPI 3.0 spec (JSON or YAML,
file or stdin) and generates a complete, immediately-buildable Vite+React+TS
SPA wired to the backend API.

- New `wfctl ui` parent command dispatching to `scaffold` and `build` subcommands
- Parses OpenAPI 3.0 spec: groups paths into resource pages, detects auth endpoints
- Generates: package.json, tsconfig.json, vite.config.ts (with /api proxy),
  index.html, src/main.tsx, App.tsx (routes), api.ts (typed client with Bearer
  auth + 401 redirect), auth.tsx (AuthProvider/useAuth context), Layout,
  DataTable, FormField components, and one [Resource]Page.tsx per resource
- Auth pages (LoginPage, RegisterPage) are auto-detected from spec or forced
  with -auth flag; skipped when no auth endpoints are found
- Form fields inferred from requestBody schema (text/email/password/number/select)
- API client exports typed functions per operation using path template literals
- 27 tests covering spec parsing (JSON/YAML), analysis, code generation helpers,
  and CLI integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR's title indicates it adds a wfctl api extract command for offline OpenAPI 3.0 spec generation. However, the actual changes include four major unrelated features that should likely be separate PRs:

  1. wfctl api extract command (matches PR description) - Offline OpenAPI 3.0 spec extraction from config files
  2. Redis cache module - New cache.redis module with three pipeline steps: cache_get, cache_set, cache_delete
  3. State machine pipeline steps - New statemachine_transition and statemachine_get steps for triggering state transitions within pipelines
  4. Build from config step - New CI/CD build_from_config step that generates Docker images from workflow configs

Changes:

  • Adds wfctl api extract command with JSON/YAML output, schema inference from pipeline steps, and support for both workflow routes and pipeline HTTP triggers
  • Introduces Redis-backed caching capability with three new pipeline steps for get/set/delete operations
  • Adds state machine integration to pipelines with transition triggering and state query steps
  • Implements Docker image building from workflow configs with plugin support

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
cmd/wfctl/main.go Registers new api and ui commands (ui function missing)
cmd/wfctl/api_extract.go Implements OpenAPI 3.0 spec extraction from config files
cmd/wfctl/api_extract_test.go 20 tests covering spec generation, formats, schema inference
module/cache_redis.go Redis cache module implementing CacheModule interface
module/cache_redis_test.go 9 tests for Redis cache operations
module/pipeline_step_cache_get.go Pipeline step for reading from cache
module/pipeline_step_cache_get_test.go 11 tests for cache get step
module/pipeline_step_cache_set.go Pipeline step for writing to cache
module/pipeline_step_cache_set_test.go 8 tests for cache set step
module/pipeline_step_cache_delete.go Pipeline step for cache deletion
module/pipeline_step_cache_delete_test.go 6 tests for cache delete step
module/pipeline_step_statemachine_transition.go Pipeline step for triggering state machine transitions
module/pipeline_step_statemachine_transition_test.go 17 tests for state machine transition step
module/pipeline_step_statemachine_get.go Pipeline step for querying workflow instance state
module/pipeline_step_statemachine_get_test.go 8 tests for state machine get step
module/pipeline_step_build_from_config.go CI/CD step for building Docker images from configs
module/pipeline_step_build_from_config_test.go 16 tests for build from config step
plugins/storage/plugin.go Registers cache.redis module and updates capability declarations
plugins/storage/plugin_test.go Updates test expectations for new cache module
plugins/pipelinesteps/plugin.go Registers three new cache pipeline steps
plugins/pipelinesteps/plugin_test.go Updates test expectations for new cache steps
plugins/statemachine/plugin.go Registers state machine pipeline steps with wrapStepFactory helper
plugins/statemachine/plugin_test.go Adds TestStepFactories and updates test expectations
plugins/cicd/plugin.go Registers build_from_config step
plugins/cicd/plugin_test.go Updates test expectations for new build step

// Look for a server module with a descriptive name
for _, mod := range cfg.Modules {
if mod.Type == "http.server" && mod.Name != "server" && mod.Name != "" {
return strings.Title(strings.ReplaceAll(mod.Name, "-", " ")) //nolint:staticcheck
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.Title is deprecated as of Go 1.18. Use cases.Title from golang.org/x/text/cases instead, or implement a simple title-casing function if the dependency is undesirable.

Copilot uses AI. Check for mistakes.
"ui": runUI,
"publish": runPublish,
"deploy": runDeploy,
"api": runAPI,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title and description focus exclusively on adding the wfctl api extract command for OpenAPI spec generation. However, this PR also includes several unrelated substantial features: a Redis cache module with three new pipeline steps (cache_get, cache_set, cache_delete), two new state machine pipeline steps (statemachine_transition, statemachine_get), and a new build_from_config CI/CD step. These additions represent significant new functionality beyond what is described in the PR. Consider splitting these into separate PRs, each with focused scope and description, or updating the PR description to accurately reflect all changes being introduced.

Copilot uses AI. Check for mistakes.
}
}

func TestBuildFromConfigStep_GenerateDockerfile_NoPLugins(t *testing.T) {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in test name: "NoPLugins" should be "NoPlugins" (capital L instead of lowercase l).

Suggested change
func TestBuildFromConfigStep_GenerateDockerfile_NoPLugins(t *testing.T) {
func TestBuildFromConfigStep_GenerateDockerfile_NoPlugins(t *testing.T) {

Copilot uses AI. Check for mistakes.
Comment on lines +500 to +513
_, err = fmt.Fprintf(out, "")
if err != nil {
return err
}
_, err = out.Seek(0, 0)
if err != nil {
return err
}
f, err := os.Open(path) //nolint:gosec
if err != nil {
return err
}
defer f.Close()
_, copyErr := io.Copy(out, f)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyDirRecursive function opens the same file twice (lines 490 and 508), which is inefficient. The first os.Open at line 490 (stored in variable in) is already available and should be used for io.Copy instead of opening the file again at line 508. The fmt.Fprintf and Seek operations on lines 500-506 appear unnecessary - just copy from in directly to out.

Suggested change
_, err = fmt.Fprintf(out, "")
if err != nil {
return err
}
_, err = out.Seek(0, 0)
if err != nil {
return err
}
f, err := os.Open(path) //nolint:gosec
if err != nil {
return err
}
defer f.Close()
_, copyErr := io.Copy(out, f)
_, copyErr := io.Copy(out, in)

Copilot uses AI. Check for mistakes.
"manifest": runManifest,
"migrate": runMigrate,
"build-ui": runBuildUI,
"ui": runUI,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command map registers "ui" with runUI function, but this function is not defined anywhere in the codebase. This will cause a compilation error. Either remove this entry or implement the runUI function.

Copilot uses AI. Check for mistakes.
Comment on lines +577 to +583

func min(a, b int) int {
if a < b {
return a
}
return b
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go 1.21+ provides built-in min and max functions. Since the repository targets Go 1.26 (per go.mod), this custom min function is redundant and should be replaced with the built-in min function. Remove the custom min implementation and use the built-in version directly at line 388.

Suggested change
func min(a, b int) int {
if a < b {
return a
}
return b
}

Copilot uses AI. Check for mistakes.
tmpl *TemplateEngine
}

// NewCacheGetStepFactory returns a StepFactory that creates CacheGetStep instances.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache pipeline step factories (cache_get, cache_set, cache_delete) lack structured configuration documentation that other pipeline steps provide. Add a doc comment block above NewCacheGetStepFactory, NewCacheSetStepFactory, and NewCacheDeleteStepFactory following the pattern used in NewStateMachineTransitionStepFactory (lines 22-35 of pipeline_step_statemachine_transition.go). Include: config field descriptions, example YAML configuration, and output field descriptions.

Suggested change
// NewCacheGetStepFactory returns a StepFactory that creates CacheGetStep instances.
// NewCacheGetStepFactory returns a StepFactory that creates CacheGetStep instances.
//
// Configuration fields:
// - cache (string, required): Name of the CacheModule service to use (as registered in the application).
// - key (string, required): Cache key template, e.g. "user:{{.user_id}}". The template is resolved against the pipeline context.
// - output (string, optional, default "value"): Name of the output field that will contain the cached value.
// - miss_ok (bool, optional, default true): When true, a cache miss is not treated as an error; when false, a miss causes the step to fail.
//
// Example YAML configuration:
//
// - name: load-user-from-cache
// type: cache_get
// config:
// cache: user-cache
// key: "user:{{.user_id}}"
// output: user
// miss_ok: false
//
// Output fields:
// - <output>: The value retrieved from the cache (empty string on cache miss when miss_ok is true).
// - cache_hit (bool): True if the key was found in the cache, false on cache miss.

Copilot uses AI. Check for mistakes.
@intel352 intel352 changed the title feat(wfctl): add wfctl api extract command for offline OpenAPI 3.0 spec generation feat(wfctl): add wfctl api extract and wfctl ui scaffold commands Feb 25, 2026
@intel352 intel352 merged commit f990722 into main Feb 25, 2026
13 of 14 checks passed
@intel352 intel352 deleted the feat/wfctl-api-extract branch February 25, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants