← Files DefuddleARCHIVED FILE

skills/defuddle-web-content/references/source/src/frontmatter.ts

2.17 KB · Oct 2, 2026 · 00:35 UTC

↓ Download file

import { countWords } from './utils';
import type { DefuddleResponse } from './types';

// Count words, treating each CJK character as one word (mirrors countWords),
// and truncate the text once maxWords is exceeded.
function truncateWords(text: string, maxWords: number): string {
	let words = 0;
	let inWord = false;

	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
		const isCJK = (
			(code >= 0x3040 && code <= 0x309f) ||
			(code >= 0x30a0 && code <= 0x30ff) ||
			(code >= 0x3400 && code <= 0x4dbf) ||
			(code >= 0x4e00 && code <= 0x9fff) ||
			(code >= 0xf900 && code <= 0xfaff) ||
			(code >= 0xac00 && code <= 0xd7af)
		);

		if (isCJK) {
			words++;
			inWord = false;
		} else if (code <= 32) {
			inWord = false;
		} else if (!inWord) {
			words++;
			inWord = true;
		}

		if (words > maxWords) {
			return text.slice(0, i).trimEnd() + '…';
		}
	}
	return text;
}

/**
 * Build a YAML frontmatter block ("---\n…\n---\n\n") from extracted metadata.
 * Shared by the CLI (`--frontmatter`) and the defuddle.md worker so both emit
 * identical output. `source:` is only emitted when a sourceUrl is provided
 * (CLI stdin/file input has no URL).
 */
export function buildFrontmatter(result: DefuddleResponse, sourceUrl?: string): string {
	const lines: string[] = ['---'];

	// Escape a string for use as a YAML double-quoted value
	const esc = (s: string) => s.replace(/"/g, '\\"').replace(/\n/g, ' ');

	if (result.title) lines.push(`title: "${esc(result.title)}"`);
	if (result.author) lines.push(`author: "${esc(result.author)}"`);
	if (result.site) lines.push(`site: "${esc(result.site)}"`);
	if (result.published) lines.push(`published: ${result.published}`);
	if (sourceUrl) lines.push(`source: "${sourceUrl}"`);
	if (result.domain) lines.push(`domain: "${result.domain}"`);
	if (result.language) lines.push(`language: "${result.language}"`);
	if (result.description) {
		const desc = countWords(result.description) > 300
			? truncateWords(result.description, 300)
			: result.description;
		lines.push(`description: "${esc(desc)}"`);
	}
	if (result.wordCount) lines.push(`word_count: ${result.wordCount}`);

	lines.push('---');
	return lines.join('\n') + '\n\n';
}

SHA-256: d6e9a7223190b70227bb701b5dd42fb1a62b5e329c4c1c356f5a63385b343c3f