sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
151 lines (149 loc) • 6.95 kB
JavaScript
import { writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Worker } from "node:worker_threads";
import logSymbols from "log-symbols";
import readPkgUp from "read-pkg-up";
import { isatty } from "node:tty";
import chalk from "chalk";
const isTty = isatty(1), headers = {
error: isTty ? chalk.bold(chalk.bgRed(chalk.black(" ERROR "))) : chalk.red("[ERROR]"),
warning: isTty ? chalk.bold(chalk.bgYellow(chalk.black(" WARN "))) : chalk.yellow("[WARN]")
}, severityValues = {
error: 0,
warning: 1
};
function formatPath(pathSegments) {
const format = ([curr, ...next], mode = "object") => {
if (!curr) return "";
if (curr.kind === "property") return format(next, curr.name === "of" ? "array" : "object");
const name = curr.name ? curr.name : `<anonymous_${curr.type}>`;
return `${mode === "array" ? `[${name}]` : `.${name}`}${format(next)}`;
};
return format(pathSegments.slice(1)).slice(1);
}
function getAggregatedSeverity(groupOrGroups) {
return (Array.isArray(groupOrGroups) ? groupOrGroups : [groupOrGroups]).flatMap((group) => group.problems.map((problem) => problem.severity)).find((severity) => severity === "error") ? "error" : "warning";
}
function formatSchemaValidation(validation) {
let unnamedTopLevelTypeCount = 0;
return Object.entries(validation.reduce((acc, next) => {
const [firstSegment] = next.path;
if (!firstSegment || firstSegment.kind !== "type") return acc;
const topLevelType = firstSegment.name || `<unnamed_${firstSegment.type}_type_${unnamedTopLevelTypeCount++}>`, problems = acc[topLevelType] ?? [];
return problems.push(next), acc[topLevelType] = problems, acc;
}, {})).sort((a, b) => {
const [aType, aGroups] = a, [bType, bGroups] = b, aValue = severityValues[getAggregatedSeverity(aGroups)], bValue = severityValues[getAggregatedSeverity(bGroups)];
return aValue === bValue ? aType.localeCompare(bType, "en-US") : aValue - bValue;
}).map(([topLevelType, groups]) => {
const formattedTopLevelType = isTty ? chalk.bgWhite(chalk.black(` ${topLevelType} `)) : `[${topLevelType}]`, header = `${headers[getAggregatedSeverity(groups)]} ${formattedTopLevelType}`, body = groups.sort((a, b) => severityValues[getAggregatedSeverity(a)] - severityValues[getAggregatedSeverity(b)]).map((group) => {
const formattedPath = ` ${chalk.bold(formatPath(group.path) || "(root)")}`, formattedMessages = group.problems.sort((a, b) => severityValues[a.severity] - severityValues[b.severity]).map(({
severity,
message
}) => ` ${logSymbols[severity]} ${message}`).join(`
`);
return `${formattedPath}
${formattedMessages}`;
}).join(`
`);
return `${header}
${body}`;
}).join(`
`);
}
function generateMetafile(schema) {
const output = {
imports: [],
exports: [],
inputs: {},
bytes: 0
}, inputs = {};
function processType(path2, entry) {
let childSize = 0;
if (entry.fields)
for (const [name, fieldEntry] of Object.entries(entry.fields))
processType(`${path2}/${name}`, fieldEntry), childSize += fieldEntry.size;
if (entry.of)
for (const [name, fieldEntry] of Object.entries(entry.of))
processType(`${path2}/${name}`, fieldEntry), childSize += fieldEntry.size;
const selfSize = entry.size - childSize;
inputs[path2] = {
bytes: selfSize,
imports: [],
format: "esm"
}, output.inputs[path2] = {
bytesInOutput: selfSize
}, output.bytes += selfSize;
}
for (const [name, entry] of Object.entries(schema.types)) {
const fakePath = `schema/${entry.extends}/${name}`;
processType(fakePath, entry);
}
for (const [name, entry] of Object.entries(schema.hoisted)) {
const fakePath = `hoisted/${name}`;
processType(fakePath, entry);
}
return {
outputs: {
root: output
},
inputs
};
}
const __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
async function validateAction(args, {
workDir,
output
}) {
const flags = args.extOptions, rootPkgPath = readPkgUp.sync({
cwd: __dirname$1
})?.path;
if (!rootPkgPath)
throw new Error("Could not find root directory for `sanity` package");
const workerPath = path.join(path.dirname(rootPkgPath), "lib", "_internal", "cli", "threads", "validateSchema.cjs"), level = flags.level || "warning";
if (level !== "error" && level !== "warning")
throw new Error("Invalid level. Available levels are 'error' and 'warning'.");
const format = flags.format || "pretty";
if (!["pretty", "ndjson", "json"].includes(format))
throw new Error(`Did not recognize format '${flags.format}'. Available formats are 'pretty', 'ndjson', and 'json'.`);
let spinner;
format === "pretty" && (spinner = output.spinner(flags.workspace ? `Validating schema from workspace '${flags.workspace}'\u2026` : "Validating schema\u2026").start());
const worker = new Worker(workerPath, {
workerData: {
workDir,
level,
workspace: flags.workspace,
debugSerialize: !!flags["debug-metafile-path"]
},
env: process.env
}), {
validation,
serializedDebug
} = await new Promise((resolve, reject) => {
worker.addListener("message", resolve), worker.addListener("error", reject);
}), problems = validation.flatMap((group) => group.problems), errorCount = problems.filter((problem) => problem.severity === "error").length, warningCount = problems.filter((problem) => problem.severity === "warning").length, didFail = getAggregatedSeverity(validation) === "error";
if (flags["debug-metafile-path"] && !didFail) {
if (!serializedDebug) throw new Error("serializedDebug should always be produced");
const metafile = generateMetafile(serializedDebug);
writeFileSync(flags["debug-metafile-path"], JSON.stringify(metafile), "utf8");
}
switch (format) {
case "ndjson": {
for (const group of validation)
output.print(JSON.stringify(group));
break;
}
case "json": {
output.print(JSON.stringify(validation));
break;
}
default:
spinner?.succeed("Validated schema"), output.print(`
Validation results:`), output.print(`${logSymbols.error} Errors: ${errorCount.toLocaleString("en-US")} error${errorCount === 1 ? "" : "s"}`), level !== "error" && output.print(`${logSymbols.warning} Warnings: ${warningCount.toLocaleString("en-US")} warning${warningCount === 1 ? "" : "s"}`), output.print(), output.print(formatSchemaValidation(validation)), flags["debug-metafile-path"] && (output.print(), didFail ? output.print(`${logSymbols.info} Metafile not written due to validation errors`) : (output.print(`${logSymbols.info} Metafile written to: ${flags["debug-metafile-path"]}`), output.print(" This can be analyzed at https://esbuild.github.io/analyze/")));
}
process.exitCode = didFail ? 1 : 0;
}
export {
validateAction as default
};
//# sourceMappingURL=validateAction.js.map