Files
centdix 835db5d290 feat(cli): detect missing folders on sync push and add 'wmill folder add-missing' (#8011)
* 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>
2026-02-24 08:38:17 +00:00
..
2026-02-13 16:41:04 +00:00

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 string
  • target (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 doc is 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:

  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

npm test

Run tests in watch mode:

npm test:watch

Testing locally with the CLI

To test local changes before publishing, use npm link:

# 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:

./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).