mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
* 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>
123 lines
3.9 KiB
JavaScript
123 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// Generates the AJV-ready JSON schemas in src/gen from the repo's OpenAPI specs.
|
|
// Kept in plain node (no shell) so it runs identically on Windows, where the CLI
|
|
// builds and tests this package as a local dependency.
|
|
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
// The package's own runtime YAML parser, so generating schemas needs no extra dependency.
|
|
const { parseWithPointers } = require("@stoplight/yaml");
|
|
|
|
const { generateSchemas } = require("./generate-resource-schemas.js");
|
|
|
|
// Severity 0 is DiagnosticSeverity.Error.
|
|
const YAML_ERROR = 0;
|
|
|
|
// parseWithPointers recovers from syntax errors rather than throwing, so an unchecked parse
|
|
// can hand back a spec silently missing whatever it failed to read — schemas that drop a
|
|
// definition and lint that quietly stops validating it.
|
|
function parseSpec(filePath) {
|
|
const { data, diagnostics } = parseWithPointers(
|
|
fs.readFileSync(filePath, "utf8")
|
|
);
|
|
const errors = diagnostics.filter((d) => d.severity === YAML_ERROR);
|
|
if (errors.length > 0) {
|
|
throw new Error(
|
|
`${filePath} is not valid YAML:\n` +
|
|
errors
|
|
.map((d) => ` line ${d.range.start.line + 1}: ${d.message}`)
|
|
.join("\n")
|
|
);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
const packageDir = path.join(__dirname, "..");
|
|
const repoDir = path.join(packageDir, "..");
|
|
const outputDir = path.join(packageDir, "src", "gen");
|
|
|
|
function removeDiscriminatorMappings(schema) {
|
|
const removeMapping = (obj) => {
|
|
if (obj && typeof obj === "object") {
|
|
if (obj.discriminator?.mapping) delete obj.discriminator.mapping;
|
|
for (const v of Object.values(obj)) removeMapping(v);
|
|
}
|
|
};
|
|
removeMapping(schema);
|
|
|
|
// ToolValue's discriminator does not work with the allOf in FlowModuleTool
|
|
if (schema.components?.schemas?.ToolValue?.discriminator) {
|
|
delete schema.components.schemas.ToolValue.discriminator;
|
|
}
|
|
}
|
|
|
|
// AJV does not handle OpenAPI 3.0 `nullable: true` combined with `enum` — null must
|
|
// be explicitly listed in the enum for validation to accept null values.
|
|
function addNullToNullableEnums(obj) {
|
|
if (!obj || typeof obj !== "object") return;
|
|
if (Array.isArray(obj)) {
|
|
obj.forEach(addNullToNullableEnums);
|
|
return;
|
|
}
|
|
if (
|
|
obj.nullable === true &&
|
|
Array.isArray(obj.enum) &&
|
|
!obj.enum.includes(null)
|
|
) {
|
|
obj.enum.push(null);
|
|
}
|
|
for (const v of Object.values(obj)) addNullToNullableEnums(v);
|
|
}
|
|
|
|
function main() {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wmill-yaml-validator-"));
|
|
try {
|
|
fs.mkdirSync(path.join(outputDir, "triggers"), { recursive: true });
|
|
|
|
const openflowOutputPath = path.join(outputDir, "openflow.json");
|
|
const backendOutputPath = path.join(tmpDir, "backend-openapi.json");
|
|
|
|
const openflowSchema = parseSpec(
|
|
path.join(repoDir, "openflow.openapi.yaml")
|
|
);
|
|
const backendSchema = parseSpec(
|
|
path.join(repoDir, "backend", "windmill-api", "openapi.yaml")
|
|
);
|
|
|
|
removeDiscriminatorMappings(openflowSchema);
|
|
|
|
fs.writeFileSync(
|
|
openflowOutputPath,
|
|
JSON.stringify(openflowSchema, null, 2) + "\n"
|
|
);
|
|
fs.writeFileSync(
|
|
backendOutputPath,
|
|
JSON.stringify(backendSchema, null, 2) + "\n"
|
|
);
|
|
|
|
generateSchemas(backendOutputPath, openflowOutputPath, outputDir);
|
|
|
|
const files = [
|
|
openflowOutputPath,
|
|
path.join(outputDir, "schedule.json"),
|
|
...fs
|
|
.readdirSync(path.join(outputDir, "triggers"))
|
|
.map((f) => path.join(outputDir, "triggers", f)),
|
|
].filter((f) => f.endsWith(".json"));
|
|
|
|
for (const file of files) {
|
|
const schema = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
addNullToNullableEnums(schema);
|
|
fs.writeFileSync(file, JSON.stringify(schema, null, 2) + "\n");
|
|
}
|
|
|
|
console.log(`Generated ${files.length} schemas in ${outputDir}`);
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
main();
|