import fs from "node:fs"; import path from "node:path"; import Ajv, { type JSONSchemaType } from "ajv"; import findConfig from "find-config"; const ajv = new Ajv(); export type WrapAttributes = | "auto" | "force" | "force-aligned" | "force-expand-multiline" | "aligned-multiple" | "preserve" | "preserve-aligned"; export type SortHtmlAttributes = | "none" | "alphabetical" | "code-guide" | "idiomatic" | "vuejs" | "custom"; export type EndOfLine = "LF" | "CRLF"; export interface RuntimeConfig { indentSize?: number; wrapLineLength?: number; wrapAttributes?: WrapAttributes; wrapAttributesMinAttrs?: number; indentInnerHtml?: boolean; endWithNewline?: boolean; endOfLine?: EndOfLine; useTabs?: boolean; sortTailwindcssClasses?: boolean; tailwindcssConfigPath?: string; sortHtmlAttributes?: SortHtmlAttributes; customHtmlAttributesOrder?: string[] | string; noMultipleEmptyLines?: boolean; noPhpSyntaxCheck?: boolean; noSingleQuote?: boolean; noTrailingCommaPhp?: boolean; extraLiners?: string[]; componentPrefix?: string[]; } const defaultConfigNames = [".bladeformatterrc.json", ".bladeformatterrc"]; export function findRuntimeConfig(filePath: string): string | null { for (let i = 0; i < defaultConfigNames.length; i += 1) { const result: string | null = findConfig(defaultConfigNames[i], { cwd: path.dirname(filePath), home: false, }); if (result) { return result; } } return null; } export async function readRuntimeConfig( filePath: string | null, ): Promise { if (filePath === null) { return undefined; } const options = JSON.parse((await fs.promises.readFile(filePath)).toString()); const schema: JSONSchemaType = { type: "object", properties: { indentSize: { type: "integer", nullable: true }, wrapLineLength: { type: "integer", nullable: true }, wrapAttributes: { type: "string", enum: [ "auto", "force", "force-aligned", "force-expand-multiline", "aligned-multiple", "preserve", "preserve-aligned", ], nullable: true, }, wrapAttributesMinAttrs: { type: "integer", nullable: true, default: 2 }, indentInnerHtml: { type: "boolean", nullable: true }, endWithNewline: { type: "boolean", nullable: true }, endOfLine: { type: "string", enum: ["LF", "CRLF"], nullable: true }, useTabs: { type: "boolean", nullable: true }, sortTailwindcssClasses: { type: "boolean", nullable: true }, tailwindcssConfigPath: { type: "string", nullable: true }, sortHtmlAttributes: { type: "string", enum: [ "none", "alphabetical", "code-guide", "idiomatic", "vuejs", "custom", ], nullable: true, }, customHtmlAttributesOrder: { type: "array", nullable: true, items: { type: "string" }, default: [], }, noMultipleEmptyLines: { type: "boolean", nullable: true }, noPhpSyntaxCheck: { type: "boolean", nullable: true }, noSingleQuote: { type: "boolean", nullable: true }, noTrailingCommaPhp: { type: "boolean", nullable: true }, extraLiners: { type: "array", nullable: true, items: { type: "string" }, default: ["head", "body", "/html"], }, componentPrefix: { type: "array", nullable: true, items: { type: "string" }, default: ["x-", "livewire:"], }, }, additionalProperties: true, }; const validate = ajv.compile(schema); if (!validate(options)) { throw validate; } return options; }