* feat(yaml-validator)!: unify flow, schedule, and trigger validation - replace FlowValidator with WindmillYamlValidator.validate(doc, target) - generate schedule/trigger schemas from backend OpenAPI and OpenFlow refs - add schedule/trigger/filename-target tests and update AI agent fixtures - bump windmill-yaml-validator to 2.0.0 BREAKING CHANGE: FlowValidator and validateFlow() are replaced by WindmillYamlValidator.validate(doc, target). * add lint command * add deno-compat script and docs for local yaml-validator testing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: make nullable fields pass yaml validation Add nullable: true to static_asset_config and authentication_resource_path in HttpTrigger schema. Post-process generated JSON schemas to add null to enums with nullable: true (AJV doesn't handle OpenAPI 3.0 nullable + enum). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add nullable to all Option<T> fields in trigger and schedule OpenAPI schemas Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(frontend): handle nullable fields from updated OpenAPI types Add ?? undefined coalescing at assignment sites where generated types now include | null from the OpenAPI nullable additions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(lint): show allowed values in enum validation errors Instead of "must be equal to one of the allowed values", now shows "must be one of: 'r', 'w', 'rw'" for enum validation failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add nullable to Edit/New trigger and schedule OpenAPI schemas Ensures create/update request body types accept null for the same fields that GET response types return as nullable, enabling clean round-tripping without type mismatches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * use published package * publish * refactor(lint): remove unused --includes/--excludes/--extra-includes CLI options These options were defined but never wired to the file filtering logic. The lint command still respects includes/excludes from wmill.yaml via mergeConfigWithConfigFile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(lint): handle additionalProperties errors and expand test coverage Add formatting for AJV additionalProperties keyword to show the unknown property name. Add unit tests for all formatValidationError branches and integration tests for --json report shape, --fail-on-warn with mixed files, non-existent directory, and enum error output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add realistic validator tests for schedules, triggers, and edge cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add email trigger validation support Add email trigger schema generation, validation, and linting. Email triggers are no longer skipped with a warning — they are validated like all other trigger types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore(cli): bump windmill-yaml-validator to 1.1.1 (email trigger support) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * publish * rm * fix: address PR review feedback for lint command - Add email to trigger kinds test loop instead of separate test - Add email to ValidationTarget docs in README - Type formatYamlDiagnostics param directly instead of unsafe cast - Destructure json option before mergeConfigWithConfigFile for clarity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(cli): add --lint option to sync push command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Windmill YAML Validator
A TypeScript-based YAML validator for Windmill flow, schedule, and trigger files.
Overview
The windmill-yaml-validator provides runtime validation for Windmill YAML files. It is used by editor integrations to show validation errors while editing:
flow.yaml/flow.yml*.schedule.yaml/*.schedule.yml*.{http|websocket|kafka|nats|postgres|mqtt|sqs|gcp}_trigger.yaml(or.yml)
Features
- Unified validation API: One validator class for flow/schedule/trigger files
- Schema-based validation: Uses OpenFlow and backend OpenAPI-derived schemas
- Detailed error reporting: Returns comprehensive error information with specific paths to invalid fields
Installation
npm install windmill-yaml-validator
Usage
Basic Validation
import { WindmillYamlValidator } from "windmill-yaml-validator";
const validator = new WindmillYamlValidator();
const flowYaml = `
summary: Test Flow
value:
modules: []
`;
const flowResult = validator.validate(flowYaml, { type: "flow" });
const scheduleYaml = `
schedule: "0 0 12 * * *"
timezone: "UTC"
enabled: true
script_path: "f/jobs/daily_sync"
is_flow: false
`;
const scheduleResult = validator.validate(scheduleYaml, { type: "schedule" });
const triggerYaml = `
script_path: "f/triggers/http_handler"
is_flow: false
route_path: "api/webhook"
request_type: "sync"
authentication_method: "none"
http_method: "post"
is_static_website: false
workspaced_route: false
wrap_body: false
raw_string: false
`;
const triggerResult = validator.validate(triggerYaml, {
type: "trigger",
triggerKind: "http",
});
console.log(flowResult.errors, scheduleResult.errors, triggerResult.errors);
Target Inference by Filename
import {
WindmillYamlValidator,
getValidationTargetFromFilename,
} from "windmill-yaml-validator";
const validator = new WindmillYamlValidator();
const target = getValidationTargetFromFilename(
"f/webhooks/order_created.http_trigger.yaml"
);
if (target) {
const result = validator.validate(fileContents, target);
console.log(result.errors);
}
Error Handling
const invalidYaml = `
summary: 123 # Should be a string
value:
modules:
- id: step1
value:
type: rawscript
language: invalid_language # Invalid enum value
`;
const result = validator.validate(invalidYaml, { type: "flow" });
result.errors.forEach((error) => {
console.log(`Error at ${error.instancePath}: ${error.message}`);
// Example output:
// Error at /summary: must be string
// Error at /value/modules/0/value/language: must be equal to one of the allowed values
});
API
WindmillYamlValidator
Main validator class for Windmill YAML validation.
Constructor
new WindmillYamlValidator();
Initializes AJV validators for flow, schedule, and trigger schemas.
Methods
validate(doc: string, target: ValidationTarget)
Validates a YAML document against the selected target schema.
Parameters:
doc(string): YAML document stringtarget(ValidationTarget):{ type: "flow" }{ type: "schedule" }{ type: "trigger", triggerKind: "http" | "websocket" | "kafka" | "nats" | "postgres" | "mqtt" | "sqs" | "gcp" | "email" }
Returns:
{
parsed: YamlParserResult<unknown>; // Parsed YAML with source pointers
errors: ErrorObject[]; // Array of validation errors (empty if valid)
}
Throws:
- Error if
docis not a string
getValidationTargetFromFilename(path: string)
Infers validation target from file naming conventions. Returns null for unsupported files.
Development
Building
npm run build
The build process:
- Runs
gen_openflow_schema.shto generate:src/gen/openflow.jsonsrc/gen/schedule.jsonsrc/gen/triggers/*.json
- Removes discriminator mappings (not supported by AJV)
- Compiles TypeScript to JavaScript
Testing
npm test
Run tests in watch mode:
npm test:watch
Testing locally with the CLI
The Windmill CLI (cli/) is Deno-based and imports this package via npm:windmill-yaml-validator@1.1.0. Since Deno's npm: specifier always resolves from the npm registry, local testing requires a compatibility script that makes the TypeScript sources directly importable by Deno.
The deno-compat.sh script handles two Deno requirements:
- Adding
.tsextensions to relative imports - Adding
with { type: "json" }assertions to JSON imports
Steps:
- Apply Deno compatibility:
./deno-compat.sh
- Add the following entries to
cli/deno.jsonimports:
"npm:windmill-yaml-validator@1.1.0": "../windmill-yaml-validator/src/index.ts",
"ajv": "npm:ajv@^8.17.1",
"@stoplight/yaml": "npm:@stoplight/yaml@^4.3.0"
- Run the CLI directly with Deno:
cd ../cli
deno run -A src/main.ts lint
- When done, restore everything:
./deno-compat.sh -r # restore original imports
# Remove the 3 import map lines from cli/deno.json
Schema Generation
The validator uses a JSON schema generated from the OpenAPI specification:
./gen_openflow_schema.sh
This script:
- Converts
openflow.openapi.yamlandbackend/windmill-api/openapi.yamlinto JSON - Removes discriminator mappings for AJV compatibility
- Removes the
ToolValuediscriminator entirely (see below) - Generates standalone schedule/trigger schemas for CLI file shape
Why Remove Discriminators?
The OpenFlow schema uses OpenAPI discriminators for efficient type resolution in oneOf schemas. However, AJV's discriminator support has limitations:
- Discriminator Mappings: Not fully supported by AJV, so they are removed from all schemas
- ToolValue Discriminator: Completely removed because
FlowModuleToolusesallOfcomposition, which prevents AJV from finding the discriminator property (tool_type) at the expected location
Impact: Without discriminators, AJV falls back to standard oneOf validation, which:
- Tests each alternative until one matches
- Is slightly slower but still performant for our use case
- Provides the same validation correctness
- Works correctly with complex schema compositions like
allOf
Breaking Change
FlowValidator and validateFlow() were replaced by WindmillYamlValidator and validate(doc, target).