Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/command/dev-call/cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "cliffy/command/mod.ts";
import { quartoConfig } from "../../core/quarto.ts";
import { commands } from "../command.ts";
import { buildJsCommand } from "./build-artifacts/cmd.ts";
import { hidden } from "../../core/lib/external/colors.ts";
import { validateYamlCommand } from "./validate-yaml/cmd.ts";

type CommandOptionInfo = {
name: string;
Expand Down Expand Up @@ -70,4 +70,5 @@ export const devCallCommand = new Command()
Deno.exit(1);
})
.command("cli-info", generateCliInfoCommand)
.command("validate-yaml", validateYamlCommand)
.command("build-artifacts", buildJsCommand);
88 changes: 88 additions & 0 deletions src/command/dev-call/validate-yaml/cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* cmd.ts
*
* Copyright (C) 2025 Posit Software, PBC
*/

import { Command } from "cliffy/command/mod.ts";
import { initYamlIntelligenceResourcesFromFilesystem } from "../../../core/schema/utils.ts";
import { readAndValidateYamlFromMappedString } from "../../../core/lib/yaml-schema/validated-yaml.ts";
import { mappedStringFromFile } from "../../../core/mapped-text.ts";
import {
getSchemaDefinition,
setSchemaDefinition,
} from "../../../core/lib/yaml-validation/schema.ts";
import { error } from "../../../deno_ral/log.ts";
import { tidyverseFormatError } from "../../../core/lib/errors.ts";
import {
convertFromYaml,
getSchemaSchemas,
} from "../../../core/lib/yaml-schema/from-yaml.ts";

const getSchema = async (schemaNameOrFile: string) => {
if (schemaNameOrFile.endsWith(".yml")) {
getSchemaSchemas();
// it's a file, we load it, validate it against the schema schema
// and then return it
const file = mappedStringFromFile(schemaNameOrFile);
const schema = getSchemaDefinition("schema/schema");
const result = await readAndValidateYamlFromMappedString(
file,
schema,
);
if (result.yamlValidationErrors.length) {
error("Schema file is not valid");
for (const err of result.yamlValidationErrors) {
error(tidyverseFormatError(err.niceError), { colorize: false });
}
Deno.exit(1);
}
const schemaName = `user-schema-${schemaNameOrFile}`;
const newSchema = convertFromYaml(result.yaml);
newSchema.$id = schemaName;
setSchemaDefinition(newSchema);
return getSchemaDefinition(schemaName);
} else {
// it's a schema name, we get it from the schema registry
// and return it
return getSchemaDefinition(schemaNameOrFile);
}
};

export const validateYamlCommand = new Command()
.name("validate-yaml")
.hidden()
.arguments("<input:string>")
.option(
"-s, --schema [schema:string]",
"Name of schema in Quarto's definitions.yml. If string ends with .yml, it is treated as a file name for a new schema, which is validated, loaded, and then used.",
)
.option(
"--json",
"If set, output error messages in JSON format.",
)
.description(
"Validates a YAML file against Quarto's schemas.\n\n",
)
.action(async (options: any, input: string) => {
await initYamlIntelligenceResourcesFromFilesystem();
if (!options.schema) {
throw new Error("Schema name or file is required");
}
const file = mappedStringFromFile(input);
const schema = await getSchema(options.schema);
const result = await readAndValidateYamlFromMappedString(
file,
schema,
);
if (options.json) {
console.log(JSON.stringify(result.yamlValidationErrors, null, 2));
} else {
for (const err of result.yamlValidationErrors) {
error(tidyverseFormatError(err.niceError), { colorize: false });
}
}
if (result.yamlValidationErrors.length) {
Deno.exit(1);
}
});
Loading