From f4cfe2f650c56fd9cf15ffefb827e5bd17d60ca3 Mon Sep 17 00:00:00 2001 From: Jonathan Doughty Date: Sun, 31 May 2026 12:37:29 -0400 Subject: [PATCH] fix(config): merge markdown hooks when extending config --- __tests__/unit/node/config.test.ts | 42 ++++++++++++++++++++++++++++++ src/node/config.ts | 33 +++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 __tests__/unit/node/config.test.ts diff --git a/__tests__/unit/node/config.test.ts b/__tests__/unit/node/config.test.ts new file mode 100644 index 00000000..8e0849bd --- /dev/null +++ b/__tests__/unit/node/config.test.ts @@ -0,0 +1,42 @@ +import { mergeConfig } from 'node/config' + +describe('node/config', () => { + test('composes markdown hooks when merging configs', async () => { + const calls: string[] = [] + const md = {} as any + + const merged = mergeConfig( + { + markdown: { + preConfig: () => { + calls.push('base-pre') + }, + config: async () => { + calls.push('base') + }, + externalLinks: { rel: 'noopener' } + } + }, + { + markdown: { + preConfig: async () => { + calls.push('child-pre') + }, + config: () => { + calls.push('child') + }, + externalLinks: { target: '_blank' } + } + } + ) + + await merged.markdown?.preConfig?.(md) + await merged.markdown?.config?.(md) + + expect(calls).toEqual(['base-pre', 'child-pre', 'base', 'child']) + expect(merged.markdown?.externalLinks).toEqual({ + rel: 'noopener', + target: '_blank' + }) + }) +}) diff --git a/src/node/config.ts b/src/node/config.ts index bdcd00e1..cef2fdbd 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -289,9 +289,13 @@ async function resolveConfigExtends( } export function mergeConfig(a: UserConfig, b: UserConfig, isRoot = true) { + return mergeObjectConfig(a, b, isRoot) +} + +function mergeObjectConfig(a: T, b: T, isRoot = true): T { const merged: Record = { ...a } for (const key in b) { - const value = b[key as keyof UserConfig] + const value = b[key as keyof T] as any if (value == null) { continue } @@ -303,16 +307,41 @@ export function mergeConfig(a: UserConfig, b: UserConfig, isRoot = true) { if (isObject(existing) && isObject(value)) { if (isRoot && key === 'vite') { merged[key] = mergeViteConfig(existing, value) + } else if (key === 'markdown') { + merged[key] = mergeMarkdownConfig(existing, value) } else { - merged[key] = mergeConfig(existing, value, false) + merged[key] = mergeObjectConfig(existing, value, false) } continue } merged[key] = value } + return merged as T +} + +function mergeMarkdownConfig( + a: NonNullable, + b: NonNullable +) { + const merged = mergeObjectConfig(a, b, false) + merged.preConfig = mergeConfigHooks(a.preConfig, b.preConfig) + merged.config = mergeConfigHooks(a.config, b.config) return merged } +function mergeConfigHooks Awaitable>( + a: T | undefined, + b: T | undefined +): T | undefined { + if (!a) return b + if (!b) return a + + return (async (...args: Parameters) => { + await a(...args) + await b(...args) + }) as unknown as T +} + export async function resolveSiteData( root: string, userConfig?: UserConfig,