From a1781f034ebd1fc135624bac8ed3b13d99656353 Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sun, 19 Apr 2026 13:25:27 +0800 Subject: [PATCH] fix(exporters): quote YAML frontmatter titles starting with reserved indicators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `escapeYaml` only triggered on `:`, `#`, `"`, `'`, or newline, so titles beginning with YAML 1.2 reserved indicator chars (`-`, `?`, `[`, `{`, `>`, `|`, `!`, etc.) emitted unquoted scalars that strict parsers treat as sequences/flow nodes — corrupting the frontmatter for any artifact whose `` or `<h1>` starts with such a character. Also quote values with leading/trailing whitespace, which similarly fail to round-trip through plain scalars. Signed-off-by: hqhq1025 <1506751656@qq.com> --- packages/exporters/src/markdown.test.ts | 13 +++++++++++++ packages/exporters/src/markdown.ts | 14 +++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/exporters/src/markdown.test.ts b/packages/exporters/src/markdown.test.ts index bf3080e43..cd2b1d523 100644 --- a/packages/exporters/src/markdown.test.ts +++ b/packages/exporters/src/markdown.test.ts @@ -79,6 +79,19 @@ describe('htmlToMarkdown', () => { expect(out.length).toBeGreaterThan(0); }); + it('quotes titles starting with YAML indicator chars so frontmatter stays valid', () => { + const cases = ['- TODO List', '? maybe', '[bracket', '{brace', '> quote', '| pipe', '! bang']; + for (const title of cases) { + const out = htmlToMarkdown('<p>x</p>', { title, schemaVersion: 1 }); + expect(out).toContain(`title: ${JSON.stringify(title)}\n`); + } + }); + + it('quotes titles with leading or trailing whitespace', () => { + const out = htmlToMarkdown('<p>x</p>', { title: ' padded ', schemaVersion: 1 }); + expect(out).toContain('title: " padded "\n'); + }); + it('strips javascript: links but keeps the visible text', () => { const out = htmlToMarkdown('<p><a href="javascript:alert(1)">x</a></p>', META); expect(out).toContain('x'); diff --git a/packages/exporters/src/markdown.ts b/packages/exporters/src/markdown.ts index 43d4edd94..eeee5efac 100644 --- a/packages/exporters/src/markdown.ts +++ b/packages/exporters/src/markdown.ts @@ -47,8 +47,20 @@ function renderFrontmatter(meta: MarkdownMeta): string { return `${lines.join('\n')}\n`; } +// YAML 1.2 reserved indicator chars that, at the start of a plain scalar, +// change parsing (sequence/flow/anchor/alias/directive/etc). Strings with +// leading/trailing whitespace also need quoting to round-trip correctly. +const YAML_LEADING_INDICATOR = /^[-?:,[\]{}#&*!|>'"%@`]/; +const YAML_NEEDS_QUOTING = /[:#"'\n]/; + function escapeYaml(value: string): string { - if (/[:#"'\n]/.test(value)) return JSON.stringify(value); + if ( + YAML_NEEDS_QUOTING.test(value) || + YAML_LEADING_INDICATOR.test(value) || + value !== value.trim() + ) { + return JSON.stringify(value); + } return value; }