@yukyu30/fluorite 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/parse.ts","../src/assertion.ts","../src/recorder.ts","../src/check.ts","../src/config.ts","../src/report.ts"],"sourcesContent":["export { check, checkData } from \"./check.js\";\nexport { defineConfig, loadConfig, resolveConfigPath } from \"./config.js\";\nexport { parseFrontmatter } from \"./parse.js\";\nexport type { ParseResult } from \"./parse.js\";\nexport { Recorder } from \"./recorder.js\";\nexport { KeyAssertion, EachAssertion } from \"./assertion.js\";\nexport { formatReports } from \"./report.js\";\nexport type { FileReport, FormatOptions } from \"./report.js\";\nexport type {\n CheckResult,\n RuleResult,\n RulesFn,\n FluoriteConfig,\n ValueType,\n} from \"./types.js\";\n","import matter from \"gray-matter\";\n\n/** Outcome of parsing frontmatter out of a Markdown source string. */\nexport interface ParseResult {\n /** Parsed frontmatter data (empty object when none / on error). */\n data: Record<string, unknown>;\n /** The Markdown body following the frontmatter block. */\n content: string;\n /** Whether a frontmatter block was present at all. */\n hasFrontmatter: boolean;\n /** Parse error message, if the frontmatter (YAML) was malformed. */\n error?: string;\n}\n\n/**\n * Extract frontmatter from a Markdown source string.\n *\n * Never throws: malformed YAML is reported via the `error` field so callers\n * can record it as a failure rather than crash.\n */\nexport function parseFrontmatter(source: string): ParseResult {\n const hasFrontmatter = /^?\\s*---\\r?\\n/.test(source);\n try {\n const parsed = matter(source);\n const data = (parsed.data ?? {}) as Record<string, unknown>;\n return {\n data,\n content: parsed.content,\n hasFrontmatter,\n };\n } catch (err) {\n return {\n data: {},\n content: source,\n hasFrontmatter,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { Recorder } from \"./recorder.js\";\nimport type { RuleResult, ValueType } from \"./types.js\";\n\n/** Sentinel meaning \"the key was not present in the frontmatter\". */\nconst MISSING = Symbol(\"missing\");\n\nfunction valueType(value: unknown): ValueType | \"undefined\" {\n if (value === MISSING) return \"undefined\";\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n const t = typeof value;\n if (t === \"string\" || t === \"number\" || t === \"boolean\" || t === \"object\") {\n return t;\n }\n return \"undefined\";\n}\n\nfunction lengthOf(value: unknown): number | undefined {\n if (typeof value === \"string\" || Array.isArray(value)) return value.length;\n return undefined;\n}\n\nfunction display(value: unknown): string {\n if (value === MISSING) return \"undefined\";\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Fluent, chainable assertions for a single frontmatter key.\n *\n * Each terminal matcher records one {@link RuleResult} on the parent\n * {@link Recorder} and returns `this`, so matchers can be chained:\n *\n * ```ts\n * fm.key(\"title\").required().type(\"string\").lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * ```\n *\n * The `.not` modifier negates only the next matcher, then resets.\n */\nexport class KeyAssertion {\n #negated = false;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n private readonly present: boolean,\n ) {}\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n /** The raw value (resolved against the sentinel) used by matchers. */\n private get resolved(): unknown {\n return this.present ? this.value : MISSING;\n }\n\n /**\n * Record a result, applying the pending `.not` negation, then reset it.\n */\n private record(\n rule: string,\n rawPass: boolean,\n describe: (negated: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const ok = negated ? !rawPass : rawPass;\n const result: RuleResult = {\n key: this.key,\n rule,\n ok,\n negated,\n message: describe(negated),\n value: this.present ? this.value : undefined,\n expected,\n };\n this.recorder.push(result);\n return this;\n }\n\n /** Assert the key exists in the frontmatter. */\n required(): this {\n return this.record(\n \"required\",\n this.present,\n (neg) => (neg ? `should not exist` : `is required`),\n );\n }\n\n /** Alias of {@link required}. */\n exists(): this {\n return this.record(\n \"exists\",\n this.present,\n (neg) => (neg ? `should not exist` : `should exist`),\n );\n }\n\n /** Assert the value is of the given type. */\n type(expected: ValueType): this {\n const actual = valueType(this.resolved);\n return this.record(\n \"type\",\n actual === expected,\n (neg) =>\n neg\n ? `should not be of type ${expected}`\n : `should be of type ${expected} (was ${actual})`,\n expected,\n );\n }\n\n /** Assert the value strictly equals `expected` (deep for arrays/objects). */\n eq(expected: unknown): this {\n return this.record(\n \"eq\",\n deepEqual(this.resolved, expected),\n (neg) =>\n neg\n ? `should not equal ${display(expected)}`\n : `should equal ${display(expected)}`,\n expected,\n );\n }\n\n /**\n * Assert the value is an array whose every element is in `allowed` (enum).\n *\n * Designed for catching tag notation drift: define the canonical set once\n * and any stray / mistyped value is reported.\n *\n * ```ts\n * fm.key(\"tags\").subsetOf([\"ok\", \"release\", \"blog\"]);\n * ```\n */\n subsetOf(allowed: readonly unknown[]): this {\n const v = this.resolved;\n const isArray = Array.isArray(v);\n const invalid = isArray\n ? v.filter((el) => !allowed.some((a) => deepEqual(el, a)))\n : [];\n return this.record(\n \"subsetOf\",\n isArray && invalid.length === 0,\n (neg) => {\n if (!isArray) return `should be an array of values from ${display(allowed)}`;\n if (neg) return `should contain values outside ${display(allowed)}`;\n return invalid.length\n ? `all items should be one of ${display(allowed)} (invalid: ${display(invalid)})`\n : `all items should be one of ${display(allowed)}`;\n },\n allowed,\n );\n }\n\n /** Alias of {@link subsetOf}. */\n only(allowed: readonly unknown[]): this {\n return this.subsetOf(allowed);\n }\n\n /**\n * Apply matchers to every element of an array value.\n *\n * ```ts\n * fm.key(\"tags\").each.oneOf([\"ok\", \"release\"]);\n * fm.key(\"tags\").each.matches(/^[a-z0-9-]+$/);\n * ```\n */\n get each(): EachAssertion {\n const negated = this.#negated;\n this.#negated = false;\n return new EachAssertion(this.recorder, this.key, this.resolved, negated);\n }\n\n /** Assert the value is one of `allowed` (enum). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n allowed.some((a) => deepEqual(this.resolved, a)),\n (neg) =>\n neg\n ? `should not be one of ${display(allowed)}`\n : `should be one of ${display(allowed)}`,\n allowed,\n );\n }\n\n /** Assert a string value matches the given regular expression. */\n matches(pattern: RegExp): this {\n const v = this.resolved;\n const pass = typeof v === \"string\" && pattern.test(v);\n return this.record(\n \"matches\",\n pass,\n (neg) =>\n neg\n ? `should not match ${pattern}`\n : `should match ${pattern}`,\n pattern.source,\n );\n }\n\n /** Assert an array contains `item`, or a string contains the substring. */\n has(item: unknown): this {\n const v = this.resolved;\n let pass = false;\n if (Array.isArray(v)) pass = v.some((el) => deepEqual(el, item));\n else if (typeof v === \"string\" && typeof item === \"string\")\n pass = v.includes(item);\n return this.record(\n \"has\",\n pass,\n (neg) =>\n neg ? `should not have ${display(item)}` : `should have ${display(item)}`,\n item,\n );\n }\n\n /** Assert an array/string contains all of `items`. */\n hasAll(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.every((item) => contains(v, item));\n return this.record(\n \"hasAll\",\n pass,\n (neg) =>\n neg\n ? `should not have all of ${display(items)}`\n : `should have all of ${display(items)}`,\n items,\n );\n }\n\n /** Assert an array/string contains at least one of `items`. */\n hasAny(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.some((item) => contains(v, item));\n return this.record(\n \"hasAny\",\n pass,\n (neg) =>\n neg\n ? `should not have any of ${display(items)}`\n : `should have any of ${display(items)}`,\n items,\n );\n }\n\n /** Assert the string/array length equals `n`. */\n length(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"length\",\n len === n,\n (neg) =>\n neg\n ? `length should not be ${n} (was ${len ?? \"n/a\"})`\n : `length should be ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at least `n`. */\n lengthMin(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMin\",\n len !== undefined && len >= n,\n (neg) =>\n neg\n ? `length should be < ${n} (was ${len ?? \"n/a\"})`\n : `length should be >= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at most `n`. */\n lengthMax(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMax\",\n len !== undefined && len <= n,\n (neg) =>\n neg\n ? `length should be > ${n} (was ${len ?? \"n/a\"})`\n : `length should be <= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n}\n\n/**\n * Applies matchers to every element of an array frontmatter value.\n *\n * Obtained via {@link KeyAssertion.each}. Each terminal matcher records a\n * single {@link RuleResult}: it passes only when the value is an array and\n * every element satisfies the matcher; failures list the offending elements.\n */\nexport class EachAssertion {\n #negated: boolean;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n negated: boolean,\n ) {\n this.#negated = negated;\n }\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n private record(\n rule: string,\n perElement: (el: unknown) => boolean,\n describe: (negated: boolean, invalid: unknown[], isArray: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const isArray = Array.isArray(this.value);\n const invalid = isArray\n ? (this.value as unknown[]).filter((el) => !perElement(el))\n : [];\n const rawPass = isArray && invalid.length === 0;\n // A non-array can never satisfy a per-element check, even when negated.\n const ok = isArray ? (negated ? !rawPass : rawPass) : false;\n this.recorder.push({\n key: this.key,\n rule: `each.${rule}`,\n ok,\n negated,\n message: describe(negated, invalid, isArray),\n value: this.value === MISSING ? undefined : this.value,\n expected,\n });\n return this;\n }\n\n /** Every element must be one of `allowed` (enum over array contents). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n (el) => allowed.some((a) => deepEqual(el, a)),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of values from ${display(allowed)}`\n : neg\n ? `every item should be outside ${display(allowed)}`\n : `every item should be one of ${display(allowed)}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n allowed,\n );\n }\n\n /** Every element must be of the given type. */\n type(expected: ValueType): this {\n return this.record(\n \"type\",\n (el) => valueType(el) === expected,\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of ${expected}`\n : neg\n ? `every item should not be of type ${expected}`\n : `every item should be of type ${expected}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n expected,\n );\n }\n\n /** Every (string) element must match the pattern. */\n matches(pattern: RegExp): this {\n return this.record(\n \"matches\",\n (el) => typeof el === \"string\" && pattern.test(el),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of strings matching ${pattern}`\n : neg\n ? `every item should not match ${pattern}`\n : `every item should match ${pattern}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n pattern.source,\n );\n }\n}\n\nfunction contains(container: unknown, item: unknown): boolean {\n if (Array.isArray(container)) return container.some((el) => deepEqual(el, item));\n if (typeof container === \"string\" && typeof item === \"string\")\n return container.includes(item);\n return false;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((el, i) => deepEqual(el, b[i]));\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n return ak.every((k) => deepEqual(ao[k], bo[k]));\n}\n","import { KeyAssertion } from \"./assertion.js\";\nimport type { RuleResult } from \"./types.js\";\n\n/**\n * The `fm` object passed to a rule set. Holds the parsed frontmatter data,\n * hands out {@link KeyAssertion} instances via {@link key}, and accumulates\n * every {@link RuleResult} the matchers record.\n */\nexport class Recorder {\n readonly results: RuleResult[] = [];\n\n constructor(readonly data: Record<string, unknown>) {}\n\n /** Begin a chain of assertions against the given frontmatter key. */\n key(name: string): KeyAssertion {\n const present = Object.prototype.hasOwnProperty.call(this.data, name);\n return new KeyAssertion(this, name, this.data[name], present);\n }\n\n /** Internal: append a recorded rule result. */\n push(result: RuleResult): void {\n this.results.push(result);\n }\n}\n","import { parseFrontmatter } from \"./parse.js\";\nimport { Recorder } from \"./recorder.js\";\nimport type { CheckResult, RulesFn, RuleResult } from \"./types.js\";\n\n/**\n * Run a rule set against the frontmatter of a Markdown source string.\n *\n * Never throws: malformed YAML or a missing frontmatter block is surfaced as a\n * failing {@link RuleResult} so results can always be collected and reported.\n *\n * ```ts\n * const result = check(markdown, (fm) => {\n * fm.key(\"title\").required().lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * });\n * if (!result.ok) console.error(result.failures);\n * ```\n */\nexport function check(source: string, rules: RulesFn): CheckResult {\n const parsed = parseFrontmatter(source);\n const recorder = new Recorder(parsed.data);\n\n if (parsed.error) {\n recorder.push(parseErrorResult(`invalid frontmatter: ${parsed.error}`));\n } else if (!parsed.hasFrontmatter) {\n recorder.push(parseErrorResult(\"no frontmatter block found\"));\n } else {\n rules(recorder);\n }\n\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return {\n ok: failures.length === 0,\n results,\n failures,\n data: parsed.data,\n };\n}\n\n/** Run a rule set against already-parsed frontmatter data. */\nexport function checkData(\n data: Record<string, unknown>,\n rules: RulesFn,\n): CheckResult {\n const recorder = new Recorder(data);\n rules(recorder);\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return { ok: failures.length === 0, results, failures, data };\n}\n\nfunction parseErrorResult(message: string): RuleResult {\n return {\n key: \"(frontmatter)\",\n rule: \"parse\",\n ok: false,\n negated: false,\n message,\n value: undefined,\n };\n}\n","import { pathToFileURL } from \"node:url\";\nimport { access } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { FluoriteConfig } from \"./types.js\";\n\n/**\n * Identity helper for authoring a config file with full type inference:\n *\n * ```ts\n * import { defineConfig } from \"fluorite\";\n * export default defineConfig({\n * include: [\"docs/**\\/*.md\"],\n * rules: (fm) => fm.key(\"title\").required(),\n * });\n * ```\n */\nexport function defineConfig(config: FluoriteConfig): FluoriteConfig {\n return config;\n}\n\nconst CONFIG_NAMES = [\n \"fluorite.config.js\",\n \"fluorite.config.mjs\",\n \"fluorite.config.cjs\",\n];\n\n/**\n * Locate a config file: an explicit path, or the first conventional name found\n * in `cwd`. Returns `undefined` when none is found.\n */\nexport async function resolveConfigPath(\n explicit: string | undefined,\n cwd = process.cwd(),\n): Promise<string | undefined> {\n if (explicit) return resolve(cwd, explicit);\n for (const name of CONFIG_NAMES) {\n const candidate = resolve(cwd, name);\n if (await fileExists(candidate)) return candidate;\n }\n return undefined;\n}\n\n/** Dynamically import a config file and return its default export. */\nexport async function loadConfig(path: string): Promise<FluoriteConfig> {\n const mod = await import(pathToFileURL(path).href);\n const config = (mod.default ?? mod) as FluoriteConfig;\n if (!config || typeof config.rules !== \"function\") {\n throw new Error(\n `Config at ${path} must export a default object with a \"rules\" function.`,\n );\n }\n return config;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\nimport type { CheckResult } from \"./types.js\";\n\n/** A check result paired with the file it came from. */\nexport interface FileReport {\n file: string;\n result: CheckResult;\n}\n\nexport interface FormatOptions {\n /** Suppress per-file lines for passing files. */\n quiet?: boolean;\n}\n\nfunction displayValue(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Format file reports into a human-readable, colorized string. */\nexport function formatReports(\n reports: FileReport[],\n options: FormatOptions = {},\n): string {\n const lines: string[] = [];\n let passed = 0;\n let failed = 0;\n let ruleFailures = 0;\n\n for (const { file, result } of reports) {\n if (result.ok) {\n passed++;\n if (!options.quiet) lines.push(`${pc.green(\"✔\")} ${file}`);\n } else {\n failed++;\n lines.push(`${pc.red(\"✘\")} ${file}`);\n for (const failure of result.failures) {\n ruleFailures++;\n const where = pc.dim(`(value: ${displayValue(failure.value)})`);\n lines.push(\n ` ${pc.red(\"✘\")} ${pc.bold(failure.key)}: ${failure.message} ${where}`,\n );\n }\n }\n }\n\n const summary = [\n `${reports.length} files`,\n pc.green(`${passed} passed`),\n failed > 0 ? pc.red(`${failed} failed`) : `${failed} failed`,\n `${ruleFailures} rule failures`,\n ].join(\", \");\n\n lines.push(\"\");\n lines.push(summary);\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["matter","pc"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/parse.ts","../src/assertion.ts","../src/recorder.ts","../src/check.ts","../src/config.ts","../src/report.ts"],"sourcesContent":["export { check, checkData } from \"./check.js\";\nexport { defineConfig, loadConfig, resolveConfigPath } from \"./config.js\";\nexport { parseFrontmatter } from \"./parse.js\";\nexport type { ParseResult } from \"./parse.js\";\nexport { Recorder } from \"./recorder.js\";\nexport { KeyAssertion, EachAssertion } from \"./assertion.js\";\nexport { formatReports } from \"./report.js\";\nexport type { FileReport, FormatOptions } from \"./report.js\";\nexport type {\n CheckResult,\n RuleResult,\n RulesFn,\n FluoriteConfig,\n ValueType,\n} from \"./types.js\";\n","import matter from \"gray-matter\";\nimport yaml from \"js-yaml\";\n\n/**\n * gray-matter's default YAML engine follows YAML 1.1 and coerces unquoted\n * dates (`date: 2026-06-07`) into JavaScript `Date` objects. That erases how\n * the value was written, so the format can no longer be linted — a clean\n * `YYYY-MM-DD`, a full timestamp, and an impossible date all collapse to a\n * `Date` (or get silently re-serialised).\n *\n * We swap in js-yaml's `CORE_SCHEMA`, which omits the `!!timestamp` type, so\n * every date stays a verbatim string. Matchers like `isoDate()` / `matches()`\n * can then validate the written `YYYY-MM-DD` form without requiring every\n * value to be quoted. Booleans, numbers and `null` are still parsed.\n */\nconst keepDatesAsStrings = (input: string): object =>\n (yaml.load(input, { schema: yaml.CORE_SCHEMA }) as object) ?? {};\n\n/** Outcome of parsing frontmatter out of a Markdown source string. */\nexport interface ParseResult {\n /** Parsed frontmatter data (empty object when none / on error). */\n data: Record<string, unknown>;\n /** The Markdown body following the frontmatter block. */\n content: string;\n /** Whether a frontmatter block was present at all. */\n hasFrontmatter: boolean;\n /** Parse error message, if the frontmatter (YAML) was malformed. */\n error?: string;\n}\n\n/**\n * Extract frontmatter from a Markdown source string.\n *\n * Never throws: malformed YAML is reported via the `error` field so callers\n * can record it as a failure rather than crash.\n */\nexport function parseFrontmatter(source: string): ParseResult {\n const hasFrontmatter = /^?\\s*---\\r?\\n/.test(source);\n try {\n const parsed = matter(source, { engines: { yaml: keepDatesAsStrings } });\n const data = (parsed.data ?? {}) as Record<string, unknown>;\n return {\n data,\n content: parsed.content,\n hasFrontmatter,\n };\n } catch (err) {\n return {\n data: {},\n content: source,\n hasFrontmatter,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { Recorder } from \"./recorder.js\";\nimport type { RuleResult, ValueType } from \"./types.js\";\n\n/** Sentinel meaning \"the key was not present in the frontmatter\". */\nconst MISSING = Symbol(\"missing\");\n\nfunction valueType(value: unknown): ValueType | \"undefined\" {\n if (value === MISSING) return \"undefined\";\n if (value === null) return \"null\";\n if (value instanceof Date) return \"date\";\n if (Array.isArray(value)) return \"array\";\n const t = typeof value;\n if (t === \"string\" || t === \"number\" || t === \"boolean\" || t === \"object\") {\n return t;\n }\n return \"undefined\";\n}\n\nfunction lengthOf(value: unknown): number | undefined {\n if (typeof value === \"string\" || Array.isArray(value)) return value.length;\n return undefined;\n}\n\nfunction display(value: unknown): string {\n if (value === MISSING) return \"undefined\";\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Fluent, chainable assertions for a single frontmatter key.\n *\n * Each terminal matcher records one {@link RuleResult} on the parent\n * {@link Recorder} and returns `this`, so matchers can be chained:\n *\n * ```ts\n * fm.key(\"title\").required().type(\"string\").lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * ```\n *\n * The `.not` modifier negates only the next matcher, then resets.\n */\nexport class KeyAssertion {\n #negated = false;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n private readonly present: boolean,\n ) {}\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n /** The raw value (resolved against the sentinel) used by matchers. */\n private get resolved(): unknown {\n return this.present ? this.value : MISSING;\n }\n\n /**\n * Record a result, applying the pending `.not` negation, then reset it.\n */\n private record(\n rule: string,\n rawPass: boolean,\n describe: (negated: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const ok = negated ? !rawPass : rawPass;\n const result: RuleResult = {\n key: this.key,\n rule,\n ok,\n negated,\n message: describe(negated),\n value: this.present ? this.value : undefined,\n expected,\n };\n this.recorder.push(result);\n return this;\n }\n\n /** Assert the key exists in the frontmatter. */\n required(): this {\n return this.record(\n \"required\",\n this.present,\n (neg) => (neg ? `should not exist` : `is required`),\n );\n }\n\n /** Alias of {@link required}. */\n exists(): this {\n return this.record(\n \"exists\",\n this.present,\n (neg) => (neg ? `should not exist` : `should exist`),\n );\n }\n\n /** Assert the value is of the given type. */\n type(expected: ValueType): this {\n const actual = valueType(this.resolved);\n return this.record(\n \"type\",\n actual === expected,\n (neg) =>\n neg\n ? `should not be of type ${expected}`\n : `should be of type ${expected} (was ${actual})`,\n expected,\n );\n }\n\n /** Assert the value strictly equals `expected` (deep for arrays/objects). */\n eq(expected: unknown): this {\n return this.record(\n \"eq\",\n deepEqual(this.resolved, expected),\n (neg) =>\n neg\n ? `should not equal ${display(expected)}`\n : `should equal ${display(expected)}`,\n expected,\n );\n }\n\n /**\n * Assert the value is an array whose every element is in `allowed` (enum).\n *\n * Designed for catching tag notation drift: define the canonical set once\n * and any stray / mistyped value is reported.\n *\n * ```ts\n * fm.key(\"tags\").subsetOf([\"ok\", \"release\", \"blog\"]);\n * ```\n */\n subsetOf(allowed: readonly unknown[]): this {\n const v = this.resolved;\n const isArray = Array.isArray(v);\n const invalid = isArray\n ? v.filter((el) => !allowed.some((a) => deepEqual(el, a)))\n : [];\n return this.record(\n \"subsetOf\",\n isArray && invalid.length === 0,\n (neg) => {\n if (!isArray) return `should be an array of values from ${display(allowed)}`;\n if (neg) return `should contain values outside ${display(allowed)}`;\n return invalid.length\n ? `all items should be one of ${display(allowed)} (invalid: ${display(invalid)})`\n : `all items should be one of ${display(allowed)}`;\n },\n allowed,\n );\n }\n\n /** Alias of {@link subsetOf}. */\n only(allowed: readonly unknown[]): this {\n return this.subsetOf(allowed);\n }\n\n /**\n * Apply matchers to every element of an array value.\n *\n * ```ts\n * fm.key(\"tags\").each.oneOf([\"ok\", \"release\"]);\n * fm.key(\"tags\").each.matches(/^[a-z0-9-]+$/);\n * ```\n */\n get each(): EachAssertion {\n const negated = this.#negated;\n this.#negated = false;\n return new EachAssertion(this.recorder, this.key, this.resolved, negated);\n }\n\n /** Assert the value is one of `allowed` (enum). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n allowed.some((a) => deepEqual(this.resolved, a)),\n (neg) =>\n neg\n ? `should not be one of ${display(allowed)}`\n : `should be one of ${display(allowed)}`,\n allowed,\n );\n }\n\n /** Assert a string value matches the given regular expression. */\n matches(pattern: RegExp): this {\n const v = this.resolved;\n const pass = typeof v === \"string\" && pattern.test(v);\n return this.record(\n \"matches\",\n pass,\n (neg) =>\n neg\n ? `should not match ${pattern}`\n : `should match ${pattern}`,\n pattern.source,\n );\n }\n\n /**\n * Assert the value is a calendar date written as `YYYY-MM-DD`.\n *\n * fluorite keeps unquoted YAML dates as strings (see {@link parseFrontmatter}),\n * so this checks the written form exactly: a full timestamp\n * (`2026-06-07 10:30:00`), a loosely-padded date (`2026-6-7`), or an\n * impossible date (`2026-02-30`) all fail.\n *\n * ```ts\n * fm.key(\"date\").required().isoDate();\n * ```\n */\n isoDate(): this {\n const v = this.resolved;\n const pass = typeof v === \"string\" && isCalendarDate(v);\n return this.record(\n \"isoDate\",\n pass,\n (neg) =>\n neg\n ? `should not be a YYYY-MM-DD date`\n : `should be a YYYY-MM-DD date (was ${\n typeof v === \"string\" ? display(v) : valueType(v)\n })`,\n );\n }\n\n /** Assert an array contains `item`, or a string contains the substring. */\n has(item: unknown): this {\n const v = this.resolved;\n let pass = false;\n if (Array.isArray(v)) pass = v.some((el) => deepEqual(el, item));\n else if (typeof v === \"string\" && typeof item === \"string\")\n pass = v.includes(item);\n return this.record(\n \"has\",\n pass,\n (neg) =>\n neg ? `should not have ${display(item)}` : `should have ${display(item)}`,\n item,\n );\n }\n\n /** Assert an array/string contains all of `items`. */\n hasAll(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.every((item) => contains(v, item));\n return this.record(\n \"hasAll\",\n pass,\n (neg) =>\n neg\n ? `should not have all of ${display(items)}`\n : `should have all of ${display(items)}`,\n items,\n );\n }\n\n /** Assert an array/string contains at least one of `items`. */\n hasAny(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.some((item) => contains(v, item));\n return this.record(\n \"hasAny\",\n pass,\n (neg) =>\n neg\n ? `should not have any of ${display(items)}`\n : `should have any of ${display(items)}`,\n items,\n );\n }\n\n /** Assert the string/array length equals `n`. */\n length(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"length\",\n len === n,\n (neg) =>\n neg\n ? `length should not be ${n} (was ${len ?? \"n/a\"})`\n : `length should be ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at least `n`. */\n lengthMin(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMin\",\n len !== undefined && len >= n,\n (neg) =>\n neg\n ? `length should be < ${n} (was ${len ?? \"n/a\"})`\n : `length should be >= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at most `n`. */\n lengthMax(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMax\",\n len !== undefined && len <= n,\n (neg) =>\n neg\n ? `length should be > ${n} (was ${len ?? \"n/a\"})`\n : `length should be <= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n}\n\n/**\n * Applies matchers to every element of an array frontmatter value.\n *\n * Obtained via {@link KeyAssertion.each}. Each terminal matcher records a\n * single {@link RuleResult}: it passes only when the value is an array and\n * every element satisfies the matcher; failures list the offending elements.\n */\nexport class EachAssertion {\n #negated: boolean;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n negated: boolean,\n ) {\n this.#negated = negated;\n }\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n private record(\n rule: string,\n perElement: (el: unknown) => boolean,\n describe: (negated: boolean, invalid: unknown[], isArray: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const isArray = Array.isArray(this.value);\n const invalid = isArray\n ? (this.value as unknown[]).filter((el) => !perElement(el))\n : [];\n const rawPass = isArray && invalid.length === 0;\n // A non-array can never satisfy a per-element check, even when negated.\n const ok = isArray ? (negated ? !rawPass : rawPass) : false;\n this.recorder.push({\n key: this.key,\n rule: `each.${rule}`,\n ok,\n negated,\n message: describe(negated, invalid, isArray),\n value: this.value === MISSING ? undefined : this.value,\n expected,\n });\n return this;\n }\n\n /** Every element must be one of `allowed` (enum over array contents). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n (el) => allowed.some((a) => deepEqual(el, a)),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of values from ${display(allowed)}`\n : neg\n ? `every item should be outside ${display(allowed)}`\n : `every item should be one of ${display(allowed)}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n allowed,\n );\n }\n\n /** Every element must be of the given type. */\n type(expected: ValueType): this {\n return this.record(\n \"type\",\n (el) => valueType(el) === expected,\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of ${expected}`\n : neg\n ? `every item should not be of type ${expected}`\n : `every item should be of type ${expected}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n expected,\n );\n }\n\n /** Every (string) element must match the pattern. */\n matches(pattern: RegExp): this {\n return this.record(\n \"matches\",\n (el) => typeof el === \"string\" && pattern.test(el),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of strings matching ${pattern}`\n : neg\n ? `every item should not match ${pattern}`\n : `every item should match ${pattern}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n pattern.source,\n );\n }\n\n /** Every element must be a `YYYY-MM-DD` calendar date string. */\n isoDate(): this {\n return this.record(\n \"isoDate\",\n (el) => typeof el === \"string\" && isCalendarDate(el),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of YYYY-MM-DD dates`\n : neg\n ? `every item should not be a YYYY-MM-DD date`\n : `every item should be a YYYY-MM-DD date${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n );\n }\n}\n\n/**\n * True only for a string that is a real calendar date in `YYYY-MM-DD` form.\n *\n * The four-then-two-then-two digit shape is required (so `2026-6-7` fails), and\n * the round-trip through {@link Date.UTC} rejects impossible dates such as\n * `2026-02-30` or `2026-13-01` (which would otherwise roll over to a valid\n * day in the next month/year).\n */\nfunction isCalendarDate(value: string): boolean {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value);\n if (!m) return false;\n const year = Number(m[1]);\n const month = Number(m[2]);\n const day = Number(m[3]);\n const dt = new Date(Date.UTC(year, month - 1, day));\n return (\n dt.getUTCFullYear() === year &&\n dt.getUTCMonth() === month - 1 &&\n dt.getUTCDate() === day\n );\n}\n\nfunction contains(container: unknown, item: unknown): boolean {\n if (Array.isArray(container)) return container.some((el) => deepEqual(el, item));\n if (typeof container === \"string\" && typeof item === \"string\")\n return container.includes(item);\n return false;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((el, i) => deepEqual(el, b[i]));\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n return ak.every((k) => deepEqual(ao[k], bo[k]));\n}\n","import { KeyAssertion } from \"./assertion.js\";\nimport type { RuleResult } from \"./types.js\";\n\n/**\n * The `fm` object passed to a rule set. Holds the parsed frontmatter data,\n * hands out {@link KeyAssertion} instances via {@link key}, and accumulates\n * every {@link RuleResult} the matchers record.\n */\nexport class Recorder {\n readonly results: RuleResult[] = [];\n\n constructor(readonly data: Record<string, unknown>) {}\n\n /** Begin a chain of assertions against the given frontmatter key. */\n key(name: string): KeyAssertion {\n const present = Object.prototype.hasOwnProperty.call(this.data, name);\n return new KeyAssertion(this, name, this.data[name], present);\n }\n\n /** Internal: append a recorded rule result. */\n push(result: RuleResult): void {\n this.results.push(result);\n }\n}\n","import { parseFrontmatter } from \"./parse.js\";\nimport { Recorder } from \"./recorder.js\";\nimport type { CheckResult, RulesFn, RuleResult } from \"./types.js\";\n\n/**\n * Run a rule set against the frontmatter of a Markdown source string.\n *\n * Never throws: malformed YAML or a missing frontmatter block is surfaced as a\n * failing {@link RuleResult} so results can always be collected and reported.\n *\n * ```ts\n * const result = check(markdown, (fm) => {\n * fm.key(\"title\").required().lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * });\n * if (!result.ok) console.error(result.failures);\n * ```\n */\nexport function check(source: string, rules: RulesFn): CheckResult {\n const parsed = parseFrontmatter(source);\n const recorder = new Recorder(parsed.data);\n\n if (parsed.error) {\n recorder.push(parseErrorResult(`invalid frontmatter: ${parsed.error}`));\n } else if (!parsed.hasFrontmatter) {\n recorder.push(parseErrorResult(\"no frontmatter block found\"));\n } else {\n rules(recorder);\n }\n\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return {\n ok: failures.length === 0,\n results,\n failures,\n data: parsed.data,\n };\n}\n\n/** Run a rule set against already-parsed frontmatter data. */\nexport function checkData(\n data: Record<string, unknown>,\n rules: RulesFn,\n): CheckResult {\n const recorder = new Recorder(data);\n rules(recorder);\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return { ok: failures.length === 0, results, failures, data };\n}\n\nfunction parseErrorResult(message: string): RuleResult {\n return {\n key: \"(frontmatter)\",\n rule: \"parse\",\n ok: false,\n negated: false,\n message,\n value: undefined,\n };\n}\n","import { pathToFileURL } from \"node:url\";\nimport { access } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { FluoriteConfig } from \"./types.js\";\n\n/**\n * Identity helper for authoring a config file with full type inference:\n *\n * ```ts\n * import { defineConfig } from \"fluorite\";\n * export default defineConfig({\n * include: [\"docs/**\\/*.md\"],\n * rules: (fm) => fm.key(\"title\").required(),\n * });\n * ```\n */\nexport function defineConfig(config: FluoriteConfig): FluoriteConfig {\n return config;\n}\n\nconst CONFIG_NAMES = [\n \"fluorite.config.js\",\n \"fluorite.config.mjs\",\n \"fluorite.config.cjs\",\n];\n\n/**\n * Locate a config file: an explicit path, or the first conventional name found\n * in `cwd`. Returns `undefined` when none is found.\n */\nexport async function resolveConfigPath(\n explicit: string | undefined,\n cwd = process.cwd(),\n): Promise<string | undefined> {\n if (explicit) return resolve(cwd, explicit);\n for (const name of CONFIG_NAMES) {\n const candidate = resolve(cwd, name);\n if (await fileExists(candidate)) return candidate;\n }\n return undefined;\n}\n\n/** Dynamically import a config file and return its default export. */\nexport async function loadConfig(path: string): Promise<FluoriteConfig> {\n const mod = await import(pathToFileURL(path).href);\n const config = (mod.default ?? mod) as FluoriteConfig;\n if (!config || typeof config.rules !== \"function\") {\n throw new Error(\n `Config at ${path} must export a default object with a \"rules\" function.`,\n );\n }\n return config;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\nimport type { CheckResult } from \"./types.js\";\n\n/** A check result paired with the file it came from. */\nexport interface FileReport {\n file: string;\n result: CheckResult;\n}\n\nexport interface FormatOptions {\n /** Suppress per-file lines for passing files. */\n quiet?: boolean;\n}\n\nfunction displayValue(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Format file reports into a human-readable, colorized string. */\nexport function formatReports(\n reports: FileReport[],\n options: FormatOptions = {},\n): string {\n const lines: string[] = [];\n let passed = 0;\n let failed = 0;\n let ruleFailures = 0;\n\n for (const { file, result } of reports) {\n if (result.ok) {\n passed++;\n if (!options.quiet) lines.push(`${pc.green(\"✔\")} ${file}`);\n } else {\n failed++;\n lines.push(`${pc.red(\"✘\")} ${file}`);\n for (const failure of result.failures) {\n ruleFailures++;\n const where = pc.dim(`(value: ${displayValue(failure.value)})`);\n lines.push(\n ` ${pc.red(\"✘\")} ${pc.bold(failure.key)}: ${failure.message} ${where}`,\n );\n }\n }\n }\n\n const summary = [\n `${reports.length} files`,\n pc.green(`${passed} passed`),\n failed > 0 ? pc.red(`${failed} failed`) : `${failed} failed`,\n `${ruleFailures} rule failures`,\n ].join(\", \");\n\n lines.push(\"\");\n lines.push(summary);\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["yaml","matter","pc"]}
package/dist/index.d.cts CHANGED
@@ -60,6 +60,19 @@ declare class KeyAssertion {
60
60
  oneOf(allowed: readonly unknown[]): this;
61
61
  /** Assert a string value matches the given regular expression. */
62
62
  matches(pattern: RegExp): this;
63
+ /**
64
+ * Assert the value is a calendar date written as `YYYY-MM-DD`.
65
+ *
66
+ * fluorite keeps unquoted YAML dates as strings (see {@link parseFrontmatter}),
67
+ * so this checks the written form exactly: a full timestamp
68
+ * (`2026-06-07 10:30:00`), a loosely-padded date (`2026-6-7`), or an
69
+ * impossible date (`2026-02-30`) all fail.
70
+ *
71
+ * ```ts
72
+ * fm.key("date").required().isoDate();
73
+ * ```
74
+ */
75
+ isoDate(): this;
63
76
  /** Assert an array contains `item`, or a string contains the substring. */
64
77
  has(item: unknown): this;
65
78
  /** Assert an array/string contains all of `items`. */
@@ -95,6 +108,8 @@ declare class EachAssertion {
95
108
  type(expected: ValueType): this;
96
109
  /** Every (string) element must match the pattern. */
97
110
  matches(pattern: RegExp): this;
111
+ /** Every element must be a `YYYY-MM-DD` calendar date string. */
112
+ isoDate(): this;
98
113
  }
99
114
 
100
115
  /**
@@ -113,7 +128,7 @@ declare class Recorder {
113
128
  }
114
129
 
115
130
  /** The set of value types understood by the `type()` matcher. */
116
- type ValueType = "string" | "number" | "boolean" | "array" | "object" | "null";
131
+ type ValueType = "string" | "number" | "boolean" | "array" | "object" | "null" | "date";
117
132
  /** Result of a single matcher invocation against one key. */
118
133
  interface RuleResult {
119
134
  /** The frontmatter key that was inspected, e.g. `"tags"`. */
package/dist/index.d.ts CHANGED
@@ -60,6 +60,19 @@ declare class KeyAssertion {
60
60
  oneOf(allowed: readonly unknown[]): this;
61
61
  /** Assert a string value matches the given regular expression. */
62
62
  matches(pattern: RegExp): this;
63
+ /**
64
+ * Assert the value is a calendar date written as `YYYY-MM-DD`.
65
+ *
66
+ * fluorite keeps unquoted YAML dates as strings (see {@link parseFrontmatter}),
67
+ * so this checks the written form exactly: a full timestamp
68
+ * (`2026-06-07 10:30:00`), a loosely-padded date (`2026-6-7`), or an
69
+ * impossible date (`2026-02-30`) all fail.
70
+ *
71
+ * ```ts
72
+ * fm.key("date").required().isoDate();
73
+ * ```
74
+ */
75
+ isoDate(): this;
63
76
  /** Assert an array contains `item`, or a string contains the substring. */
64
77
  has(item: unknown): this;
65
78
  /** Assert an array/string contains all of `items`. */
@@ -95,6 +108,8 @@ declare class EachAssertion {
95
108
  type(expected: ValueType): this;
96
109
  /** Every (string) element must match the pattern. */
97
110
  matches(pattern: RegExp): this;
111
+ /** Every element must be a `YYYY-MM-DD` calendar date string. */
112
+ isoDate(): this;
98
113
  }
99
114
 
100
115
  /**
@@ -113,7 +128,7 @@ declare class Recorder {
113
128
  }
114
129
 
115
130
  /** The set of value types understood by the `type()` matcher. */
116
- type ValueType = "string" | "number" | "boolean" | "array" | "object" | "null";
131
+ type ValueType = "string" | "number" | "boolean" | "array" | "object" | "null" | "date";
117
132
  /** Result of a single matcher invocation against one key. */
118
133
  interface RuleResult {
119
134
  /** The frontmatter key that was inspected, e.g. `"tags"`. */
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  parseFrontmatter,
11
11
  resolveConfigPath
12
- } from "./chunk-2QW4LSG5.js";
12
+ } from "./chunk-PTAYLHQM.js";
13
13
  export {
14
14
  EachAssertion,
15
15
  KeyAssertion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yukyu30/fluorite",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Inspect and validate Markdown frontmatter with a readable, chainable DSL — usable as a library and a CLI.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@
18
18
  "cli"
19
19
  ],
20
20
  "bin": {
21
- "fluorite": "./dist/cli.js"
21
+ "fluorite": "dist/cli.js"
22
22
  },
23
23
  "main": "./dist/index.cjs",
24
24
  "module": "./dist/index.js",
@@ -43,16 +43,20 @@
43
43
  "build": "tsup",
44
44
  "test": "vitest run",
45
45
  "test:watch": "vitest",
46
+ "coverage": "vitest run --coverage",
46
47
  "typecheck": "tsc --noEmit",
47
48
  "prepublishOnly": "npm run build"
48
49
  },
49
50
  "dependencies": {
50
51
  "gray-matter": "^4.0.3",
52
+ "js-yaml": "^3.14.2",
51
53
  "picocolors": "^1.1.1",
52
54
  "tinyglobby": "^0.2.10"
53
55
  },
54
56
  "devDependencies": {
57
+ "@types/js-yaml": "^3.12.10",
55
58
  "@types/node": "^22.10.0",
59
+ "@vitest/coverage-v8": "^2.1.9",
56
60
  "tsup": "^8.3.5",
57
61
  "typescript": "^5.7.2",
58
62
  "vitest": "^2.1.8"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/parse.ts","../src/assertion.ts","../src/recorder.ts","../src/check.ts","../src/config.ts","../src/report.ts"],"sourcesContent":["import matter from \"gray-matter\";\n\n/** Outcome of parsing frontmatter out of a Markdown source string. */\nexport interface ParseResult {\n /** Parsed frontmatter data (empty object when none / on error). */\n data: Record<string, unknown>;\n /** The Markdown body following the frontmatter block. */\n content: string;\n /** Whether a frontmatter block was present at all. */\n hasFrontmatter: boolean;\n /** Parse error message, if the frontmatter (YAML) was malformed. */\n error?: string;\n}\n\n/**\n * Extract frontmatter from a Markdown source string.\n *\n * Never throws: malformed YAML is reported via the `error` field so callers\n * can record it as a failure rather than crash.\n */\nexport function parseFrontmatter(source: string): ParseResult {\n const hasFrontmatter = /^?\\s*---\\r?\\n/.test(source);\n try {\n const parsed = matter(source);\n const data = (parsed.data ?? {}) as Record<string, unknown>;\n return {\n data,\n content: parsed.content,\n hasFrontmatter,\n };\n } catch (err) {\n return {\n data: {},\n content: source,\n hasFrontmatter,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { Recorder } from \"./recorder.js\";\nimport type { RuleResult, ValueType } from \"./types.js\";\n\n/** Sentinel meaning \"the key was not present in the frontmatter\". */\nconst MISSING = Symbol(\"missing\");\n\nfunction valueType(value: unknown): ValueType | \"undefined\" {\n if (value === MISSING) return \"undefined\";\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n const t = typeof value;\n if (t === \"string\" || t === \"number\" || t === \"boolean\" || t === \"object\") {\n return t;\n }\n return \"undefined\";\n}\n\nfunction lengthOf(value: unknown): number | undefined {\n if (typeof value === \"string\" || Array.isArray(value)) return value.length;\n return undefined;\n}\n\nfunction display(value: unknown): string {\n if (value === MISSING) return \"undefined\";\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Fluent, chainable assertions for a single frontmatter key.\n *\n * Each terminal matcher records one {@link RuleResult} on the parent\n * {@link Recorder} and returns `this`, so matchers can be chained:\n *\n * ```ts\n * fm.key(\"title\").required().type(\"string\").lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * ```\n *\n * The `.not` modifier negates only the next matcher, then resets.\n */\nexport class KeyAssertion {\n #negated = false;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n private readonly present: boolean,\n ) {}\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n /** The raw value (resolved against the sentinel) used by matchers. */\n private get resolved(): unknown {\n return this.present ? this.value : MISSING;\n }\n\n /**\n * Record a result, applying the pending `.not` negation, then reset it.\n */\n private record(\n rule: string,\n rawPass: boolean,\n describe: (negated: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const ok = negated ? !rawPass : rawPass;\n const result: RuleResult = {\n key: this.key,\n rule,\n ok,\n negated,\n message: describe(negated),\n value: this.present ? this.value : undefined,\n expected,\n };\n this.recorder.push(result);\n return this;\n }\n\n /** Assert the key exists in the frontmatter. */\n required(): this {\n return this.record(\n \"required\",\n this.present,\n (neg) => (neg ? `should not exist` : `is required`),\n );\n }\n\n /** Alias of {@link required}. */\n exists(): this {\n return this.record(\n \"exists\",\n this.present,\n (neg) => (neg ? `should not exist` : `should exist`),\n );\n }\n\n /** Assert the value is of the given type. */\n type(expected: ValueType): this {\n const actual = valueType(this.resolved);\n return this.record(\n \"type\",\n actual === expected,\n (neg) =>\n neg\n ? `should not be of type ${expected}`\n : `should be of type ${expected} (was ${actual})`,\n expected,\n );\n }\n\n /** Assert the value strictly equals `expected` (deep for arrays/objects). */\n eq(expected: unknown): this {\n return this.record(\n \"eq\",\n deepEqual(this.resolved, expected),\n (neg) =>\n neg\n ? `should not equal ${display(expected)}`\n : `should equal ${display(expected)}`,\n expected,\n );\n }\n\n /**\n * Assert the value is an array whose every element is in `allowed` (enum).\n *\n * Designed for catching tag notation drift: define the canonical set once\n * and any stray / mistyped value is reported.\n *\n * ```ts\n * fm.key(\"tags\").subsetOf([\"ok\", \"release\", \"blog\"]);\n * ```\n */\n subsetOf(allowed: readonly unknown[]): this {\n const v = this.resolved;\n const isArray = Array.isArray(v);\n const invalid = isArray\n ? v.filter((el) => !allowed.some((a) => deepEqual(el, a)))\n : [];\n return this.record(\n \"subsetOf\",\n isArray && invalid.length === 0,\n (neg) => {\n if (!isArray) return `should be an array of values from ${display(allowed)}`;\n if (neg) return `should contain values outside ${display(allowed)}`;\n return invalid.length\n ? `all items should be one of ${display(allowed)} (invalid: ${display(invalid)})`\n : `all items should be one of ${display(allowed)}`;\n },\n allowed,\n );\n }\n\n /** Alias of {@link subsetOf}. */\n only(allowed: readonly unknown[]): this {\n return this.subsetOf(allowed);\n }\n\n /**\n * Apply matchers to every element of an array value.\n *\n * ```ts\n * fm.key(\"tags\").each.oneOf([\"ok\", \"release\"]);\n * fm.key(\"tags\").each.matches(/^[a-z0-9-]+$/);\n * ```\n */\n get each(): EachAssertion {\n const negated = this.#negated;\n this.#negated = false;\n return new EachAssertion(this.recorder, this.key, this.resolved, negated);\n }\n\n /** Assert the value is one of `allowed` (enum). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n allowed.some((a) => deepEqual(this.resolved, a)),\n (neg) =>\n neg\n ? `should not be one of ${display(allowed)}`\n : `should be one of ${display(allowed)}`,\n allowed,\n );\n }\n\n /** Assert a string value matches the given regular expression. */\n matches(pattern: RegExp): this {\n const v = this.resolved;\n const pass = typeof v === \"string\" && pattern.test(v);\n return this.record(\n \"matches\",\n pass,\n (neg) =>\n neg\n ? `should not match ${pattern}`\n : `should match ${pattern}`,\n pattern.source,\n );\n }\n\n /** Assert an array contains `item`, or a string contains the substring. */\n has(item: unknown): this {\n const v = this.resolved;\n let pass = false;\n if (Array.isArray(v)) pass = v.some((el) => deepEqual(el, item));\n else if (typeof v === \"string\" && typeof item === \"string\")\n pass = v.includes(item);\n return this.record(\n \"has\",\n pass,\n (neg) =>\n neg ? `should not have ${display(item)}` : `should have ${display(item)}`,\n item,\n );\n }\n\n /** Assert an array/string contains all of `items`. */\n hasAll(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.every((item) => contains(v, item));\n return this.record(\n \"hasAll\",\n pass,\n (neg) =>\n neg\n ? `should not have all of ${display(items)}`\n : `should have all of ${display(items)}`,\n items,\n );\n }\n\n /** Assert an array/string contains at least one of `items`. */\n hasAny(items: readonly unknown[]): this {\n const v = this.resolved;\n const pass = items.some((item) => contains(v, item));\n return this.record(\n \"hasAny\",\n pass,\n (neg) =>\n neg\n ? `should not have any of ${display(items)}`\n : `should have any of ${display(items)}`,\n items,\n );\n }\n\n /** Assert the string/array length equals `n`. */\n length(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"length\",\n len === n,\n (neg) =>\n neg\n ? `length should not be ${n} (was ${len ?? \"n/a\"})`\n : `length should be ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at least `n`. */\n lengthMin(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMin\",\n len !== undefined && len >= n,\n (neg) =>\n neg\n ? `length should be < ${n} (was ${len ?? \"n/a\"})`\n : `length should be >= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n\n /** Assert the string/array length is at most `n`. */\n lengthMax(n: number): this {\n const len = lengthOf(this.resolved);\n return this.record(\n \"lengthMax\",\n len !== undefined && len <= n,\n (neg) =>\n neg\n ? `length should be > ${n} (was ${len ?? \"n/a\"})`\n : `length should be <= ${n} (was ${len ?? \"n/a\"})`,\n n,\n );\n }\n}\n\n/**\n * Applies matchers to every element of an array frontmatter value.\n *\n * Obtained via {@link KeyAssertion.each}. Each terminal matcher records a\n * single {@link RuleResult}: it passes only when the value is an array and\n * every element satisfies the matcher; failures list the offending elements.\n */\nexport class EachAssertion {\n #negated: boolean;\n\n constructor(\n private readonly recorder: Recorder,\n private readonly key: string,\n private readonly value: unknown,\n negated: boolean,\n ) {\n this.#negated = negated;\n }\n\n /** Negate the next matcher in the chain. */\n get not(): this {\n this.#negated = true;\n return this;\n }\n\n private record(\n rule: string,\n perElement: (el: unknown) => boolean,\n describe: (negated: boolean, invalid: unknown[], isArray: boolean) => string,\n expected?: unknown,\n ): this {\n const negated = this.#negated;\n this.#negated = false;\n const isArray = Array.isArray(this.value);\n const invalid = isArray\n ? (this.value as unknown[]).filter((el) => !perElement(el))\n : [];\n const rawPass = isArray && invalid.length === 0;\n // A non-array can never satisfy a per-element check, even when negated.\n const ok = isArray ? (negated ? !rawPass : rawPass) : false;\n this.recorder.push({\n key: this.key,\n rule: `each.${rule}`,\n ok,\n negated,\n message: describe(negated, invalid, isArray),\n value: this.value === MISSING ? undefined : this.value,\n expected,\n });\n return this;\n }\n\n /** Every element must be one of `allowed` (enum over array contents). */\n oneOf(allowed: readonly unknown[]): this {\n return this.record(\n \"oneOf\",\n (el) => allowed.some((a) => deepEqual(el, a)),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of values from ${display(allowed)}`\n : neg\n ? `every item should be outside ${display(allowed)}`\n : `every item should be one of ${display(allowed)}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n allowed,\n );\n }\n\n /** Every element must be of the given type. */\n type(expected: ValueType): this {\n return this.record(\n \"type\",\n (el) => valueType(el) === expected,\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of ${expected}`\n : neg\n ? `every item should not be of type ${expected}`\n : `every item should be of type ${expected}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n expected,\n );\n }\n\n /** Every (string) element must match the pattern. */\n matches(pattern: RegExp): this {\n return this.record(\n \"matches\",\n (el) => typeof el === \"string\" && pattern.test(el),\n (neg, invalid, isArray) =>\n !isArray\n ? `should be an array of strings matching ${pattern}`\n : neg\n ? `every item should not match ${pattern}`\n : `every item should match ${pattern}${invalid.length ? ` (invalid: ${display(invalid)})` : \"\"}`,\n pattern.source,\n );\n }\n}\n\nfunction contains(container: unknown, item: unknown): boolean {\n if (Array.isArray(container)) return container.some((el) => deepEqual(el, item));\n if (typeof container === \"string\" && typeof item === \"string\")\n return container.includes(item);\n return false;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((el, i) => deepEqual(el, b[i]));\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n return ak.every((k) => deepEqual(ao[k], bo[k]));\n}\n","import { KeyAssertion } from \"./assertion.js\";\nimport type { RuleResult } from \"./types.js\";\n\n/**\n * The `fm` object passed to a rule set. Holds the parsed frontmatter data,\n * hands out {@link KeyAssertion} instances via {@link key}, and accumulates\n * every {@link RuleResult} the matchers record.\n */\nexport class Recorder {\n readonly results: RuleResult[] = [];\n\n constructor(readonly data: Record<string, unknown>) {}\n\n /** Begin a chain of assertions against the given frontmatter key. */\n key(name: string): KeyAssertion {\n const present = Object.prototype.hasOwnProperty.call(this.data, name);\n return new KeyAssertion(this, name, this.data[name], present);\n }\n\n /** Internal: append a recorded rule result. */\n push(result: RuleResult): void {\n this.results.push(result);\n }\n}\n","import { parseFrontmatter } from \"./parse.js\";\nimport { Recorder } from \"./recorder.js\";\nimport type { CheckResult, RulesFn, RuleResult } from \"./types.js\";\n\n/**\n * Run a rule set against the frontmatter of a Markdown source string.\n *\n * Never throws: malformed YAML or a missing frontmatter block is surfaced as a\n * failing {@link RuleResult} so results can always be collected and reported.\n *\n * ```ts\n * const result = check(markdown, (fm) => {\n * fm.key(\"title\").required().lengthMin(10);\n * fm.key(\"tags\").not.has(\"ng\");\n * });\n * if (!result.ok) console.error(result.failures);\n * ```\n */\nexport function check(source: string, rules: RulesFn): CheckResult {\n const parsed = parseFrontmatter(source);\n const recorder = new Recorder(parsed.data);\n\n if (parsed.error) {\n recorder.push(parseErrorResult(`invalid frontmatter: ${parsed.error}`));\n } else if (!parsed.hasFrontmatter) {\n recorder.push(parseErrorResult(\"no frontmatter block found\"));\n } else {\n rules(recorder);\n }\n\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return {\n ok: failures.length === 0,\n results,\n failures,\n data: parsed.data,\n };\n}\n\n/** Run a rule set against already-parsed frontmatter data. */\nexport function checkData(\n data: Record<string, unknown>,\n rules: RulesFn,\n): CheckResult {\n const recorder = new Recorder(data);\n rules(recorder);\n const results = recorder.results;\n const failures = results.filter((r) => !r.ok);\n return { ok: failures.length === 0, results, failures, data };\n}\n\nfunction parseErrorResult(message: string): RuleResult {\n return {\n key: \"(frontmatter)\",\n rule: \"parse\",\n ok: false,\n negated: false,\n message,\n value: undefined,\n };\n}\n","import { pathToFileURL } from \"node:url\";\nimport { access } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { FluoriteConfig } from \"./types.js\";\n\n/**\n * Identity helper for authoring a config file with full type inference:\n *\n * ```ts\n * import { defineConfig } from \"fluorite\";\n * export default defineConfig({\n * include: [\"docs/**\\/*.md\"],\n * rules: (fm) => fm.key(\"title\").required(),\n * });\n * ```\n */\nexport function defineConfig(config: FluoriteConfig): FluoriteConfig {\n return config;\n}\n\nconst CONFIG_NAMES = [\n \"fluorite.config.js\",\n \"fluorite.config.mjs\",\n \"fluorite.config.cjs\",\n];\n\n/**\n * Locate a config file: an explicit path, or the first conventional name found\n * in `cwd`. Returns `undefined` when none is found.\n */\nexport async function resolveConfigPath(\n explicit: string | undefined,\n cwd = process.cwd(),\n): Promise<string | undefined> {\n if (explicit) return resolve(cwd, explicit);\n for (const name of CONFIG_NAMES) {\n const candidate = resolve(cwd, name);\n if (await fileExists(candidate)) return candidate;\n }\n return undefined;\n}\n\n/** Dynamically import a config file and return its default export. */\nexport async function loadConfig(path: string): Promise<FluoriteConfig> {\n const mod = await import(pathToFileURL(path).href);\n const config = (mod.default ?? mod) as FluoriteConfig;\n if (!config || typeof config.rules !== \"function\") {\n throw new Error(\n `Config at ${path} must export a default object with a \"rules\" function.`,\n );\n }\n return config;\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n","import pc from \"picocolors\";\nimport type { CheckResult } from \"./types.js\";\n\n/** A check result paired with the file it came from. */\nexport interface FileReport {\n file: string;\n result: CheckResult;\n}\n\nexport interface FormatOptions {\n /** Suppress per-file lines for passing files. */\n quiet?: boolean;\n}\n\nfunction displayValue(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Format file reports into a human-readable, colorized string. */\nexport function formatReports(\n reports: FileReport[],\n options: FormatOptions = {},\n): string {\n const lines: string[] = [];\n let passed = 0;\n let failed = 0;\n let ruleFailures = 0;\n\n for (const { file, result } of reports) {\n if (result.ok) {\n passed++;\n if (!options.quiet) lines.push(`${pc.green(\"✔\")} ${file}`);\n } else {\n failed++;\n lines.push(`${pc.red(\"✘\")} ${file}`);\n for (const failure of result.failures) {\n ruleFailures++;\n const where = pc.dim(`(value: ${displayValue(failure.value)})`);\n lines.push(\n ` ${pc.red(\"✘\")} ${pc.bold(failure.key)}: ${failure.message} ${where}`,\n );\n }\n }\n }\n\n const summary = [\n `${reports.length} files`,\n pc.green(`${passed} passed`),\n failed > 0 ? pc.red(`${failed} failed`) : `${failed} failed`,\n `${ruleFailures} rule failures`,\n ].join(\", \");\n\n lines.push(\"\");\n lines.push(summary);\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}