* fix(cli): lint against the checkout's schema, not the published validator Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): mirror the permissioned_as exclusion into agent guidance Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: version windmill-yaml-validator with the release, publish by hand Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: generate schemas with the validator's own yaml parser Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): declare ajv, no longer reaching tests via the validator Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: fail schema generation on a spec YAML syntax error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.0 KiB
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
npm run gen(scripts/gen-schemas.js) to generate:src/gen/openflow.jsonsrc/gen/schedule.jsonsrc/gen/triggers/*.json
- Removes discriminator mappings (not supported by AJV)
- Compiles TypeScript to JavaScript
Testing
npm install # cli/ installs this package with --omit=dev, so jest may be absent
npm test
Run tests in watch mode:
npm test:watch
Relationship with the CLI
wmill lint does not use the published package: it imports src/index.ts directly, and
cli's preinstall regenerates src/gen from the repo's OpenAPI specs. Lint therefore
always validates against the schema of the commit it is built from, and local changes here
are picked up with no publish or link step:
# In cli/
bun run src/main.ts lint
The published package still exists for editor integrations. CI does not publish it —
.github/change-versions.sh moves its version with each release, but pushing that version
to npm is a manual ./publish.sh.
Schema Generation
The validator uses a JSON schema generated from the OpenAPI specification:
npm run gen
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).