* fix: auto-create missing folders during sync push for non-admin users Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: show missing folders in sync push summary before confirmation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: improve sync push folder auto-creation error handling and json output Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: only treat 404 as missing folder in getFolder check Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: remove obsolete Deno compatibility layer from yaml-validator Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(cli): add @types/bun dev dependency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): replace auto-create folders with `wmill folder add-missing` command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(cli): improve folder commands with summary field and simpler push API Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): add confirmation prompt to folder add-missing command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(cli): simplify missing folder check to use local stat instead of remote API Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * update skills * feat(cli): warn admins but block non-admins on missing folder.meta.yaml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * cleaning * cleaning * test(cli): add tests for missing folder detection and folder commands - Add tests for `folder new`, `folder push`, `folder add-missing` commands - Add tests for sync push missing folder.meta.yaml detection (admin warning, non-admin block) - Fix getBasePostgresUrl to strip query params (e.g. ?sslmode=disable) from DATABASE_URL - Add createNonAdminUser and runCLIWithToken test utilities to test_backend.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(cli): unify runCLICommand with optional token parameter Replace separate runCLIWithToken utility with an optional { workspace?, token? } options object on the existing runCLICommand across all backends. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * own workspace * test(cli): isolate folder_missing_meta tests with per-test workspace * test(cli): shorten isolated workspace id/name for workspace limits * test(cli): archive temp isolated workspaces after each folder test --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
235 lines
5.5 KiB
Markdown
235 lines
5.5 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
|
|
|
|
To test local changes before publishing, use `npm link`:
|
|
|
|
```bash
|
|
# In windmill-yaml-validator/
|
|
npm run build
|
|
npm link
|
|
|
|
# In cli/
|
|
npm link windmill-yaml-validator
|
|
bun run src/main.ts lint
|
|
```
|
|
|
|
### 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)`.
|