Files
centdix 37d1277b91 feat(cli): add lint command (#7917)
* 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>
2026-02-13 16:41:04 +00:00

259 lines
6.3 KiB
Markdown

# 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
```bash
npm install windmill-yaml-validator
```
## Usage
### Basic Validation
```typescript
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
```typescript
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
```typescript
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
```typescript
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 string
- `target` (`ValidationTarget`):
- `{ type: "flow" }`
- `{ type: "schedule" }`
- `{ type: "trigger", triggerKind: "http" | "websocket" | "kafka" | "nats" | "postgres" | "mqtt" | "sqs" | "gcp" | "email" }`
**Returns:**
```typescript
{
parsed: YamlParserResult<unknown>; // Parsed YAML with source pointers
errors: ErrorObject[]; // Array of validation errors (empty if valid)
}
```
**Throws:**
- Error if `doc` is not a string
### `getValidationTargetFromFilename(path: string)`
Infers validation target from file naming conventions. Returns `null` for unsupported files.
## Development
### Building
```bash
npm run build
```
The build process:
1. Runs `gen_openflow_schema.sh` to generate:
- `src/gen/openflow.json`
- `src/gen/schedule.json`
- `src/gen/triggers/*.json`
2. Removes discriminator mappings (not supported by AJV)
3. Compiles TypeScript to JavaScript
### Testing
```bash
npm test
```
Run tests in watch mode:
```bash
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 `.ts` extensions to relative imports
- Adding `with { type: "json" }` assertions to JSON imports
**Steps:**
1. Apply Deno compatibility:
```bash
./deno-compat.sh
```
2. Add the following entries to `cli/deno.json` imports:
```json
"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"
```
3. Run the CLI directly with Deno:
```bash
cd ../cli
deno run -A src/main.ts lint
```
4. When done, restore everything:
```bash
./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:
```bash
./gen_openflow_schema.sh
```
This script:
- Converts `openflow.openapi.yaml` and `backend/windmill-api/openapi.yaml` into JSON
- Removes discriminator mappings for AJV compatibility
- Removes the `ToolValue` discriminator 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:
1. **Discriminator Mappings**: Not fully supported by AJV, so they are removed from all schemas
2. **ToolValue Discriminator**: Completely removed because `FlowModuleTool` uses `allOf` composition, 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)`.