From d8611339fdcbd3b528362084176c07ccea715ba2 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Tue, 24 Feb 2026 22:36:04 +0900 Subject: [PATCH 1/8] Extract transformWithSeonbi() from formatPostContent() Separate Seonbi API call logic (fetch, request body, response parsing, error handling) into a dedicated function for better testability and reusability. Co-Authored-By: Claude Opus 4.6 --- src/text.ts | 96 ++++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 46 deletions(-) diff --git a/src/text.ts b/src/text.ts index da784624..d195ac04 100644 --- a/src/text.ts +++ b/src/text.ts @@ -265,6 +265,55 @@ export function extractText(html: string | null): string | null { // biome-ignore lint/complexity/useLiteralKeys: tsc claims about this const SEONBI_URL = process.env["SEONBI_URL"]; +async function transformWithSeonbi(html: string): Promise { + const response = await fetch(SEONBI_URL!, { + method: "POST", + body: JSON.stringify({ + content: html, + contentType: "text/html", + quote: "HorizontalCornerBrackets", + cite: "AngleQuotes", + arrow: { + bidirArrow: true, + doubleArrow: true, + }, + ellipsis: true, + emDash: true, + stop: "Horizontal", + hanja: { + rendering: "HanjaInRuby", + reading: { + initialSoundLaw: true, + useDictionaries: ["kr-stdict"], + dictionary: {}, + }, + }, + }), + }); + try { + const seonbiResult = await response.json(); + if (seonbiResult.success) { + if ( + Array.isArray(seonbiResult.warnings) && + seonbiResult.warnings.length > 0 + ) { + logger.warn("Seonbi warnings: {warnings}", { + warnings: seonbiResult.warnings, + }); + } + return seonbiResult.content; + } + logger.error("Seonbi failed to format post content: {message}", { + message: seonbiResult.message, + }); + } catch (error) { + logger.error("Failed to format post content with Seonbi: {error}", { + error, + }); + } + return html; +} + export async function formatPostContent( db: PgDatabase< PostgresJsQueryResultHKT, @@ -284,52 +333,7 @@ export async function formatPostContent( SEONBI_URL != null && (language === "ko" || language?.startsWith("ko-")) ) { - const response = await fetch(SEONBI_URL, { - method: "POST", - body: JSON.stringify({ - content: result.html, - contentType: "text/html", - quote: "HorizontalCornerBrackets", - cite: "AngleQuotes", - arrow: { - bidirArrow: true, - doubleArrow: true, - }, - ellipsis: true, - emDash: true, - stop: "Horizontal", - hanja: { - rendering: "HanjaInRuby", - reading: { - initialSoundLaw: true, - useDictionaries: ["kr-stdict"], - dictionary: {}, - }, - }, - }), - }); - try { - const seonbiResult = await response.json(); - if (seonbiResult.success) { - result.html = seonbiResult.content; - if ( - Array.isArray(seonbiResult.warnings) && - seonbiResult.warnings.length > 0 - ) { - logger.warn("Seonbi warnings: {warnings}", { - warnings: seonbiResult.warnings, - }); - } - } else { - logger.error("Seonbi failed to format post content: {message}", { - message: seonbiResult.message, - }); - } - } catch (error) { - logger.error("Failed to format post content with Seonbi: {error}", { - error, - }); - } + result.html = await transformWithSeonbi(result.html); } return result; } From 870f328e5b725c7af975c1046542d0c16f942b7f Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 17:07:58 +0900 Subject: [PATCH 2/8] Add @seonbi/node native binding support for Korean text formatting Allow using SEONBI_NATIVE=true to perform Seonbi transformations locally via napi-rs bindings instead of requiring an external HTTP API (SEONBI_URL). Falls back to SEONBI_URL if native is unavailable. Co-Authored-By: Claude Opus 4.6 --- package.json | 3 +++ pnpm-lock.yaml | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/text.ts | 59 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 122 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 971cc82c..f2f4136a 100644 --- a/package.json +++ b/package.json @@ -86,5 +86,8 @@ "typescript": "^5.9.2", "vitest": "^3.1.4" }, + "optionalDependencies": { + "@seonbi/node": "0.2.1-alpha.0" + }, "packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 269dd9df..c45e2f52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,10 @@ importers: zod: specifier: ^4.3.5 version: 4.3.5 + optionalDependencies: + '@seonbi/node': + specifier: 0.2.1-alpha.0 + version: 0.2.1-alpha.0 devDependencies: '@biomejs/biome': specifier: ^2.4.4 @@ -2191,6 +2195,39 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 + '@seonbi/node-darwin-arm64@0.2.1-alpha.0': + resolution: {integrity: sha512-oE0JmApaEk0wvucxjokOY1QbE046t34AsJpDlS+0yv4cc0RO0MtnXsfD+e8OhGATnEH27tk28ZLvwfMuyqbk7A==} + cpu: [arm64] + os: [darwin] + + '@seonbi/node-darwin-x64@0.2.1-alpha.0': + resolution: {integrity: sha512-kFrnHXQze8q58u7KvsXidzES2P8VbsE/TVVhP21kcJqdXu1Oloo+tZAbvVJRxVU02otR1hXU4rayf+pLVnivvw==} + cpu: [x64] + os: [darwin] + + '@seonbi/node-linux-arm64-gnu@0.2.1-alpha.0': + resolution: {integrity: sha512-pgF9/8/oj6r6R7TJjzvevjPNY+TmpwoNBr0dPx19nAEQZFG44FgGvo373HEw72jh2V5qPiarLCSxNnrV8Q/pJw==} + cpu: [arm64] + os: [linux] + + '@seonbi/node-linux-x64-gnu@0.2.1-alpha.0': + resolution: {integrity: sha512-mN79CWZzcrT8Rf2/G8rCROz+rUZMhdcYVa+1UAuWFKPxXreGt+hizFrH+5azpJvua60hY20T7AimZwI+vNFBkg==} + cpu: [x64] + os: [linux] + + '@seonbi/node-linux-x64-musl@0.2.1-alpha.0': + resolution: {integrity: sha512-LnPr0l6kF2lBeBgu5ofGFyym7joFdoK12HKXYSnlGO+TEuetyWNhPX5O/2kZLWArF5wKQgPwUjSuza/kw8lcQg==} + cpu: [x64] + os: [linux] + + '@seonbi/node-win32-x64-msvc@0.2.1-alpha.0': + resolution: {integrity: sha512-2uNrRTVh51zZdM47Q3SGKyh7J0e2kywmwgJ9DC84nydiZTwV+zP6wMIryOCu6UhQJF1HEMgBtyA7RlHuojDP3Q==} + cpu: [x64] + os: [win32] + + '@seonbi/node@0.2.1-alpha.0': + resolution: {integrity: sha512-EVly/uBdA7OHl2KZ51kJz7tl7SslcP0WJn3xN3X8loPl4eNFzIdgwPOnF3L+hwex7/oxFge0DbYLPme5EGrCyg==} + '@shikijs/core@3.22.0': resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==} @@ -7671,6 +7708,34 @@ snapshots: '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 10.39.0 + '@seonbi/node-darwin-arm64@0.2.1-alpha.0': + optional: true + + '@seonbi/node-darwin-x64@0.2.1-alpha.0': + optional: true + + '@seonbi/node-linux-arm64-gnu@0.2.1-alpha.0': + optional: true + + '@seonbi/node-linux-x64-gnu@0.2.1-alpha.0': + optional: true + + '@seonbi/node-linux-x64-musl@0.2.1-alpha.0': + optional: true + + '@seonbi/node-win32-x64-msvc@0.2.1-alpha.0': + optional: true + + '@seonbi/node@0.2.1-alpha.0': + optionalDependencies: + '@seonbi/node-darwin-arm64': 0.2.1-alpha.0 + '@seonbi/node-darwin-x64': 0.2.1-alpha.0 + '@seonbi/node-linux-arm64-gnu': 0.2.1-alpha.0 + '@seonbi/node-linux-x64-gnu': 0.2.1-alpha.0 + '@seonbi/node-linux-x64-musl': 0.2.1-alpha.0 + '@seonbi/node-win32-x64-msvc': 0.2.1-alpha.0 + optional: true + '@shikijs/core@3.22.0': dependencies: '@shikijs/types': 3.22.0 diff --git a/src/text.ts b/src/text.ts index d195ac04..b7e02620 100644 --- a/src/text.ts +++ b/src/text.ts @@ -262,6 +262,21 @@ export function extractText(html: string | null): string | null { return $(":root").text(); } +const SEONBI_NATIVE = + process.env.SEONBI_NATIVE?.trim()?.toLowerCase() === "true"; + +let seonbiTransform: + | ((config: import("@seonbi/node").Configuration, input: string) => string) + | null = null; +if (SEONBI_NATIVE) { + try { + const mod = await import("@seonbi/node"); + seonbiTransform = mod.transform; + } catch { + logger.error("SEONBI_NATIVE is enabled but @seonbi/node is not installed"); + } +} + // biome-ignore lint/complexity/useLiteralKeys: tsc claims about this const SEONBI_URL = process.env["SEONBI_URL"]; @@ -314,6 +329,39 @@ async function transformWithSeonbi(html: string): Promise { return html; } +function transformWithSeonbiNative(html: string): string { + try { + return seonbiTransform!( + { + contentType: "text/html", + quote: "HorizontalCornerBrackets" as const, + cite: "AngleQuotes" as const, + arrow: { + bidirArrow: true, + doubleArrow: true, + }, + ellipsis: true, + emDash: true, + stop: "Horizontal" as const, + hanja: { + rendering: "HanjaInRuby" as const, + reading: { + initialSoundLaw: true, + useDictionaries: ["kr-stdict"], + dictionary: {}, + }, + }, + } as import("@seonbi/node").Configuration, + html, + ); + } catch (error) { + logger.error("Failed to format post content with Seonbi native: {error}", { + error, + }); + return html; + } +} + export async function formatPostContent( db: PgDatabase< PostgresJsQueryResultHKT, @@ -329,11 +377,12 @@ export async function formatPostContent( }, ): Promise { const result = await formatText(db, text, options); - if ( - SEONBI_URL != null && - (language === "ko" || language?.startsWith("ko-")) - ) { - result.html = await transformWithSeonbi(result.html); + if (language === "ko" || language?.startsWith("ko-")) { + if (seonbiTransform != null) { + result.html = transformWithSeonbiNative(result.html); + } else if (SEONBI_URL != null) { + result.html = await transformWithSeonbi(result.html); + } } return result; } From b2cabad97ea5b95e8d8324c96ba37db10c1048de Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 17:42:39 +0900 Subject: [PATCH 3/8] Extract PostContentTransformer type and getPostContentTransformer() Separate language detection and transformer selection logic from formatPostContent() into a dedicated getPostContentTransformer() function and a PostContentTransformer type alias. Co-Authored-By: Claude Opus 4.6 --- src/text.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/text.ts b/src/text.ts index b7e02620..7a02581e 100644 --- a/src/text.ts +++ b/src/text.ts @@ -262,6 +262,8 @@ export function extractText(html: string | null): string | null { return $(":root").text(); } +export type PostContentTransformer = (text: string) => Promise; + const SEONBI_NATIVE = process.env.SEONBI_NATIVE?.trim()?.toLowerCase() === "true"; @@ -329,7 +331,7 @@ async function transformWithSeonbi(html: string): Promise { return html; } -function transformWithSeonbiNative(html: string): string { +async function transformWithSeonbiNative(html: string): Promise { try { return seonbiTransform!( { @@ -362,6 +364,16 @@ function transformWithSeonbiNative(html: string): string { } } +function getPostContentTransformer( + language: string | null | undefined, +): PostContentTransformer | null { + if (language === "ko" || language?.startsWith("ko-")) { + if (seonbiTransform != null) return transformWithSeonbiNative; + if (SEONBI_URL != null) return transformWithSeonbi; + } + return null; +} + export async function formatPostContent( db: PgDatabase< PostgresJsQueryResultHKT, @@ -377,12 +389,9 @@ export async function formatPostContent( }, ): Promise { const result = await formatText(db, text, options); - if (language === "ko" || language?.startsWith("ko-")) { - if (seonbiTransform != null) { - result.html = transformWithSeonbiNative(result.html); - } else if (SEONBI_URL != null) { - result.html = await transformWithSeonbi(result.html); - } + const transformer = getPostContentTransformer(language); + if (transformer != null) { + result.html = await transformer(result.html); } return result; } From f445181008639a33c5303a4f449479e4850587ef Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 17:47:42 +0900 Subject: [PATCH 4/8] Rename transformWithSeonbi to transformWithSeonbiApi Clarify that this function uses the Seonbi HTTP API, distinguishing it from transformWithSeonbiNative which uses the native binding. Co-Authored-By: Claude Opus 4.6 --- src/text.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text.ts b/src/text.ts index 7a02581e..f85ecaa7 100644 --- a/src/text.ts +++ b/src/text.ts @@ -282,7 +282,7 @@ if (SEONBI_NATIVE) { // biome-ignore lint/complexity/useLiteralKeys: tsc claims about this const SEONBI_URL = process.env["SEONBI_URL"]; -async function transformWithSeonbi(html: string): Promise { +async function transformWithSeonbiApi(html: string): Promise { const response = await fetch(SEONBI_URL!, { method: "POST", body: JSON.stringify({ @@ -369,7 +369,7 @@ function getPostContentTransformer( ): PostContentTransformer | null { if (language === "ko" || language?.startsWith("ko-")) { if (seonbiTransform != null) return transformWithSeonbiNative; - if (SEONBI_URL != null) return transformWithSeonbi; + if (SEONBI_URL != null) return transformWithSeonbiApi; } return null; } From 6f20a5791bb37beee48f336a54a827c37795ca93 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 18:38:49 +0900 Subject: [PATCH 5/8] Add info log when @seonbi/node native binding is loaded --- src/text.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text.ts b/src/text.ts index f85ecaa7..0a8c836f 100644 --- a/src/text.ts +++ b/src/text.ts @@ -274,6 +274,7 @@ if (SEONBI_NATIVE) { try { const mod = await import("@seonbi/node"); seonbiTransform = mod.transform; + logger.info("Enabled seonbi native binding"); } catch { logger.error("SEONBI_NATIVE is enabled but @seonbi/node is not installed"); } From bfeac85dd757cea21adc9b59c97b9ccfd2c1214a Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 18:52:04 +0900 Subject: [PATCH 6/8] Refactor determining Korean post content transformer --- src/text.ts | 194 +++++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 94 deletions(-) diff --git a/src/text.ts b/src/text.ts index 0a8c836f..8c52ee5e 100644 --- a/src/text.ts +++ b/src/text.ts @@ -262,115 +262,121 @@ export function extractText(html: string | null): string | null { return $(":root").text(); } -export type PostContentTransformer = (text: string) => Promise; +export type PostContentTransformer = (html: string) => Promise; -const SEONBI_NATIVE = - process.env.SEONBI_NATIVE?.trim()?.toLowerCase() === "true"; +const koPostContentTransformer = await determineKoPostContentTransformer(); -let seonbiTransform: - | ((config: import("@seonbi/node").Configuration, input: string) => string) - | null = null; -if (SEONBI_NATIVE) { - try { - const mod = await import("@seonbi/node"); - seonbiTransform = mod.transform; - logger.info("Enabled seonbi native binding"); - } catch { - logger.error("SEONBI_NATIVE is enabled but @seonbi/node is not installed"); - } -} +async function determineKoPostContentTransformer(): Promise { + const SEONBI_NATIVE = + process.env.SEONBI_NATIVE?.trim()?.toLowerCase() === "true"; -// biome-ignore lint/complexity/useLiteralKeys: tsc claims about this -const SEONBI_URL = process.env["SEONBI_URL"]; + // biome-ignore lint/complexity/useLiteralKeys: tsc claims about this + const SEONBI_URL = process.env["SEONBI_URL"]; -async function transformWithSeonbiApi(html: string): Promise { - const response = await fetch(SEONBI_URL!, { - method: "POST", - body: JSON.stringify({ - content: html, - contentType: "text/html", - quote: "HorizontalCornerBrackets", - cite: "AngleQuotes", - arrow: { - bidirArrow: true, - doubleArrow: true, - }, - ellipsis: true, - emDash: true, - stop: "Horizontal", - hanja: { - rendering: "HanjaInRuby", - reading: { - initialSoundLaw: true, - useDictionaries: ["kr-stdict"], - dictionary: {}, - }, - }, - }), - }); - try { - const seonbiResult = await response.json(); - if (seonbiResult.success) { - if ( - Array.isArray(seonbiResult.warnings) && - seonbiResult.warnings.length > 0 - ) { - logger.warn("Seonbi warnings: {warnings}", { - warnings: seonbiResult.warnings, - }); - } - return seonbiResult.content; + if (SEONBI_NATIVE) { + try { + const { transform } = await import("@seonbi/node"); + return async (html: string) => { + try { + return transform( + { + contentType: "text/html", + quote: "HorizontalCornerBrackets" as const, + cite: "AngleQuotes" as const, + arrow: { + bidirArrow: true, + doubleArrow: true, + }, + ellipsis: true, + emDash: true, + stop: "Horizontal" as const, + hanja: { + rendering: "HanjaInRuby" as const, + reading: { + initialSoundLaw: true, + useDictionaries: ["kr-stdict"], + dictionary: {}, + }, + }, + } as import("@seonbi/node").Configuration, + html, + ); + } catch (error) { + logger.error( + "Failed to format post content with Seonbi native: {error}", + { + error, + }, + ); + return html; + } + }; + } catch { + logger.error( + "SEONBI_NATIVE is enabled but @seonbi/node is not installed", + ); } - logger.error("Seonbi failed to format post content: {message}", { - message: seonbiResult.message, - }); - } catch (error) { - logger.error("Failed to format post content with Seonbi: {error}", { - error, - }); } - return html; -} -async function transformWithSeonbiNative(html: string): Promise { - try { - return seonbiTransform!( - { - contentType: "text/html", - quote: "HorizontalCornerBrackets" as const, - cite: "AngleQuotes" as const, - arrow: { - bidirArrow: true, - doubleArrow: true, - }, - ellipsis: true, - emDash: true, - stop: "Horizontal" as const, - hanja: { - rendering: "HanjaInRuby" as const, - reading: { - initialSoundLaw: true, - useDictionaries: ["kr-stdict"], - dictionary: {}, + if (SEONBI_URL != null) { + return async (html: string): Promise => { + const response = await fetch(SEONBI_URL, { + method: "POST", + body: JSON.stringify({ + content: html, + contentType: "text/html", + quote: "HorizontalCornerBrackets", + cite: "AngleQuotes", + arrow: { + bidirArrow: true, + doubleArrow: true, }, - }, - } as import("@seonbi/node").Configuration, - html, - ); - } catch (error) { - logger.error("Failed to format post content with Seonbi native: {error}", { - error, - }); - return html; + ellipsis: true, + emDash: true, + stop: "Horizontal", + hanja: { + rendering: "HanjaInRuby", + reading: { + initialSoundLaw: true, + useDictionaries: ["kr-stdict"], + dictionary: {}, + }, + }, + }), + }); + try { + const seonbiResult = await response.json(); + if (seonbiResult.success) { + if ( + Array.isArray(seonbiResult.warnings) && + seonbiResult.warnings.length > 0 + ) { + logger.warn("Seonbi warnings: {warnings}", { + warnings: seonbiResult.warnings, + }); + } + return seonbiResult.content; + } + logger.error("Seonbi failed to format post content: {message}", { + message: seonbiResult.message, + }); + } catch (error) { + logger.error("Failed to format post content with Seonbi: {error}", { + error, + }); + } + return html; + }; } + + return async (html: string) => html; } function getPostContentTransformer( language: string | null | undefined, ): PostContentTransformer | null { if (language === "ko" || language?.startsWith("ko-")) { - if (seonbiTransform != null) return transformWithSeonbiNative; - if (SEONBI_URL != null) return transformWithSeonbiApi; + return koPostContentTransformer; } return null; } From 06b137ba7a864a670ca9189e1839f8f4897a7a69 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Fri, 27 Feb 2026 18:52:48 +0900 Subject: [PATCH 7/8] Bump @seonbi/node to 0.2.1 --- package.json | 2 +- pnpm-lock.yaml | 58 +++++++++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index f2f4136a..3cc827a1 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "vitest": "^3.1.4" }, "optionalDependencies": { - "@seonbi/node": "0.2.1-alpha.0" + "@seonbi/node": "0.2.1" }, "packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c45e2f52..0961bdfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,8 +160,8 @@ importers: version: 4.3.5 optionalDependencies: '@seonbi/node': - specifier: 0.2.1-alpha.0 - version: 0.2.1-alpha.0 + specifier: 0.2.1 + version: 0.2.1 devDependencies: '@biomejs/biome': specifier: ^2.4.4 @@ -2195,38 +2195,38 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 - '@seonbi/node-darwin-arm64@0.2.1-alpha.0': - resolution: {integrity: sha512-oE0JmApaEk0wvucxjokOY1QbE046t34AsJpDlS+0yv4cc0RO0MtnXsfD+e8OhGATnEH27tk28ZLvwfMuyqbk7A==} + '@seonbi/node-darwin-arm64@0.2.1': + resolution: {integrity: sha512-GTIlLXcatfzVnTluN8y2vcCWAsrNvX0gc57CuYx1n7xK09Vm8U9c6Hw/x+gFCWSfODyDbwV9uRXZDF4xTBaY5Q==} cpu: [arm64] os: [darwin] - '@seonbi/node-darwin-x64@0.2.1-alpha.0': - resolution: {integrity: sha512-kFrnHXQze8q58u7KvsXidzES2P8VbsE/TVVhP21kcJqdXu1Oloo+tZAbvVJRxVU02otR1hXU4rayf+pLVnivvw==} + '@seonbi/node-darwin-x64@0.2.1': + resolution: {integrity: sha512-Eq4WIFaMcQ9hKufwAoewO5d6TIZl5XfFIu8YgQGJfuhE5vXl/weUhoFmaqJJJmcEMJF4XNwEKyC4y5nv/P63Bg==} cpu: [x64] os: [darwin] - '@seonbi/node-linux-arm64-gnu@0.2.1-alpha.0': - resolution: {integrity: sha512-pgF9/8/oj6r6R7TJjzvevjPNY+TmpwoNBr0dPx19nAEQZFG44FgGvo373HEw72jh2V5qPiarLCSxNnrV8Q/pJw==} + '@seonbi/node-linux-arm64-gnu@0.2.1': + resolution: {integrity: sha512-yKGDq/iNmI7W1r03+q4DzoFbBe9dImyjgfp/wb3sgDB/DGS31c/rm1GdeEwWmlh3BrCMzhZkfJjQ0oPQAzajbg==} cpu: [arm64] os: [linux] - '@seonbi/node-linux-x64-gnu@0.2.1-alpha.0': - resolution: {integrity: sha512-mN79CWZzcrT8Rf2/G8rCROz+rUZMhdcYVa+1UAuWFKPxXreGt+hizFrH+5azpJvua60hY20T7AimZwI+vNFBkg==} + '@seonbi/node-linux-x64-gnu@0.2.1': + resolution: {integrity: sha512-MDGQiCaH+4C3sdMatuK96CFwJIcZ8qWohX2qM2Q2Wxmjf5VTaJDuJ9HBLV2xvK0KLUtmGVAaI4T2YxnkT3RJPg==} cpu: [x64] os: [linux] - '@seonbi/node-linux-x64-musl@0.2.1-alpha.0': - resolution: {integrity: sha512-LnPr0l6kF2lBeBgu5ofGFyym7joFdoK12HKXYSnlGO+TEuetyWNhPX5O/2kZLWArF5wKQgPwUjSuza/kw8lcQg==} + '@seonbi/node-linux-x64-musl@0.2.1': + resolution: {integrity: sha512-y88O0DJoDA9GeuFWw8IDWeCVk/JmPr6GoYHWx2Bt76ZfRyZicXApJGHaHlfJh1UH2KKFe+iZLFdrculz79pvJg==} cpu: [x64] os: [linux] - '@seonbi/node-win32-x64-msvc@0.2.1-alpha.0': - resolution: {integrity: sha512-2uNrRTVh51zZdM47Q3SGKyh7J0e2kywmwgJ9DC84nydiZTwV+zP6wMIryOCu6UhQJF1HEMgBtyA7RlHuojDP3Q==} + '@seonbi/node-win32-x64-msvc@0.2.1': + resolution: {integrity: sha512-P6JOdmhQ6werGebzzZ3R3PAEuCrieMr55KThzudxmGE11GydyB9+Witgl1u0WAryr7cvrrEYqYKFvStRQLzSkg==} cpu: [x64] os: [win32] - '@seonbi/node@0.2.1-alpha.0': - resolution: {integrity: sha512-EVly/uBdA7OHl2KZ51kJz7tl7SslcP0WJn3xN3X8loPl4eNFzIdgwPOnF3L+hwex7/oxFge0DbYLPme5EGrCyg==} + '@seonbi/node@0.2.1': + resolution: {integrity: sha512-gOzS78Zj+qgjwGClgXxGadbNOIMaEYAruT080gGsBVXviWif/3yccgKPaHXaLSQl0LnASra7uMvPAvWcyKYDaw==} '@shikijs/core@3.22.0': resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==} @@ -7708,32 +7708,32 @@ snapshots: '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 10.39.0 - '@seonbi/node-darwin-arm64@0.2.1-alpha.0': + '@seonbi/node-darwin-arm64@0.2.1': optional: true - '@seonbi/node-darwin-x64@0.2.1-alpha.0': + '@seonbi/node-darwin-x64@0.2.1': optional: true - '@seonbi/node-linux-arm64-gnu@0.2.1-alpha.0': + '@seonbi/node-linux-arm64-gnu@0.2.1': optional: true - '@seonbi/node-linux-x64-gnu@0.2.1-alpha.0': + '@seonbi/node-linux-x64-gnu@0.2.1': optional: true - '@seonbi/node-linux-x64-musl@0.2.1-alpha.0': + '@seonbi/node-linux-x64-musl@0.2.1': optional: true - '@seonbi/node-win32-x64-msvc@0.2.1-alpha.0': + '@seonbi/node-win32-x64-msvc@0.2.1': optional: true - '@seonbi/node@0.2.1-alpha.0': + '@seonbi/node@0.2.1': optionalDependencies: - '@seonbi/node-darwin-arm64': 0.2.1-alpha.0 - '@seonbi/node-darwin-x64': 0.2.1-alpha.0 - '@seonbi/node-linux-arm64-gnu': 0.2.1-alpha.0 - '@seonbi/node-linux-x64-gnu': 0.2.1-alpha.0 - '@seonbi/node-linux-x64-musl': 0.2.1-alpha.0 - '@seonbi/node-win32-x64-msvc': 0.2.1-alpha.0 + '@seonbi/node-darwin-arm64': 0.2.1 + '@seonbi/node-darwin-x64': 0.2.1 + '@seonbi/node-linux-arm64-gnu': 0.2.1 + '@seonbi/node-linux-x64-gnu': 0.2.1 + '@seonbi/node-linux-x64-musl': 0.2.1 + '@seonbi/node-win32-x64-msvc': 0.2.1 optional: true '@shikijs/core@3.22.0': From 4791951881a3c61df445f020a4b1526213161d8c Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Sat, 28 Feb 2026 15:10:31 +0900 Subject: [PATCH 8/8] Deduplicate seonbi configuration --- package.json | 2 +- pnpm-lock.yaml | 58 ++++++++++++++++++++++----------------------- src/text.ts | 64 ++++++++++++++++++-------------------------------- 3 files changed, 53 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 3cc827a1..652d52bc 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "vitest": "^3.1.4" }, "optionalDependencies": { - "@seonbi/node": "0.2.1" + "@seonbi/node": "0.2.2" }, "packageManager": "pnpm@9.15.1+sha512.1acb565e6193efbebda772702950469150cf12bcc764262e7587e71d19dc98a423dff9536e57ea44c49bdf790ff694e83c27be5faa23d67e0c033b583be4bfcf" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0961bdfa..56d7ce47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,8 +160,8 @@ importers: version: 4.3.5 optionalDependencies: '@seonbi/node': - specifier: 0.2.1 - version: 0.2.1 + specifier: 0.2.2 + version: 0.2.2 devDependencies: '@biomejs/biome': specifier: ^2.4.4 @@ -2195,38 +2195,38 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 - '@seonbi/node-darwin-arm64@0.2.1': - resolution: {integrity: sha512-GTIlLXcatfzVnTluN8y2vcCWAsrNvX0gc57CuYx1n7xK09Vm8U9c6Hw/x+gFCWSfODyDbwV9uRXZDF4xTBaY5Q==} + '@seonbi/node-darwin-arm64@0.2.2': + resolution: {integrity: sha512-seMwCWrP57tRU+K4q0UrpulUKW6Iydu6CZqOTqul+p/QhI9iQ3RR4yL374QXi3WI56KcwAFQ+gIfl1PWTVX4NA==} cpu: [arm64] os: [darwin] - '@seonbi/node-darwin-x64@0.2.1': - resolution: {integrity: sha512-Eq4WIFaMcQ9hKufwAoewO5d6TIZl5XfFIu8YgQGJfuhE5vXl/weUhoFmaqJJJmcEMJF4XNwEKyC4y5nv/P63Bg==} + '@seonbi/node-darwin-x64@0.2.2': + resolution: {integrity: sha512-ujSO6hJC8sJHQdUsn99sVFhlKhwnSr1QFOMWP+ZRy5knLvgX8vGMTTh3oTF8gajOD0BhhdMK5DEIQg31vSancA==} cpu: [x64] os: [darwin] - '@seonbi/node-linux-arm64-gnu@0.2.1': - resolution: {integrity: sha512-yKGDq/iNmI7W1r03+q4DzoFbBe9dImyjgfp/wb3sgDB/DGS31c/rm1GdeEwWmlh3BrCMzhZkfJjQ0oPQAzajbg==} + '@seonbi/node-linux-arm64-gnu@0.2.2': + resolution: {integrity: sha512-cPR+NPTXCr3nK/oOsm9vAA8cYZy7qwe/++qYgJ6L/5BvfyipOSesBaRIVhbBVuXFm6Y2IjRezhX+krIoMrwuxQ==} cpu: [arm64] os: [linux] - '@seonbi/node-linux-x64-gnu@0.2.1': - resolution: {integrity: sha512-MDGQiCaH+4C3sdMatuK96CFwJIcZ8qWohX2qM2Q2Wxmjf5VTaJDuJ9HBLV2xvK0KLUtmGVAaI4T2YxnkT3RJPg==} + '@seonbi/node-linux-x64-gnu@0.2.2': + resolution: {integrity: sha512-LyLe7Jy7ltr0D5C7CKkP7mpUeBsWD1ffAnQhgz1IfQXVTxYYEgYRjKOVaCVGCXVE1SKJCis1cG6mEYCehfYpQw==} cpu: [x64] os: [linux] - '@seonbi/node-linux-x64-musl@0.2.1': - resolution: {integrity: sha512-y88O0DJoDA9GeuFWw8IDWeCVk/JmPr6GoYHWx2Bt76ZfRyZicXApJGHaHlfJh1UH2KKFe+iZLFdrculz79pvJg==} + '@seonbi/node-linux-x64-musl@0.2.2': + resolution: {integrity: sha512-gBIYQAB9g0SHvVJ1y9YAvZPGRYKSM6Ll0JG0cYz9VYY4WjjxUpWuqJac/K0fcEvSP46VLXU/WjUjBX74bBLK3A==} cpu: [x64] os: [linux] - '@seonbi/node-win32-x64-msvc@0.2.1': - resolution: {integrity: sha512-P6JOdmhQ6werGebzzZ3R3PAEuCrieMr55KThzudxmGE11GydyB9+Witgl1u0WAryr7cvrrEYqYKFvStRQLzSkg==} + '@seonbi/node-win32-x64-msvc@0.2.2': + resolution: {integrity: sha512-SHxLaMj/FUAJMmBY4a2oPeCD4uKQ2vKNSNdCCwY+/Q8nRVcPPqtui7owf+7/mJhu6SxXVSBqujOxsrlWvfVgWw==} cpu: [x64] os: [win32] - '@seonbi/node@0.2.1': - resolution: {integrity: sha512-gOzS78Zj+qgjwGClgXxGadbNOIMaEYAruT080gGsBVXviWif/3yccgKPaHXaLSQl0LnASra7uMvPAvWcyKYDaw==} + '@seonbi/node@0.2.2': + resolution: {integrity: sha512-FpsjHw71LUGrss7DwJXcePQPdN8UU4qZi5On67K/LFNYn8tRbdjbh4tlK1rKdx3BwS1m0GGNUMxwpdZ5Dwsi/Q==} '@shikijs/core@3.22.0': resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==} @@ -7708,32 +7708,32 @@ snapshots: '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 10.39.0 - '@seonbi/node-darwin-arm64@0.2.1': + '@seonbi/node-darwin-arm64@0.2.2': optional: true - '@seonbi/node-darwin-x64@0.2.1': + '@seonbi/node-darwin-x64@0.2.2': optional: true - '@seonbi/node-linux-arm64-gnu@0.2.1': + '@seonbi/node-linux-arm64-gnu@0.2.2': optional: true - '@seonbi/node-linux-x64-gnu@0.2.1': + '@seonbi/node-linux-x64-gnu@0.2.2': optional: true - '@seonbi/node-linux-x64-musl@0.2.1': + '@seonbi/node-linux-x64-musl@0.2.2': optional: true - '@seonbi/node-win32-x64-msvc@0.2.1': + '@seonbi/node-win32-x64-msvc@0.2.2': optional: true - '@seonbi/node@0.2.1': + '@seonbi/node@0.2.2': optionalDependencies: - '@seonbi/node-darwin-arm64': 0.2.1 - '@seonbi/node-darwin-x64': 0.2.1 - '@seonbi/node-linux-arm64-gnu': 0.2.1 - '@seonbi/node-linux-x64-gnu': 0.2.1 - '@seonbi/node-linux-x64-musl': 0.2.1 - '@seonbi/node-win32-x64-msvc': 0.2.1 + '@seonbi/node-darwin-arm64': 0.2.2 + '@seonbi/node-darwin-x64': 0.2.2 + '@seonbi/node-linux-arm64-gnu': 0.2.2 + '@seonbi/node-linux-x64-gnu': 0.2.2 + '@seonbi/node-linux-x64-musl': 0.2.2 + '@seonbi/node-win32-x64-msvc': 0.2.2 optional: true '@shikijs/core@3.22.0': diff --git a/src/text.ts b/src/text.ts index 8c52ee5e..2b82c43f 100644 --- a/src/text.ts +++ b/src/text.ts @@ -273,34 +273,33 @@ async function determineKoPostContentTransformer(): Promise { try { - return transform( - { - contentType: "text/html", - quote: "HorizontalCornerBrackets" as const, - cite: "AngleQuotes" as const, - arrow: { - bidirArrow: true, - doubleArrow: true, - }, - ellipsis: true, - emDash: true, - stop: "Horizontal" as const, - hanja: { - rendering: "HanjaInRuby" as const, - reading: { - initialSoundLaw: true, - useDictionaries: ["kr-stdict"], - dictionary: {}, - }, - }, - } as import("@seonbi/node").Configuration, - html, - ); + return transform(SEONBI_CONFIGURATION, html); } catch (error) { logger.error( "Failed to format post content with Seonbi native: {error}", @@ -323,25 +322,8 @@ async function determineKoPostContentTransformer(): Promise