@spfn/cms 0.1.0-alpha.83 → 0.1.0-alpha.86
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.
- package/dist/server.js +6 -2
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -1573,7 +1573,9 @@ var getSection = cache(async (section, locale) => {
|
|
|
1573
1573
|
const response = await client.call(
|
|
1574
1574
|
getPublishedCacheContract,
|
|
1575
1575
|
{
|
|
1576
|
-
query: { sections: section, locale: actualLocale }
|
|
1576
|
+
query: { sections: section, locale: actualLocale },
|
|
1577
|
+
fetchOptions: { next: { revalidate: 60 } }
|
|
1578
|
+
// 60초마다 자동 갱신
|
|
1577
1579
|
}
|
|
1578
1580
|
);
|
|
1579
1581
|
if ("error" in response) {
|
|
@@ -1647,7 +1649,9 @@ var getSections = cache(async (sections, locale) => {
|
|
|
1647
1649
|
const response = await client.call(
|
|
1648
1650
|
getPublishedCacheContract,
|
|
1649
1651
|
{
|
|
1650
|
-
query: { sections, locale: actualLocale }
|
|
1652
|
+
query: { sections, locale: actualLocale },
|
|
1653
|
+
fetchOptions: { next: { revalidate: 60 } }
|
|
1654
|
+
// 60초마다 자동 갱신
|
|
1651
1655
|
}
|
|
1652
1656
|
);
|
|
1653
1657
|
if ("error" in response) {
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/lib/contracts/published-cache.ts","../src/server/helpers/locale.actions.ts","../src/server/config/cms.config.ts","../src/lib/constants/locale.constants.ts","../src/server/helpers/sync.ts","../src/server/labels/helpers.ts","../src/server/repositories/cms-labels.repository.ts","../src/server/entities/cms-labels.ts","../src/server/entities/cms-schema.ts","../src/server/entities/cms-label-values.ts","../src/server/entities/cms-draft-cache.ts","../src/server/entities/cms-published-cache.ts","../src/server/entities/cms-audit-logs.ts","../src/server/repositories/cms-label-values.repository.ts","../src/server/repositories/cms-draft-cache.repository.ts","../src/server/repositories/cms-published-cache.repository.ts","../src/lib/constants/index.ts","../src/server/generators/label-sync-generator.ts"],"sourcesContent":["/**\n * @spfn/cms/server\n *\n * Server-side Only Module\n * 서버 전용 모듈 (서버 컴포넌트 + 백엔드)\n *\n * Includes:\n * - Server Components (getSection, getSections)\n * - Locale Management (Server Actions)\n * - Backend: Sync utilities\n * - Backend: Repositories\n * - Backend: Entities\n * - Backend: Label helpers\n * - Backend: Codegen generators\n *\n * @note This module should only be imported in server-side code\n */\n\nimport { cache } from 'react';\nimport { client } from '@spfn/core/client';\nimport { getPublishedCacheContract } from '@/lib/contracts/published-cache';\nimport { getLocale } from '@/server/helpers/locale.actions';\n\n/**\n * Section Data Type\n */\nexport type SectionData = {\n section: string;\n locale: string;\n content: Record<string, any>;\n version: number;\n publishedAt: string | null;\n};\n\n/**\n * Translation Function Type (runtime version)\n */\ntype ServerTranslationFunction = (\n key: string,\n defaultValue?: any,\n replace?: Record<string, string | number>\n) => any;\n\n/**\n * Section API Return Type\n */\nexport type SectionAPI = {\n /**\n * 라벨 값 가져오기 (변수 치환 지원)\n *\n * @param key - 라벨 키 (섹션 제외, 예: 'hero.title')\n * @param defaultValue - 기본값\n * @param replace - 변수 치환 맵 (예: { name: 'John' })\n * @returns 라벨 값 (문자열인 경우 변수 치환됨)\n */\n t: ServerTranslationFunction;\n\n /**\n * 섹션 데이터\n */\n data: SectionData;\n};\n\n/**\n * 변수 치환 헬퍼\n *\n * @param text - 치환할 텍스트 (예: 'Hello {name}!')\n * @param replace - 치환 맵 (예: { name: 'World' })\n * @returns 치환된 텍스트 (예: 'Hello World!')\n */\nfunction replaceVariables(text: string, replace: Record<string, string | number>): string\n{\n return text.replace(/\\{(\\w+)}/g, (match, key) =>\n {\n const value = replace[key];\n return value !== undefined ? String(value) : match;\n });\n}\n\n/**\n * 섹션 데이터 로드 (React cache 적용)\n *\n * 동일한 요청 내에서 같은 섹션을 여러 번 요청해도 한 번만 API 호출\n *\n * @param section - 섹션 이름 (예: 'home', 'why-futureplay')\n * @param locale - 언어 코드 (선택, 미지정시 쿠키에서 자동 조회)\n * @returns Section API ({ t, data })\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getSection } from '@spfn/cms/server';\n *\n * export default async function HomePage()\n * {\n * // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴\n * const { t } = await getSection('home');\n *\n * // 또는 명시적으로 locale 지정\n * const { t: tEn } = await getSection('home', 'en');\n *\n * return (\n * <div>\n * <h1>{t('hero.title')}</h1>\n * <p>{t('hero.subtitle', 'Default Subtitle')}</p>\n * <p>{t('hero.greeting', 'Hello {name}!', { name: 'World' })}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport const getSection = cache(async (\n section: string,\n locale?: string\n): Promise<SectionAPI> =>\n{\n // locale이 지정되지 않으면 쿠키에서 가져옴\n const actualLocale: string = locale ?? await getLocale();\n\n try\n {\n // Call SPFN API via contract (uses singleton client)\n const response = await client.call(\n getPublishedCacheContract,\n {\n query: { sections: section, locale: actualLocale },\n }\n );\n\n // Check if response has error\n if ('error' in response)\n {\n console.warn(`[CMS] ${response.error}`);\n // Return empty section data\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n\n // Response is an array, get first element\n const found = response[0];\n\n if (!found)\n {\n // Section not found, return empty\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n\n // Success response\n const sectionData: SectionData = {\n section: found.section,\n locale: found.locale,\n content: found.content || {},\n version: found.version,\n publishedAt: found.publishedAt,\n };\n\n // Translation function\n const t: ServerTranslationFunction = (key, defaultValue, replace) =>\n {\n const fullKey = `${section}.${key}`;\n let value = sectionData.content[fullKey];\n\n if (value === undefined || value === null)\n {\n value = defaultValue ?? '';\n }\n\n // text 타입 객체이면 content 필드 추출\n if (typeof value === 'object' && value !== null && value.type === 'text' && 'content' in value)\n {\n value = value.content;\n }\n\n // 문자열인 경우 변수 치환 처리\n if (typeof value === 'string')\n {\n if (replace)\n {\n value = replaceVariables(value, replace);\n }\n return value;\n }\n\n // 문자열이 아니면 원본 값 반환 (객체 타입: image, video, file, object 등)\n return value;\n };\n\n return {\n t,\n data: sectionData,\n };\n }\n catch (error)\n {\n console.error(`[CMS] Failed to fetch section \"${section}\":`, error);\n\n // Return empty section data on error\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n});\n\n/**\n * 여러 섹션 한번에 로드 (React cache 적용)\n * 단일 API 호출로 여러 섹션을 효율적으로 가져옵니다\n *\n * @param sections - 섹션 이름 배열\n * @param locale - 언어 코드 (선택, 미지정시 쿠키에서 자동 조회)\n * @returns Section API 맵 ({ home: { t, data }, ... })\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getSections } from '@spfn/cms/server';\n *\n * export default async function Page()\n * {\n * // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴\n * const sections = await getSections(['home', 'why-futureplay']);\n *\n * // 또는 명시적으로 locale 지정\n * const sectionsEn = await getSections(['home', 'why-futureplay'], 'en');\n *\n * return (\n * <div>\n * <h1>{sections.home.t('hero.title')}</h1>\n * <p>{sections['why-futureplay'].t('intro.text')}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport const getSections = cache(async (\n sections: string[],\n locale?: string\n): Promise<Record<string, SectionAPI>> =>\n{\n // locale이 지정되지 않으면 쿠키에서 가져옴\n const actualLocale: string = locale ?? await getLocale();\n\n try\n {\n // Call SPFN API with array of sections (single HTTP request)\n const response = await client.call(\n getPublishedCacheContract,\n {\n query: { sections, locale: actualLocale },\n }\n );\n\n // Check if response has error\n if ('error' in response)\n {\n console.warn(`[CMS] ${response.error}`);\n // Return empty sections\n const sectionsMap: Record<string, SectionAPI> = {};\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n return sectionsMap;\n }\n\n // Build sections map from response\n const sectionsMap: Record<string, SectionAPI> = {};\n\n // First, create empty entries for all requested sections\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n\n // Then, fill in data for found sections\n response.forEach(sectionData =>\n {\n const createTranslationFn = (section: string, content: Record<string, any>): ServerTranslationFunction =>\n {\n return (key, defaultValue, replace) =>\n {\n const fullKey = `${section}.${key}`;\n let value = content[fullKey];\n\n if (value === undefined || value === null)\n {\n value = defaultValue ?? '';\n }\n\n // text 타입 객체이면 content 필드 추출\n if (typeof value === 'object' && value !== null && value.type === 'text' && 'content' in value)\n {\n value = value.content;\n }\n\n // 문자열인 경우 변수 치환 처리\n if (typeof value === 'string')\n {\n if (replace)\n {\n value = replaceVariables(value, replace);\n }\n return value;\n }\n\n // 문자열이 아니면 원본 값 반환 (객체 타입: image, video, file, object 등)\n return value;\n };\n };\n\n sectionsMap[sectionData.section] = {\n t: createTranslationFn(sectionData.section, sectionData.content),\n data: {\n section: sectionData.section,\n locale: sectionData.locale,\n content: sectionData.content,\n version: sectionData.version,\n publishedAt: sectionData.publishedAt,\n }\n };\n });\n\n return sectionsMap;\n }\n catch (error)\n {\n console.error(`[CMS] Failed to fetch sections:`, error);\n\n // Return empty sections on error\n const sectionsMap: Record<string, SectionAPI> = {};\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n return sectionsMap;\n }\n});\n\n// ============================================================================\n// Locale Management (Constants Only)\n// ============================================================================\n// Note: Server Actions (getLocale, setLocale, etc.) are exported from actions.ts\n// to avoid bundling \"use server\" directives into server.ts\n\nexport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n getSupportedLocales,\n getFlag,\n getDialCode,\n isRTL,\n LOCALE_INFO_MAP,\n type LocaleInfo,\n type SupportedLocale,\n} from './lib/constants/locale.constants';\n\n// ============================================================================\n// Backend: Sync Utilities (server startup, CLI scripts)\n// ============================================================================\n\nexport { syncSection, syncAll, initLabelSync, loadLabelsFromJson } from './server/helpers/sync';\n\n// ============================================================================\n// Backend: Repositories (DB access)\n// ============================================================================\n\nexport * from './server/repositories/index';\n\n// ============================================================================\n// Backend: Entities (DB schemas)\n// ============================================================================\n\nexport * from './server/entities/index';\n\n// ============================================================================\n// Backend: Label Helpers (for processing JSON labels)\n// ============================================================================\n\nexport * from './server/labels/index';\n\n// ============================================================================\n// Backend: Codegen Generators (for development)\n// ============================================================================\n\nexport { createLabelSyncGenerator } from './server/generators/label-sync-generator';","import { Type } from '@sinclair/typebox';\nimport type { RouteContract } from '@spfn/core/route';\n\nconst SectionData = Type.Object({\n section: Type.String(),\n locale: Type.String(),\n content: Type.Record(Type.String(), Type.Any()),\n version: Type.Number(),\n publishedAt: Type.Union([Type.String(), Type.Null()]),\n});\n\n/**\n * GET /_cms/published-cache\n * 발행된 콘텐츠 캐시 조회 (단일 또는 여러 섹션)\n */\nexport const getPublishedCacheContract = {\n method: 'GET' as const,\n path: '/_cms/published-cache',\n query: Type.Object({\n sections: Type.Union([\n Type.String({ description: '단일 섹션 이름 (예: home)' }),\n Type.Array(Type.String(), { description: '여러 섹션 이름 (예: [\"home\", \"footer\"])' })\n ]),\n locale: Type.Optional(Type.String({ default: 'ko', description: '언어 코드' })),\n }),\n response: Type.Union([\n // 성공: 항상 배열로 반환\n Type.Array(SectionData),\n // 에러\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/published-cache\n * 발행된 콘텐츠 캐시 업데이트/생성 (upsert)\n */\nexport const upsertPublishedCacheContract = {\n method: 'POST' as const,\n path: '/_cms/published-cache',\n body: Type.Object({\n section: Type.String({ description: '섹션 이름 (예: home)' }),\n locale: Type.String({ description: '언어 코드 (예: ko, en, ja)' }),\n content: Type.Record(Type.String(), Type.Any(), { description: '발행할 콘텐츠 (key-value 형태)' }),\n version: Type.Number({ description: '버전 번호' })\n }),\n response: Type.Union([\n SectionData,\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;","\"use server\";\n\n/**\n * Locale Management Server Actions\n *\n * Server Actions으로 구현된 locale 관리 함수\n * - 서버 컴포넌트: 일반 함수 호출로 동작\n * - 클라이언트 컴포넌트: Server Action으로 자동 처리\n */\n\nimport { cookies, headers } from 'next/headers.js';\nimport { getCmsConfig } from '@/server/config/cms.config';\nimport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n type LocaleInfo,\n} from '@/lib/constants/locale.constants';\n\n/**\n * 브라우저 언어 감지\n *\n * Accept-Language 헤더에서 지원하는 언어를 찾습니다.\n *\n * @returns 감지된 언어 코드 또는 null\n */\nasync function detectBrowserLanguage(): Promise<string | null>\n{\n try\n {\n const headersList = await headers();\n const acceptLanguage = headersList.get('accept-language');\n\n if (!acceptLanguage)\n {\n return null;\n }\n\n // \"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\" 형식 파싱\n const languages = acceptLanguage\n .split(',')\n .map(lang =>\n {\n const [code] = lang.split(';');\n return code.split('-')[0].trim();\n });\n\n const config = getCmsConfig();\n\n // 지원하는 언어 중 첫 번째 매칭되는 언어 반환\n for (const lang of languages)\n {\n if (config.locales.includes(lang))\n {\n return lang;\n }\n }\n\n return null;\n }\n catch (error)\n {\n // 헤더 접근 실패 시\n return null;\n }\n}\n\n/**\n * 현재 locale 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * 우선순위:\n * 1. 쿠키 (사용자가 명시적으로 선택한 언어)\n * 2. 브라우저 언어 감지 (설정에서 활성화된 경우)\n * 3. 시스템 기본 언어 (CMS 설정)\n *\n * @returns 현재 locale (예: 'ko', 'en')\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locale = await getLocale();\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locale, setLocale] = useState('');\n *\n * useEffect(() => {\n * getLocale().then(setLocale);\n * }, []);\n *\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n */\nexport async function getLocale(): Promise<string>\n{\n const config = getCmsConfig();\n\n // 1순위: 쿠키 (사용자가 명시적으로 선택한 언어)\n const cookieStore = await cookies();\n const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;\n\n if (cookieLocale && config.locales.includes(cookieLocale))\n {\n return cookieLocale;\n }\n\n // 2순위: 브라우저 언어 감지 (설정에서 활성화된 경우)\n if (config.detectBrowserLanguage)\n {\n const browserLang = await detectBrowserLanguage();\n if (browserLang)\n {\n return browserLang;\n }\n }\n\n // 3순위: 시스템 기본 언어\n return config.defaultLocale;\n}\n\n/**\n * Locale 설정하기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n * 쿠키에 locale을 저장합니다.\n *\n * @param locale - 설정할 locale (예: 'ko', 'en')\n * @throws {Error} 지원하지 않는 locale인 경우\n *\n * @example\n * ```tsx\n * // Server Component (Server Action)\n * import { setLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * await setLocale('en');\n * return <div>Locale changed</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component (Server Action)\n * 'use client';\n * import { setLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const handleChange = async (newLocale: string) =>\n * {\n * await setLocale(newLocale);\n * window.location.reload(); // 페이지 새로고침\n * };\n *\n * return (\n * <button onClick={() => handleChange('en')}>\n * Switch to English\n * </button>\n * );\n * }\n * ```\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const config = getCmsConfig();\n\n // 유효성 검사\n if (!config.locales.includes(locale))\n {\n throw new Error(\n `Unsupported locale: ${locale}. Supported locales: ${config.locales.join(', ')}`\n );\n }\n\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_KEY, locale, {\n path: '/',\n maxAge: 60 * 60 * 24 * 365, // 1년\n sameSite: 'lax',\n });\n}\n\n/**\n * 지원하는 locale 목록 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * @returns 지원하는 locale 배열 (예: ['ko', 'en', 'ja'])\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocales } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locales = await getLocales();\n * return <div>Supported: {locales.join(', ')}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocales } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locales, setLocales] = useState<string[]>([]);\n *\n * useEffect(() => {\n * getLocales().then(setLocales);\n * }, []);\n *\n * return (\n * <div>\n * {locales.map(locale => (\n * <button key={locale}>{locale}</button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocales(): Promise<string[]>\n{\n const config = getCmsConfig();\n return config.locales;\n}\n\n/**\n * 현재 locale과 상세 정보 함께 가져오기 (Server Action)\n *\n * locale 코드와 함께 국가 코드, 국기, 전화번호 코드 등의 상세 정보를 반환합니다.\n *\n * @returns Locale 코드와 LocaleInfo 객체\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocaleWithInfo } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const { locale, info } = await getLocaleWithInfo();\n *\n * return (\n * <div>\n * <span>{info?.flag}</span>\n * <span>{info?.nativeName}</span>\n * <span>{info?.dialCode}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocaleWithInfo(): Promise<{\n locale: string;\n info: LocaleInfo | undefined;\n}>\n{\n const locale = await getLocale();\n const info = getLocaleInfo(locale);\n\n return { locale, info };\n}\n\n/**\n * 지원하는 모든 locale과 상세 정보 가져오기 (Server Action)\n *\n * 시스템이 지원하는 모든 locale의 상세 정보를 배열로 반환합니다.\n * 언어 선택 UI를 만들 때 유용합니다.\n *\n * @returns LocaleInfo 배열\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocalesWithInfo } from '@spfn/cms/actions';\n *\n * export default async function LanguageSelector()\n * {\n * const locales = await getLocalesWithInfo();\n *\n * return (\n * <select>\n * {locales.map(info => (\n * <option key={info.locale} value={info.locale}>\n * {info.flag} {info.nativeName}\n * </option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport async function getLocalesWithInfo(): Promise<LocaleInfo[]>\n{\n const config = getCmsConfig();\n const locales = config.locales;\n\n return locales\n .map(locale => getLocaleInfo(locale))\n .filter((info): info is LocaleInfo => info !== undefined);\n}","/**\n * CMS Configuration Module\n *\n * 환경변수 기반 CMS 설정 관리\n * - SPFN_CMS_DEFAULT_LOCALE: 기본 언어 (기본값: 'ko')\n * - SPFN_CMS_SUPPORTED_LOCALES: 지원 언어 목록, 쉼표로 구분 (기본값: 'ko,en')\n * - SPFN_CMS_DETECT_BROWSER_LANGUAGE: 브라우저 언어 자동 감지 (기본값: 'false')\n */\n\n/**\n * CMS 설정 타입\n */\nexport interface CmsConfig\n{\n /**\n * 기본 언어 코드\n * @example 'ko', 'en', 'ja'\n */\n defaultLocale: string;\n\n /**\n * 프로젝트에서 사용할 언어 목록\n * @example ['ko', 'en', 'ja']\n */\n locales: string[];\n\n /**\n * @deprecated Use 'locales' instead\n * @internal For backward compatibility\n */\n supportedLocales: string[];\n\n /**\n * 브라우저 언어 자동 감지 여부\n * @default true\n */\n detectBrowserLanguage: boolean;\n}\n\n/**\n * 환경변수 읽기 헬퍼\n */\nfunction getEnvVar(key: string, defaultValue: string): string\n{\n return process.env[key] || defaultValue;\n}\n\n/**\n * 환경변수에서 boolean 읽기\n */\nfunction getEnvBoolean(key: string, defaultValue: boolean): boolean\n{\n const value = process.env[key];\n if (value === undefined) return defaultValue;\n return value === 'true' || value === '1';\n}\n\n/**\n * 환경변수에서 설정 로드\n */\nfunction loadConfigFromEnv(): CmsConfig\n{\n const defaultLocale = getEnvVar('SPFN_CMS_DEFAULT_LOCALE', 'en');\n const supportedLocalesStr = getEnvVar('SPFN_CMS_SUPPORTED_LOCALES', 'en,ko');\n const detectBrowserLanguage = getEnvBoolean('SPFN_CMS_DETECT_BROWSER_LANGUAGE', true);\n\n const locales = supportedLocalesStr\n .split(',')\n .map(locale => locale.trim())\n .filter(locale => locale.length > 0);\n\n // 기본 언어가 지원 목록에 없으면 추가\n if (!locales.includes(defaultLocale))\n {\n locales.unshift(defaultLocale);\n }\n\n return {\n defaultLocale,\n locales,\n supportedLocales: locales, // backward compatibility\n detectBrowserLanguage,\n };\n}\n\n/**\n * 현재 설정 (환경변수에서 초기화)\n */\nlet currentConfig: CmsConfig = loadConfigFromEnv();\n\n/**\n * CMS 설정 조회\n *\n * @returns 현재 CMS 설정\n *\n * @example\n * ```tsx\n * import { getCmsConfig } from '@spfn/cms';\n *\n * const config = getCmsConfig();\n * console.log(config.defaultLocale); // 'en'\n * console.log(config.locales); // ['en', 'ko']\n * ```\n */\nexport function getCmsConfig(): Readonly<CmsConfig>\n{\n return currentConfig;\n}\n\n/**\n * CMS 설정 변경 (런타임 오버라이드)\n *\n * 환경변수 설정을 런타임에 오버라이드합니다.\n * 주로 테스트나 특수한 경우에 사용됩니다.\n *\n * @param config - 변경할 설정 (부분 업데이트 가능)\n *\n * @example\n * ```tsx\n * import { configureCms } from '@spfn/cms';\n *\n * // 앱 초기화 시 (선택적)\n * configureCms({\n * defaultLocale: 'en',\n * locales: ['en', 'ko', 'ja'],\n * detectBrowserLanguage: true,\n * });\n * ```\n */\nexport function configureCms(config: Partial<CmsConfig>): void\n{\n // Backward compatibility: supportedLocales → locales\n if (config.supportedLocales && !config.locales)\n {\n config = { ...config, locales: config.supportedLocales };\n }\n\n currentConfig = {\n ...currentConfig,\n ...config,\n };\n\n // Sync locales ↔ supportedLocales\n if (config.locales)\n {\n currentConfig.supportedLocales = config.locales;\n }\n else if (config.supportedLocales)\n {\n currentConfig.locales = config.supportedLocales;\n }\n\n // 기본 언어가 지원 목록에 있는지 확인\n if (config.defaultLocale && !currentConfig.locales.includes(config.defaultLocale))\n {\n console.warn(\n `[CMS Config] Default locale '${config.defaultLocale}' not in locales, adding automatically.`,\n `Locales: [${currentConfig.locales.join(', ')}]`\n );\n\n currentConfig.locales.unshift(config.defaultLocale);\n currentConfig.supportedLocales.unshift(config.defaultLocale);\n }\n}\n\n/**\n * 설정 초기화 (환경변수에서 재로드)\n *\n * @example\n * ```tsx\n * import { resetCmsConfig } from '@spfn/cms';\n *\n * // 환경변수 설정으로 되돌리기\n * resetCmsConfig();\n * ```\n */\nexport function resetCmsConfig(): void\n{\n currentConfig = loadConfigFromEnv();\n}","/**\n * Locale Constants\n *\n * Server/Client 양쪽에서 사용 가능한 locale 관련 상수\n */\n\n/**\n * Locale 쿠키 키\n */\nexport const LOCALE_COOKIE_KEY = 'spfn-locale';\n\n/**\n * 지원하는 Locale 타입 (Type-safe)\n */\nexport type SupportedLocale =\n // 아시아-태평양\n | 'ko' // 한국어\n | 'ja' // 일본어\n | 'zh' // 중국어 (간체)\n | 'zh-TW' // 중국어 (번체, 대만)\n | 'zh-HK' // 중국어 (홍콩)\n | 'hi' // 힌디어\n | 'th' // 태국어\n | 'vi' // 베트남어\n | 'id' // 인도네시아어\n | 'ms' // 말레이어\n // 영어권\n | 'en' // 영어 (미국)\n | 'en-GB' // 영어 (영국)\n | 'en-CA' // 영어 (캐나다)\n | 'en-AU' // 영어 (호주)\n | 'en-NZ' // 영어 (뉴질랜드)\n // 서유럽\n | 'es' // 스페인어 (스페인)\n | 'es-MX' // 스페인어 (멕시코)\n | 'es-AR' // 스페인어 (아르헨티나)\n | 'es-CO' // 스페인어 (콜롬비아)\n | 'fr' // 프랑스어\n | 'de' // 독일어\n | 'it' // 이탈리아어\n | 'pt' // 포르투갈어\n | 'nl' // 네덜란드어\n // 북유럽\n | 'sv' // 스웨덴어\n | 'no' // 노르웨이어\n | 'da' // 덴마크어\n | 'fi' // 핀란드어\n // 동유럽\n | 'ru' // 러시아어\n | 'pl' // 폴란드어\n | 'uk' // 우크라이나어\n | 'cs' // 체코어\n | 'hu' // 헝가리어\n | 'ro' // 루마니아어\n | 'bg' // 불가리아어\n | 'hr' // 크로아티아어\n | 'sr' // 세르비아어\n | 'sk' // 슬로바키아어\n | 'sl' // 슬로베니아어\n | 'lt' // 리투아니아어\n | 'lv' // 라트비아어\n | 'et' // 에스토니아어\n // 남유럽\n | 'el' // 그리스어\n // 중동\n | 'tr' // 터키어\n | 'ar' // 아랍어\n | 'fa' // 페르시아어\n | 'he' // 히브리어\n // 아프리카\n | 'sw'; // 스와힐리어\n\n/**\n * 국가/지역 정보 타입\n */\nexport interface LocaleInfo\n{\n /** Locale 코드 (ISO 639-1) */\n locale: SupportedLocale;\n /** 국가 코드 (ISO 3166-1 alpha-2) */\n countryCode: string;\n /** 국기 이모지 (HTML/React용) */\n flag: string;\n /** 전화번호 국가 코드 */\n dialCode: string;\n /** 네이티브 이름 (현지어) */\n nativeName: string;\n /** 영어 이름 */\n englishName: string;\n /** RTL (Right-to-Left) 여부 */\n rtl?: boolean;\n /** 통화 코드 (ISO 4217) */\n currencyCode?: string;\n /** 날짜 형식 예시 */\n dateFormat?: string;\n}\n\n/**\n * 사전 정의된 Locale 정보 맵\n *\n * 주요 언어/국가 정보를 포함합니다.\n * 프로젝트에 맞게 추가/수정 가능합니다.\n */\nexport const LOCALE_INFO_MAP: Record<SupportedLocale, LocaleInfo> = {\n // 한국어\n ko: {\n locale: 'ko',\n countryCode: 'KR',\n flag: '🇰🇷',\n dialCode: '+82',\n nativeName: '한국어',\n englishName: 'Korean',\n currencyCode: 'KRW',\n dateFormat: 'YYYY.MM.DD',\n },\n\n // 영어 (미국)\n en: {\n locale: 'en',\n countryCode: 'US',\n flag: '🇺🇸',\n dialCode: '+1',\n nativeName: 'English',\n englishName: 'English',\n currencyCode: 'USD',\n dateFormat: 'MM/DD/YYYY',\n },\n\n // 일본어\n ja: {\n locale: 'ja',\n countryCode: 'JP',\n flag: '🇯🇵',\n dialCode: '+81',\n nativeName: '日本語',\n englishName: 'Japanese',\n currencyCode: 'JPY',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 중국어 (간체)\n zh: {\n locale: 'zh',\n countryCode: 'CN',\n flag: '🇨🇳',\n dialCode: '+86',\n nativeName: '简体中文',\n englishName: 'Chinese (Simplified)',\n currencyCode: 'CNY',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 중국어 (번체, 대만)\n 'zh-TW': {\n locale: 'zh-TW',\n countryCode: 'TW',\n flag: '🇹🇼',\n dialCode: '+886',\n nativeName: '繁體中文',\n englishName: 'Chinese (Traditional)',\n currencyCode: 'TWD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 스페인어\n es: {\n locale: 'es',\n countryCode: 'ES',\n flag: '🇪🇸',\n dialCode: '+34',\n nativeName: 'Español',\n englishName: 'Spanish',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 프랑스어\n fr: {\n locale: 'fr',\n countryCode: 'FR',\n flag: '🇫🇷',\n dialCode: '+33',\n nativeName: 'Français',\n englishName: 'French',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 독일어\n de: {\n locale: 'de',\n countryCode: 'DE',\n flag: '🇩🇪',\n dialCode: '+49',\n nativeName: 'Deutsch',\n englishName: 'German',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 이탈리아어\n it: {\n locale: 'it',\n countryCode: 'IT',\n flag: '🇮🇹',\n dialCode: '+39',\n nativeName: 'Italiano',\n englishName: 'Italian',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 포르투갈어 (브라질)\n pt: {\n locale: 'pt',\n countryCode: 'BR',\n flag: '🇧🇷',\n dialCode: '+55',\n nativeName: 'Português',\n englishName: 'Portuguese',\n currencyCode: 'BRL',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 러시아어\n ru: {\n locale: 'ru',\n countryCode: 'RU',\n flag: '🇷🇺',\n dialCode: '+7',\n nativeName: 'Русский',\n englishName: 'Russian',\n currencyCode: 'RUB',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 아랍어\n ar: {\n locale: 'ar',\n countryCode: 'SA',\n flag: '🇸🇦',\n dialCode: '+966',\n nativeName: 'العربية',\n englishName: 'Arabic',\n rtl: true,\n currencyCode: 'SAR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 힌디어\n hi: {\n locale: 'hi',\n countryCode: 'IN',\n flag: '🇮🇳',\n dialCode: '+91',\n nativeName: 'हिन्दी',\n englishName: 'Hindi',\n currencyCode: 'INR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 태국어\n th: {\n locale: 'th',\n countryCode: 'TH',\n flag: '🇹🇭',\n dialCode: '+66',\n nativeName: 'ไทย',\n englishName: 'Thai',\n currencyCode: 'THB',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 베트남어\n vi: {\n locale: 'vi',\n countryCode: 'VN',\n flag: '🇻🇳',\n dialCode: '+84',\n nativeName: 'Tiếng Việt',\n englishName: 'Vietnamese',\n currencyCode: 'VND',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 인도네시아어\n id: {\n locale: 'id',\n countryCode: 'ID',\n flag: '🇮🇩',\n dialCode: '+62',\n nativeName: 'Bahasa Indonesia',\n englishName: 'Indonesian',\n currencyCode: 'IDR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 터키어\n tr: {\n locale: 'tr',\n countryCode: 'TR',\n flag: '🇹🇷',\n dialCode: '+90',\n nativeName: 'Türkçe',\n englishName: 'Turkish',\n currencyCode: 'TRY',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 폴란드어\n pl: {\n locale: 'pl',\n countryCode: 'PL',\n flag: '🇵🇱',\n dialCode: '+48',\n nativeName: 'Polski',\n englishName: 'Polish',\n currencyCode: 'PLN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 네덜란드어\n nl: {\n locale: 'nl',\n countryCode: 'NL',\n flag: '🇳🇱',\n dialCode: '+31',\n nativeName: 'Nederlands',\n englishName: 'Dutch',\n currencyCode: 'EUR',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 중국어 (홍콩)\n 'zh-HK': {\n locale: 'zh-HK',\n countryCode: 'HK',\n flag: '🇭🇰',\n dialCode: '+852',\n nativeName: '繁體中文 (香港)',\n englishName: 'Chinese (Hong Kong)',\n currencyCode: 'HKD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 말레이어\n ms: {\n locale: 'ms',\n countryCode: 'MY',\n flag: '🇲🇾',\n dialCode: '+60',\n nativeName: 'Bahasa Melayu',\n englishName: 'Malay',\n currencyCode: 'MYR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (영국)\n 'en-GB': {\n locale: 'en-GB',\n countryCode: 'GB',\n flag: '🇬🇧',\n dialCode: '+44',\n nativeName: 'English (UK)',\n englishName: 'English (United Kingdom)',\n currencyCode: 'GBP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (캐나다)\n 'en-CA': {\n locale: 'en-CA',\n countryCode: 'CA',\n flag: '🇨🇦',\n dialCode: '+1',\n nativeName: 'English (Canada)',\n englishName: 'English (Canada)',\n currencyCode: 'CAD',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 영어 (호주)\n 'en-AU': {\n locale: 'en-AU',\n countryCode: 'AU',\n flag: '🇦🇺',\n dialCode: '+61',\n nativeName: 'English (Australia)',\n englishName: 'English (Australia)',\n currencyCode: 'AUD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (뉴질랜드)\n 'en-NZ': {\n locale: 'en-NZ',\n countryCode: 'NZ',\n flag: '🇳🇿',\n dialCode: '+64',\n nativeName: 'English (New Zealand)',\n englishName: 'English (New Zealand)',\n currencyCode: 'NZD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (멕시코)\n 'es-MX': {\n locale: 'es-MX',\n countryCode: 'MX',\n flag: '🇲🇽',\n dialCode: '+52',\n nativeName: 'Español (México)',\n englishName: 'Spanish (Mexico)',\n currencyCode: 'MXN',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (아르헨티나)\n 'es-AR': {\n locale: 'es-AR',\n countryCode: 'AR',\n flag: '🇦🇷',\n dialCode: '+54',\n nativeName: 'Español (Argentina)',\n englishName: 'Spanish (Argentina)',\n currencyCode: 'ARS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (콜롬비아)\n 'es-CO': {\n locale: 'es-CO',\n countryCode: 'CO',\n flag: '🇨🇴',\n dialCode: '+57',\n nativeName: 'Español (Colombia)',\n englishName: 'Spanish (Colombia)',\n currencyCode: 'COP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스웨덴어\n sv: {\n locale: 'sv',\n countryCode: 'SE',\n flag: '🇸🇪',\n dialCode: '+46',\n nativeName: 'Svenska',\n englishName: 'Swedish',\n currencyCode: 'SEK',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 노르웨이어\n no: {\n locale: 'no',\n countryCode: 'NO',\n flag: '🇳🇴',\n dialCode: '+47',\n nativeName: 'Norsk',\n englishName: 'Norwegian',\n currencyCode: 'NOK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 덴마크어\n da: {\n locale: 'da',\n countryCode: 'DK',\n flag: '🇩🇰',\n dialCode: '+45',\n nativeName: 'Dansk',\n englishName: 'Danish',\n currencyCode: 'DKK',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 핀란드어\n fi: {\n locale: 'fi',\n countryCode: 'FI',\n flag: '🇫🇮',\n dialCode: '+358',\n nativeName: 'Suomi',\n englishName: 'Finnish',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 우크라이나어\n uk: {\n locale: 'uk',\n countryCode: 'UA',\n flag: '🇺🇦',\n dialCode: '+380',\n nativeName: 'Українська',\n englishName: 'Ukrainian',\n currencyCode: 'UAH',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 체코어\n cs: {\n locale: 'cs',\n countryCode: 'CZ',\n flag: '🇨🇿',\n dialCode: '+420',\n nativeName: 'Čeština',\n englishName: 'Czech',\n currencyCode: 'CZK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 헝가리어\n hu: {\n locale: 'hu',\n countryCode: 'HU',\n flag: '🇭🇺',\n dialCode: '+36',\n nativeName: 'Magyar',\n englishName: 'Hungarian',\n currencyCode: 'HUF',\n dateFormat: 'YYYY.MM.DD.',\n },\n\n // 루마니아어\n ro: {\n locale: 'ro',\n countryCode: 'RO',\n flag: '🇷🇴',\n dialCode: '+40',\n nativeName: 'Română',\n englishName: 'Romanian',\n currencyCode: 'RON',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 불가리아어\n bg: {\n locale: 'bg',\n countryCode: 'BG',\n flag: '🇧🇬',\n dialCode: '+359',\n nativeName: 'Български',\n englishName: 'Bulgarian',\n currencyCode: 'BGN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 크로아티아어\n hr: {\n locale: 'hr',\n countryCode: 'HR',\n flag: '🇭🇷',\n dialCode: '+385',\n nativeName: 'Hrvatski',\n englishName: 'Croatian',\n currencyCode: 'HRK',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 세르비아어\n sr: {\n locale: 'sr',\n countryCode: 'RS',\n flag: '🇷🇸',\n dialCode: '+381',\n nativeName: 'Српски',\n englishName: 'Serbian',\n currencyCode: 'RSD',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 슬로바키아어\n sk: {\n locale: 'sk',\n countryCode: 'SK',\n flag: '🇸🇰',\n dialCode: '+421',\n nativeName: 'Slovenčina',\n englishName: 'Slovak',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 슬로베니아어\n sl: {\n locale: 'sl',\n countryCode: 'SI',\n flag: '🇸🇮',\n dialCode: '+386',\n nativeName: 'Slovenščina',\n englishName: 'Slovenian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 리투아니아어\n lt: {\n locale: 'lt',\n countryCode: 'LT',\n flag: '🇱🇹',\n dialCode: '+370',\n nativeName: 'Lietuvių',\n englishName: 'Lithuanian',\n currencyCode: 'EUR',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 라트비아어\n lv: {\n locale: 'lv',\n countryCode: 'LV',\n flag: '🇱🇻',\n dialCode: '+371',\n nativeName: 'Latviešu',\n englishName: 'Latvian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 에스토니아어\n et: {\n locale: 'et',\n countryCode: 'EE',\n flag: '🇪🇪',\n dialCode: '+372',\n nativeName: 'Eesti',\n englishName: 'Estonian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 그리스어\n el: {\n locale: 'el',\n countryCode: 'GR',\n flag: '🇬🇷',\n dialCode: '+30',\n nativeName: 'Ελληνικά',\n englishName: 'Greek',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 페르시아어\n fa: {\n locale: 'fa',\n countryCode: 'IR',\n flag: '🇮🇷',\n dialCode: '+98',\n nativeName: 'فارسی',\n englishName: 'Persian',\n rtl: true,\n currencyCode: 'IRR',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 히브리어\n he: {\n locale: 'he',\n countryCode: 'IL',\n flag: '🇮🇱',\n dialCode: '+972',\n nativeName: 'עברית',\n englishName: 'Hebrew',\n rtl: true,\n currencyCode: 'ILS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스와힐리어\n sw: {\n locale: 'sw',\n countryCode: 'KE',\n flag: '🇰🇪',\n dialCode: '+254',\n nativeName: 'Kiswahili',\n englishName: 'Swahili',\n currencyCode: 'KES',\n dateFormat: 'DD/MM/YYYY',\n },\n};\n\n/**\n * Locale 정보 가져오기\n *\n * @param locale - Locale 코드 (예: 'ko', 'en', 'ja')\n * @returns LocaleInfo 또는 undefined\n *\n * @example\n * ```typescript\n * const koInfo = getLocaleInfo('ko');\n * console.log(koInfo.flag); // 🇰🇷\n * console.log(koInfo.dialCode); // +82\n * ```\n */\nexport function getLocaleInfo(locale: string): LocaleInfo | undefined\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale];\n}\n\n/**\n * 시스템이 지원 가능한 모든 Locale 목록 가져오기\n *\n * @returns Locale 코드 배열 (50+ locales available)\n */\nexport function getAllLocales(): SupportedLocale[]\n{\n return Object.keys(LOCALE_INFO_MAP) as SupportedLocale[];\n}\n\n/**\n * @deprecated Use getAllLocales() instead\n */\nexport function getSupportedLocales(): SupportedLocale[]\n{\n return getAllLocales();\n}\n\n/**\n * 국기 이모지만 가져오기\n *\n * @param locale - Locale 코드\n * @returns 국기 이모지 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getFlag('ko'); // 🇰🇷\n * getFlag('en'); // 🇺🇸\n * ```\n */\nexport function getFlag(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.flag ?? '';\n}\n\n/**\n * 전화번호 국가 코드 가져오기\n *\n * @param locale - Locale 코드\n * @returns 전화번호 코드 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getDialCode('ko'); // +82\n * getDialCode('en'); // +1\n * ```\n */\nexport function getDialCode(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.dialCode ?? '';\n}\n\n/**\n * RTL (Right-to-Left) 여부 확인\n *\n * @param locale - Locale 코드\n * @returns RTL 여부\n *\n * @example\n * ```typescript\n * isRTL('ar'); // true (Arabic)\n * isRTL('ko'); // false (Korean)\n * ```\n */\nexport function isRTL(locale: string): boolean\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.rtl ?? false;\n}","/**\n * CMS Sync Utilities\n *\n * JSON 파일 기반 라벨 동기화\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { basename, extname, join } from 'path';\nimport { extractLabels } from '@/server/labels';\nimport { cmsLabelsRepository, cmsPublishedCacheRepository } from '@/server/repositories';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport type { NestedLabels, SectionDefinition, SyncOptions, SyncResult } from '@/lib/types';\n\n/**\n * 여러 섹션 동기화\n */\nexport async function syncAll(\n sections: SectionDefinition[],\n options: SyncOptions = {}\n): Promise<SyncResult[]>\n{\n const results: SyncResult[] = [];\n\n for (const definition of sections)\n {\n const result = await syncSection(definition, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * JSON 파일에서 라벨 로드\n */\nexport function loadLabelsFromJson(labelsDir: string): SectionDefinition[]\n{\n const sections: SectionDefinition[] = [];\n\n if (!existsSync(labelsDir))\n {\n console.warn(`[CMS] Labels directory not found: ${labelsDir}`);\n console.warn(`[CMS] Expected directory structure:`);\n console.warn(`[CMS] ${labelsDir}/`);\n console.warn(`[CMS] ├── common/ # Section directory`);\n console.warn(`[CMS] │ ├── messages.json`);\n console.warn(`[CMS] │ └── errors.json`);\n console.warn(`[CMS] └── home/ # Section directory`);\n console.warn(`[CMS] └── hero.json`);\n return sections;\n }\n\n try\n {\n const entries = readdirSync(labelsDir);\n\n if (entries.length === 0)\n {\n console.warn(`[CMS] Labels directory is empty: ${labelsDir}`);\n console.warn(`[CMS] Create section directories with JSON files inside`);\n return sections;\n }\n\n const jsonFiles = entries.filter(e => extname(e) === '.json');\n if (jsonFiles.length > 0)\n {\n console.warn(`[CMS] Found JSON files directly in ${labelsDir}:`);\n jsonFiles.forEach(f => console.warn(`[CMS] - ${f} (will be ignored)`));\n console.warn(`[CMS] JSON files should be inside section directories`);\n console.warn(`[CMS] Example: Move ${jsonFiles[0]} to ${labelsDir}/${basename(jsonFiles[0], '.json')}/${jsonFiles[0]}`);\n }\n\n for (const entry of entries)\n {\n const sectionPath = join(labelsDir, entry);\n const stat = statSync(sectionPath);\n\n if (stat.isDirectory())\n {\n const sectionName = entry;\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length > 0)\n {\n sections.push({ section: sectionName, labels });\n }\n else\n {\n console.warn(`[CMS] Section directory \"${sectionName}\" has no valid JSON files`);\n }\n }\n }\n\n if (sections.length === 0)\n {\n console.warn(`[CMS] No valid section directories found in ${labelsDir}`);\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not scan labels directory: ${labelsDir}`);\n console.error(`[CMS] Error:`, error);\n }\n\n return sections;\n}\n\nfunction loadSectionLabels(sectionPath: string): NestedLabels\n{\n const labels: NestedLabels = {};\n\n try\n {\n const files = readdirSync(sectionPath);\n\n for (const file of files)\n {\n if (extname(file) === '.json')\n {\n const filePath = join(sectionPath, file);\n const categoryName = basename(file, '.json');\n\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n labels[categoryName] = JSON.parse(content);\n }\n catch (error)\n {\n console.warn(`[CMS] Failed to parse ${filePath}`);\n }\n }\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not read section directory: ${sectionPath}`);\n }\n\n return labels;\n}\n\n/**\n * 섹션 라벨 동기화\n */\nexport async function syncSection(\n definition: SectionDefinition,\n options: SyncOptions = {}\n): Promise<SyncResult>\n{\n const {\n dryRun = false,\n updateExisting = false,\n removeUnused = false,\n verbose = false,\n } = options;\n\n const { section } = definition;\n const result: SyncResult = {\n section,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: 0,\n errors: [],\n };\n\n try\n {\n const definedLabels = extractLabels(definition);\n const definedKeys = new Set(definedLabels.map((l) => l.key));\n const existingLabels = await cmsLabelsRepository.findBySection(section);\n const existingMap = new Map(existingLabels.map((l) => [l.key, l]));\n\n if (verbose)\n {\n console.log(`\\n[${section}] Found ${definedLabels.length} labels in definition`);\n console.log(`[${section}] Found ${existingLabels.length} labels in DB`);\n }\n\n // 생성 및 업데이트\n for (const label of definedLabels)\n {\n const existing = existingMap.get(label.key);\n\n if (!existing)\n {\n if (verbose) console.log(` [CREATE] ${label.key}`);\n\n if (!dryRun)\n {\n try\n {\n const defaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n await cmsLabelsRepository.create({\n section,\n key: label.key,\n type: label.type || 'text', // 라벨 타입 (기본값: 'text')\n defaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.created++;\n }\n else if (updateExisting)\n {\n const newDefaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n const newType = label.type || 'text';\n const hasChanged = existing.defaultValue !== newDefaultValue || existing.type !== newType;\n\n if (hasChanged)\n {\n if (verbose)\n {\n console.log(` [UPDATE] ${label.key}`);\n console.log(` Old: \"${existing.defaultValue}\"`);\n console.log(` New: \"${newDefaultValue}\"`);\n }\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.updateById(existing.id, {\n type: label.type || 'text',\n defaultValue: newDefaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.updated++;\n }\n else\n {\n result.unchanged++;\n }\n }\n else\n {\n result.unchanged++;\n }\n }\n\n // 사용되지 않는 라벨 삭제\n if (removeUnused)\n {\n for (const existing of existingLabels)\n {\n if (!definedKeys.has(existing.key))\n {\n if (verbose) console.log(` [DELETE] ${existing.key}`);\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.deleteById(existing.id);\n }\n catch (error)\n {\n result.errors.push({\n key: existing.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.deleted++;\n }\n }\n }\n\n // Published cache 업데이트\n if (!dryRun && (result.created > 0 || result.updated > 0 || result.deleted > 0))\n {\n if (verbose) console.log(` [CACHE] Updating published cache for section: ${section}`);\n await updatePublishedCache(section);\n }\n }\n catch (error)\n {\n result.errors.push({\n key: '__section__',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n return result;\n}\n\n/**\n * Published Cache 업데이트\n */\nasync function updatePublishedCache(section: string): Promise<void>\n{\n const labels = await cmsLabelsRepository.findBySection(section);\n const localesSet = new Set<string>();\n const labelsByLocale: Record<string, Record<string, any>> = {};\n const singleValueLabels: Array<{ key: string; value: any }> = [];\n\n // First pass: 다국어 객체 처리 및 사용 중인 locale 수집\n labels.forEach((label) =>\n {\n try\n {\n const parsed = JSON.parse(label.defaultValue || '{}');\n\n if (typeof parsed === 'object' && !Array.isArray(parsed))\n {\n // Multilingual object\n Object.keys(parsed).forEach((locale) => localesSet.add(locale));\n Object.entries(parsed).forEach(([locale, value]) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][label.key] = value;\n });\n }\n else\n {\n // Single value (will be distributed to all locales in second pass)\n singleValueLabels.push({ key: label.key, value: label.defaultValue });\n }\n }\n catch\n {\n // Plain string (will be distributed to all locales in second pass)\n singleValueLabels.push({ key: label.key, value: label.defaultValue });\n }\n });\n\n // 최소 기본 locale 보장 (ko, en)\n if (localesSet.size === 0)\n {\n localesSet.add('ko');\n localesSet.add('en');\n }\n\n // Second pass: 단일 값을 모든 locale에 복사\n singleValueLabels.forEach(({ key, value }) =>\n {\n localesSet.forEach((locale) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][key] = value;\n });\n });\n\n const timestamp = new Date();\n for (const locale of localesSet)\n {\n await cmsPublishedCacheRepository.upsert({\n section,\n locale,\n content: labelsByLocale[locale] || {},\n publishedAt: timestamp,\n publishedBy: 'system',\n });\n }\n}\n\n/**\n * Initialize label sync for server startup\n *\n * Call this in your server.config.ts beforeRoutes hook\n *\n * @param options - Sync options\n * @param options.labelsDir - Path to labels directory (default: 'src/lib/labels')\n *\n * @example\n * ```typescript\n * import { initLabelSync } from '@spfn/cms';\n *\n * export default {\n * beforeRoutes: async (app) => {\n * await initLabelSync({ verbose: true });\n * },\n * } satisfies ServerConfig;\n * ```\n */\nexport async function initLabelSync(options: SyncOptions & { labelsDir?: string } = {}): Promise<void>\n{\n const isDevelopment = process.env.NODE_ENV === 'development';\n const verbose = options.verbose ?? isDevelopment;\n const labelsDir = options.labelsDir ?? DEFAULT_LABELS_DIR;\n\n if (verbose)\n {\n console.log('\\n🔄 Initializing label sync...\\n');\n }\n\n // Load labels from JSON files\n const sections = loadLabelsFromJson(labelsDir);\n\n if (sections.length === 0)\n {\n if (verbose)\n {\n console.log('⚠️ No labels found in', labelsDir);\n console.log('');\n }\n return;\n }\n\n const results = await syncAll(sections, {\n updateExisting: true, // 🔄 항상 업데이트 (프로덕션 포함)\n ...options,\n verbose,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalUnchanged = results.reduce((sum, r) => sum + r.unchanged, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (verbose)\n {\n console.log('✅ Label sync completed\\n');\n console.log(` Sections: ${results.length}`);\n console.log(` Created: ${totalCreated}`);\n console.log(` Updated: ${totalUpdated}`);\n console.log(` Unchanged: ${totalUnchanged}`);\n\n if (totalErrors > 0)\n {\n console.log(` Errors: ${totalErrors}\\n`);\n }\n else\n {\n console.log('');\n }\n }\n\n // Log errors\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n console.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n}","/**\n * CMS Label Helpers\n *\n * Utilities for processing label definitions from JSON files\n */\n\nimport type { SectionDefinition, NestedLabels, FlatLabel, LabelDefinition } from '@/lib/types';\n\n/**\n * 중첩된 라벨을 플랫화\n *\n * @param labels - 중첩된 라벨 객체\n * @returns 플랫화된 라벨 배열\n *\n * @example\n * ```ts\n * flattenLabels({\n * nav: {\n * home: { key: 'layout.nav.home', defaultValue: 'Home' },\n * },\n * });\n * // => [{ key: 'layout.nav.home', defaultValue: 'Home' }]\n * ```\n */\nexport function flattenLabels(labels: NestedLabels): FlatLabel[]\n{\n const result: FlatLabel[] = [];\n\n function isLabelDefinition(obj: NestedLabels | LabelDefinition): obj is LabelDefinition\n {\n return (\n 'key' in obj &&\n 'defaultValue' in obj &&\n typeof obj.key === 'string' &&\n (typeof obj.defaultValue === 'string' || typeof obj.defaultValue === 'object')\n );\n }\n\n function traverse(obj: NestedLabels | LabelDefinition)\n {\n if (isLabelDefinition(obj))\n {\n // LabelDefinition인 경우\n result.push({\n key: obj.key,\n type: obj.type,\n defaultValue: obj.defaultValue,\n description: obj.description,\n });\n }\n else\n {\n // NestedLabels인 경우\n Object.values(obj).forEach((value) =>\n {\n if (typeof value === 'object' && value !== null)\n {\n traverse(value);\n }\n });\n }\n }\n\n traverse(labels);\n return result;\n}\n\n/**\n * 섹션 정의에서 모든 라벨 추출\n *\n * @param definition - 섹션 정의\n * @returns 플랫화된 라벨 배열\n */\nexport function extractLabels(definition: SectionDefinition): FlatLabel[]\n{\n return flattenLabels(definition.labels);\n}\n","/**\n * CMS Labels Repository\n *\n * 라벨 메타데이터 관리를 위한 Repository\n */\n\nimport { findOne, findMany as findManyHelper, create as createHelper, updateOne, deleteOne, count as countHelper } from '@spfn/core/db';\nimport { asc } from 'drizzle-orm';\nimport { cmsLabels, type CmsLabel, type NewCmsLabel } from '@/server/entities';\n\n/**\n * 라벨 목록 조회\n */\nexport async function findMany(options?: {\n section?: string;\n}): Promise<CmsLabel[]>\n{\n const { section } = options || {};\n\n return findManyHelper(cmsLabels, {\n where: section ? { section } : undefined,\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 전체 라벨 수 조회\n */\nexport async function count(section?: string): Promise<number>\n{\n return countHelper(cmsLabels, section ? { section } : undefined);\n}\n\n/**\n * ID로 라벨 조회\n */\nexport async function findById(id: number): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { id });\n}\n\n/**\n * Key로 라벨 조회\n */\nexport async function findByKey(key: string): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { key });\n}\n\n/**\n * 섹션으로 모든 라벨 조회\n */\nexport async function findBySection(section: string): Promise<CmsLabel[]>\n{\n return findManyHelper(cmsLabels, {\n where: { section },\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 라벨 생성\n */\nexport async function create(data: NewCmsLabel): Promise<CmsLabel>\n{\n return createHelper(cmsLabels, data);\n}\n\n/**\n * 라벨 수정\n */\nexport async function updateById(id: number, data: Partial<NewCmsLabel>): Promise<CmsLabel | null>\n{\n return updateOne(cmsLabels, { id }, { ...data, updatedAt: new Date() });\n}\n\n/**\n * 라벨 삭제\n */\nexport async function deleteById(id: number): Promise<CmsLabel | null>\n{\n return deleteOne(cmsLabels, { id });\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelsRepository = {\n findMany,\n count,\n findById,\n findByKey,\n findBySection,\n create,\n updateById,\n deleteById\n};","/**\n * CMS Labels Entity\n *\n * 라벨의 메타데이터와 현재 발행 상태를 관리합니다.\n * - 라벨 식별 (id, key)\n * - 섹션 분류 (section)\n * - 타입 정의 (type)\n * - 발행 상태 (publishedVersion)\n */\n\nimport { index, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\nexport const cmsLabels = cmsSchema.table('labels', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 라벨 식별자\n key: text('key').notNull().unique(),\n // 예: \"home.hero.title\", \"why-futureplay.hero.subtitle\"\n // 구조: {section}.{component}.{property}\n\n // 섹션 분류 (페이지 단위)\n section: text('section').notNull(),\n // 예: \"home\", \"why-futureplay\", \"team\"\n\n // 값 타입\n type: text('type').notNull(),\n // \"text\" | \"image\" | \"video\" | \"file\" | \"object\"\n\n // 기본값\n defaultValue: text('default_value'),\n // 라벨의 기본값 (sync 시 설정)\n\n // 설명\n description: text('description'),\n // 라벨에 대한 설명 (optional)\n\n // 현재 발행된 버전 번호\n publishedVersion: integer('published_version'),\n // null = 미발행 상태\n // 1, 2, 3... = 발행된 버전 번호\n\n // 생성자 추적\n createdBy: text('created_by'),\n\n // 타임스탬프\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: 섹션별 조회 최적화\n index('cms_labels_section_idx').on(table.section),\n\n // 인덱스: key로 조회 최적화 (unique 제약으로 자동 생성되지만 명시)\n index('cms_labels_key_idx').on(table.key),\n]);\n\n// 타입 추론\nexport type CmsLabel = typeof cmsLabels.$inferSelect;\nexport type NewCmsLabel = typeof cmsLabels.$inferInsert;","/**\n * CMS Schema Definition\n *\n * Creates isolated 'spfn_cms' PostgreSQL schema for CMS tables.\n * Export this schema so drizzle-kit can generate CREATE SCHEMA statement.\n */\nimport { createFunctionSchema } from '@spfn/core/db';\n\nexport const cmsSchema = createFunctionSchema('@spfn/cms');","/**\n * CMS Label Values Entity\n *\n * 라벨의 실제 값을 저장합니다.\n * - 다국어 지원 (locale)\n * - 반응형 지원 (breakpoint)\n * - 버전 관리 (version)\n * - JSONB로 유연한 값 저장\n */\n\nimport { serial, integer, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsLabelValues = cmsSchema.table('label_values', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels\n labelId: integer('label_id')\n .notNull()\n .references(() => cmsLabels.id, { onDelete: 'cascade' }),\n\n // 버전 번호 (null = draft, number = published version)\n version: integer('version'),\n\n // 언어 코드\n locale: text('locale').notNull().default('ko'),\n // \"ko\" | \"en\" | \"ja\"\n\n // 반응형 브레이크포인트\n breakpoint: text('breakpoint'),\n // null = 기본값 (모든 화면 크기)\n // \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\"\n\n // 실제 값 (JSONB)\n value: jsonb('value').notNull(),\n // LabelValue 타입:\n // - TextValue: { type: \"text\", content: string }\n // - ImageValue: { type: \"image\", url: string, alt?: string, width?: number, height?: number }\n // - VideoValue: { type: \"video\", url: string, thumbnail?: string, duration?: number }\n // - FileValue: { type: \"file\", url: string, filename: string, size?: number }\n // - ObjectValue: { type: \"object\", fields: Record<string, LabelValue> }\n\n // 생성 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: 같은 버전에서 locale + breakpoint 조합은 유일\n unique('cms_label_values_locale_breakpoint_unique')\n .on(table.labelId, table.version, table.locale, table.breakpoint),\n\n // 인덱스: labelId + version 복합 조회 최적화\n index('cms_label_values_label_version_idx')\n .on(table.labelId, table.version),\n\n // 인덱스: locale 필터링 최적화\n index('cms_label_values_locale_idx').on(table.locale),\n]);\n\n// 타입 추론\nexport type CmsLabelValue = typeof cmsLabelValues.$inferSelect;\nexport type NewCmsLabelValue = typeof cmsLabelValues.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 텍스트 값 저장\n * await db.insert(cmsLabelValues).values({\n * labelId: 1,\n * version: 1,\n * locale: 'ko',\n * breakpoint: null,\n * value: {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * }\n * });\n *\n * // 반응형 이미지 저장 (모바일용)\n * await db.insert(cmsLabelValues).values({\n * labelId: 2,\n * version: 1,\n * locale: 'ko',\n * breakpoint: 'sm',\n * value: {\n * type: 'image',\n * url: '/uploads/hero-mobile.jpg',\n * alt: 'Hero Image',\n * width: 640,\n * height: 480\n * }\n * });\n *\n * // 특정 버전의 한국어 값 조회\n * const values = await db.select()\n * .from(cmsLabelValues)\n * .where(and(\n * eq(cmsLabelValues.labelId, 1),\n * eq(cmsLabelValues.version, 2),\n * eq(cmsLabelValues.locale, 'ko')\n * ));\n *\n * // Object 타입 값 저장 (재귀 구조)\n * await db.insert(cmsLabelValues).values({\n * labelId: 3,\n * version: 1,\n * locale: 'ko',\n * value: {\n * type: 'object',\n * fields: {\n * title: { type: 'text', content: '특징 1' },\n * icon: { type: 'image', url: '/icons/feature1.svg', alt: 'Icon' },\n * description: { type: 'text', content: '상세 설명...' }\n * }\n * }\n * });\n */","/**\n * CMS Draft Cache Entity\n *\n * 관리자별 Draft 콘텐츠를 캐싱합니다.\n * - 사용자별 격리 (userId)\n * - 실시간 미리보기 지원\n * - 동시 편집 가능\n *\n * 핵심 기능:\n * - 여러 관리자가 같은 섹션을 동시에 편집\n * - 각자의 변경사항은 자신의 미리보기에만 표시\n * - 충돌 없이 안전하게 작업\n */\n\nimport { serial, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsDraftCache = cmsSchema.table('draft_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 사용자 ID (핵심 필드!)\n userId: text('user_id').notNull(),\n // 각 관리자의 독립적인 작업 공간\n\n // Draft 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"수정 중...\" },\n // \"home.hero.subtitle\": { type: \"text\", content: \"새로운 문구\" },\n // ...\n // }\n\n // 최종 수정 시각\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: section + locale + userId 조합은 유일\n unique('cms_draft_cache_unique')\n .on(table.section, table.locale, table.userId),\n\n // 인덱스: section으로 조회 최적화\n index('cms_draft_cache_section_idx').on(table.section),\n\n // 인덱스: userId로 사용자의 모든 draft 조회 최적화\n index('cms_draft_cache_user_idx').on(table.userId),\n]);\n\n// 타입 추론\nexport type CmsDraftCache = typeof cmsDraftCache.$inferSelect;\nexport type NewCmsDraftCache = typeof cmsDraftCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // Draft 초기화 (편집 시작)\n * await db.insert(cmsDraftCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * userId: 'user-a@futureplay.com',\n * content: publishedContent // 발행 버전 복사\n * });\n *\n * // Draft 업데이트 (값 수정 시)\n * const cache = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ))\n * .limit(1);\n *\n * const updatedContent = {\n * ...cache[0].content,\n * 'home.hero.title': newValue // 부분 업데이트\n * };\n *\n * await db.update(cmsDraftCache)\n * .set({ content: updatedContent, updatedAt: new Date() })\n * .where(eq(cmsDraftCache.id, cache[0].id));\n *\n * // Draft 조회 (미리보기)\n * const draft = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, session.user.id)\n * ))\n * .limit(1);\n *\n * // 사용자의 모든 작업 중인 섹션 조회\n * const userDrafts = await db.select()\n * .from(cmsDraftCache)\n * .where(eq(cmsDraftCache.userId, userId))\n * .orderBy(desc(cmsDraftCache.updatedAt));\n *\n * // 오래된 Draft 정리 (30일 이상)\n * const stale = await db.delete(cmsDraftCache)\n * .where(lt(\n * cmsDraftCache.updatedAt,\n * new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)\n * ))\n * .returning();\n *\n * // Draft 폐기 (변경사항 버리기)\n * await db.delete(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ));\n */","/**\n * CMS Published Cache Entity\n *\n * 발행된 콘텐츠를 섹션+언어 단위로 캐싱합니다.\n * - 초고속 읽기 성능 (5ms)\n * - 단일 쿼리로 섹션 전체 로드\n * - JSONB로 즉시 사용 가능한 데이터\n *\n * 성능 비교:\n * - 정규화 테이블 JOIN: 87ms\n * - 캐시 테이블: 5ms (17배 빠름!)\n */\n\nimport { serial, text, jsonb, integer, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsPublishedCache = cmsSchema.table('published_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 캐시된 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"...\" },\n // \"home.hero.image\": { type: \"image\", url: \"...\", alt: \"...\" },\n // ...\n // }\n\n // 발행 정보\n publishedAt: timestamp('published_at', { withTimezone: true }).notNull(),\n publishedBy: text('published_by'),\n\n // 캐시 버전 (클라이언트 캐싱용)\n version: integer('version').notNull().default(1),\n}, (table) => [\n // UNIQUE 제약: section + locale 조합은 유일\n unique('cms_published_cache_unique').on(table.section, table.locale),\n\n // 인덱스: section으로 조회 최적화\n index('cms_published_cache_section_idx').on(table.section),\n]);\n\n// 타입 추론\nexport type CmsPublishedCache = typeof cmsPublishedCache.$inferSelect;\nexport type NewCmsPublishedCache = typeof cmsPublishedCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 캐시 생성/업데이트 (UPSERT)\n * await db.insert(cmsPublishedCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * content: {\n * 'home.hero.title': {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * },\n * 'home.hero.image': {\n * type: 'image',\n * url: '/uploads/hero.jpg',\n * alt: 'Hero',\n * width: 1920,\n * height: 1080\n * }\n * },\n * publishedAt: new Date(),\n * publishedBy: 'admin@futureplay.com'\n * })\n * .onConflictDoUpdate({\n * target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n * set: {\n * content: sql`EXCLUDED.content`,\n * publishedAt: sql`EXCLUDED.published_at`,\n * publishedBy: sql`EXCLUDED.published_by`,\n * version: sql`${cmsPublishedCache.version} + 1`\n * }\n * });\n *\n * // 캐시 조회 (초고속!)\n * const cache = await db.select()\n * .from(cmsPublishedCache)\n * .where(and(\n * eq(cmsPublishedCache.section, 'home'),\n * eq(cmsPublishedCache.locale, 'ko')\n * ))\n * .limit(1);\n *\n * const labels = cache[0].content; // 즉시 사용 가능!\n *\n * // 섹션의 모든 언어 캐시 조회\n * const allLocales = await db.select()\n * .from(cmsPublishedCache)\n * .where(eq(cmsPublishedCache.section, 'home'));\n *\n * // 오래된 캐시 감지\n * const stale = await db.select()\n * .from(cmsPublishedCache)\n * .where(lt(\n * cmsPublishedCache.publishedAt,\n * new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)\n * ));\n */","/**\n * CMS Audit Logs Entity\n *\n * CMS의 모든 변경사항을 추적합니다.\n * - 누가 (userId, userName)\n * - 언제 (createdAt)\n * - 무엇을 (action, changes)\n * - 왜 (metadata)\n */\n\nimport { serial, integer, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsAuditLogs = cmsSchema.table('audit_logs', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels (nullable - 라벨 삭제 시 로그는 유지)\n labelId: integer('label_id')\n .references(() => cmsLabels.id, { onDelete: 'set null' }),\n\n // 작업 유형\n action: text('action').notNull(),\n // \"create\" | \"update\" | \"publish\" | \"unpublish\" | \"archive\" | \"delete\" | \"rollback\" | \"duplicate\"\n\n // 사용자 정보\n userId: text('user_id').notNull(),\n userName: text('user_name'),\n\n // 변경 내용 (before/after)\n changes: jsonb('changes'),\n // { before: {...}, after: {...} }\n\n // 추가 메타데이터\n metadata: jsonb('metadata'),\n // { version: number, ip: string, userAgent: string, ... }\n\n // 작업 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: labelId로 이력 조회 최적화\n index('cms_audit_logs_label_id_idx').on(table.labelId),\n\n // 인덱스: userId로 사용자 활동 조회 최적화\n index('cms_audit_logs_user_id_idx').on(table.userId),\n\n // 인덱스: action 필터링 최적화\n index('cms_audit_logs_action_idx').on(table.action),\n\n // 인덱스: 시간순 조회 최적화\n index('cms_audit_logs_created_at_idx').on(table.createdAt),\n]);\n\n// 타입 추론\nexport type CmsAuditLog = typeof cmsAuditLogs.$inferSelect;\nexport type NewCmsAuditLog = typeof cmsAuditLogs.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 라벨 생성 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'create',\n * userId: 'user123',\n * userName: '김철수',\n * changes: {\n * before: null,\n * after: {\n * key: 'home.hero.title',\n * section: 'home',\n * type: 'text'\n * }\n * },\n * metadata: {\n * ip: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...'\n * }\n * });\n *\n * // 발행 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'publish',\n * userId: 'admin123',\n * userName: '관리자',\n * changes: {\n * before: { status: 'draft', publishedVersion: null },\n * after: { status: 'published', publishedVersion: 2 }\n * },\n * metadata: {\n * version: 2,\n * notes: '신규 브랜딩 적용'\n * }\n * });\n *\n * // 라벨별 이력 조회\n * const logs = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.labelId, 1))\n * .orderBy(desc(cmsAuditLogs.createdAt))\n * .limit(20);\n *\n * // 사용자별 활동 조회\n * const userActivity = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.userId, 'user123'))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n *\n * // 최근 24시간 변경 이력\n * const recent = await db.select()\n * .from(cmsAuditLogs)\n * .where(gte(cmsAuditLogs.createdAt, new Date(Date.now() - 24 * 60 * 60 * 1000)))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n */","/**\n * CMS Label Values Repository\n *\n * 라벨 값 관리를 위한 Repository\n */\n\nimport { findOne, findMany, create, updateOne, deleteMany } from '@spfn/core/db';\nimport { eq, and, SQL, isNull } from 'drizzle-orm';\nimport { cmsLabelValues, type CmsLabelValue, type NewCmsLabelValue } from '@/server/entities';\n\n/**\n * 특정 라벨의 특정 버전 값들 조회\n */\nexport async function findByLabelIdAndVersion(\n labelId: number,\n version: number,\n options?: {\n locale?: string;\n breakpoint?: string | null;\n }\n): Promise<CmsLabelValue[]>\n{\n const { locale, breakpoint } = options || {};\n\n const conditions: SQL[] = [\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n ];\n\n if (locale)\n {\n conditions.push(eq(cmsLabelValues.locale, locale));\n }\n\n if (breakpoint !== undefined)\n {\n conditions.push(\n breakpoint === null\n ? isNull(cmsLabelValues.breakpoint)\n : eq(cmsLabelValues.breakpoint, breakpoint)\n );\n }\n\n return findMany(cmsLabelValues, {\n where: and(...conditions)\n });\n}\n\n/**\n * 값 저장 (upsert)\n * - version: null → Draft 저장 (덮어쓰기)\n * - version: number → Published 버전 생성 (불변)\n */\nexport async function upsert(data: NewCmsLabelValue): Promise<CmsLabelValue>\n{\n // 기존 값이 있는지 확인\n const versionCondition = data.version === null || data.version === undefined\n ? isNull(cmsLabelValues.version)\n : eq(cmsLabelValues.version, data.version as number);\n\n const existing = await findOne(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, data.labelId),\n versionCondition,\n eq(cmsLabelValues.locale, data.locale || 'ko'),\n data.breakpoint\n ? eq(cmsLabelValues.breakpoint, data.breakpoint)\n : isNull(cmsLabelValues.breakpoint)\n )\n );\n\n if (existing)\n {\n // UPDATE (only for drafts with version: null)\n if (data.version === null || data.version === undefined)\n {\n const updated = await updateOne(\n cmsLabelValues,\n { id: existing.id },\n { value: data.value }\n );\n return updated!;\n }\n else\n {\n // Published versions are immutable - this shouldn't happen\n throw new Error(`Published version ${data.version} already exists and cannot be overwritten`);\n }\n }\n else\n {\n // INSERT (both draft and new published versions)\n return create(cmsLabelValues, data);\n }\n}\n\n/**\n * Draft 값들 조회 (version = null)\n */\nexport async function findDraftsByLabelId(labelId: number): Promise<CmsLabelValue[]>\n{\n return findMany(cmsLabelValues, {\n where: and(\n eq(cmsLabelValues.labelId, labelId),\n isNull(cmsLabelValues.version)\n )\n });\n}\n\n/**\n * 여러 값 일괄 저장\n */\nexport async function upsertMany(values: NewCmsLabelValue[]): Promise<CmsLabelValue[]>\n{\n const results = [];\n for (const value of values)\n {\n const result = await upsert(value);\n results.push(result);\n }\n return results;\n}\n\n/**\n * 특정 버전의 모든 값 삭제\n */\nexport async function deleteByVersion(labelId: number, version: number): Promise<CmsLabelValue[]>\n{\n return deleteMany(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n )\n );\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelValuesRepository = {\n findByLabelIdAndVersion,\n findDraftsByLabelId,\n upsert,\n upsertMany,\n deleteByVersion\n};","/**\n * CMS Draft Cache Repository\n *\n * 관리자별 초안 캐시 관리 (동시 편집 지원)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, lt } from 'drizzle-orm';\nimport { cmsDraftCache, type NewCmsDraftCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어 + 사용자로 초안 캐시 조회\n */\nexport async function findByUser(section: string, locale: string, userId: string)\n{\n return findOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 초안 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsDraftCache)\n{\n return upsertHelper(cmsDraftCache, data, {\n target: [cmsDraftCache.section, cmsDraftCache.locale, cmsDraftCache.userId],\n set: {\n content: data.content,\n updatedAt: new Date(),\n }\n });\n}\n\n/**\n * 특정 사용자의 모든 초안 조회\n */\nexport async function findAllByUser(userId: string)\n{\n return findMany(cmsDraftCache, {\n where: eq(cmsDraftCache.userId, userId)\n });\n}\n\n/**\n * 초안 삭제\n */\nexport async function deleteByUser(section: string, locale: string, userId: string)\n{\n await deleteOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 오래된 초안 정리 (30일 이상 미사용)\n */\nexport async function cleanupOldDrafts(daysOld: number = 30)\n{\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - daysOld);\n\n return deleteMany(\n cmsDraftCache,\n lt(cmsDraftCache.updatedAt, cutoffDate)\n );\n}\n\nexport const cmsDraftCacheRepository = {\n findByUser,\n upsert,\n findAllByUser,\n deleteByUser,\n cleanupOldDrafts,\n};","/**\n * CMS Published Cache Repository\n *\n * 발행된 콘텐츠 캐시 관리 (초고속 조회)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, sql } from 'drizzle-orm';\nimport { cmsPublishedCache, type NewCmsPublishedCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어로 발행된 캐시 조회\n */\nexport async function findBySection(section: string, locale: string = 'ko')\n{\n return findOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n}\n\n/**\n * 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsPublishedCache)\n{\n return upsertHelper(cmsPublishedCache, data, {\n target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n set: {\n content: data.content,\n publishedAt: data.publishedAt,\n publishedBy: data.publishedBy,\n version: sql`${cmsPublishedCache.version} + 1`, // 버전 증가로 클라이언트 캐시 무효화\n }\n });\n}\n\n/**\n * 섹션별 모든 언어 캐시 조회\n */\nexport async function findAllLanguages(section: string)\n{\n return findMany(cmsPublishedCache, {\n where: eq(cmsPublishedCache.section, section)\n });\n}\n\n/**\n * 캐시 삭제\n */\nexport async function deleteBySection(section: string, locale?: string)\n{\n if (locale)\n {\n await deleteOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n }\n else\n {\n await deleteMany(\n cmsPublishedCache,\n eq(cmsPublishedCache.section, section)\n );\n }\n}\n\nexport const cmsPublishedCacheRepository = {\n findBySection,\n upsert,\n findAllLanguages,\n deleteBySection,\n};","/**\n * CMS Constants\n *\n * CMS 패키지에서 사용하는 전역 상수\n */\n\n/**\n * 기본 라벨 디렉토리 경로\n *\n * JSON 라벨 파일이 저장되는 기본 디렉토리입니다.\n * 프로젝트 루트 기준 상대 경로입니다.\n *\n * @example\n * ```typescript\n * import { DEFAULT_LABELS_DIR } from '@spfn/cms';\n *\n * console.log(DEFAULT_LABELS_DIR); // 'src/lib/labels'\n * ```\n */\nexport const DEFAULT_LABELS_DIR = 'src/lib/labels';","/**\n * Label Sync Generator\n *\n * File-based label sync with JSON definitions\n *\n * Structure:\n * lib/labels/\n * layout/ # Section name\n * nav.json # Label definitions\n * footer.json\n * homepage/\n * hero.json\n *\n * Features:\n * - Incremental updates: syncs only changed section on file change\n * - Full sync on file add/delete or manual trigger\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { Generator, GeneratorOptions, GeneratorTrigger } from '@spfn/core/codegen';\nimport { join, relative, extname } from 'path';\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\n\nimport { syncAll, syncSection, loadLabelsFromJson } from '@/server/helpers/sync';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\n\nconst syncLogger = logger.child('label-sync');\n\nexport interface LabelSyncGeneratorConfig\n{\n labelsDir?: string;\n runOn?: GeneratorTrigger[];\n}\n\n/**\n * Create label sync generator\n *\n * Supports incremental updates when single files change\n */\nexport function createLabelSyncGenerator(config: LabelSyncGeneratorConfig = {}): Generator\n{\n const labelsDir = config.labelsDir ?? DEFAULT_LABELS_DIR;\n const runOn = config.runOn ?? ['watch', 'manual', 'build'];\n\n return {\n name: 'label-sync',\n watchPatterns: [`${labelsDir}/**/*.json`],\n runOn,\n\n async generate(options: GeneratorOptions): Promise<void>\n {\n const labelsPath = join(options.cwd, labelsDir);\n\n // Check if labels directory exists\n if (!existsSync(labelsPath))\n {\n if (options.debug)\n {\n syncLogger.warn(`Labels directory not found: ${labelsPath}`);\n }\n return;\n }\n\n try\n {\n // Check for incremental update opportunity\n const changedFile = options.trigger?.changedFile;\n\n if (changedFile && changedFile.event === 'change')\n {\n // Try incremental update for changed files\n const success = await attemptIncrementalSync({\n cwd: options.cwd,\n labelsPath,\n changedFilePath: changedFile.path,\n debug: options.debug\n });\n\n if (success)\n {\n if (options.debug)\n {\n syncLogger.info('Incremental sync successful');\n }\n return;\n }\n\n if (options.debug)\n {\n syncLogger.info('Incremental sync failed, doing full sync');\n }\n }\n\n // Full sync\n if (options.debug)\n {\n syncLogger.info('Starting full label sync...');\n }\n\n const sections = loadLabelsFromJson(labelsPath);\n\n if (sections.length === 0)\n {\n syncLogger.warn(`No labels found in ${labelsPath}`);\n return;\n }\n\n syncLogger.info(`Found ${sections.length} sections`);\n\n // Sync all sections\n const results = await syncAll(sections, {\n verbose: options.debug ?? false,\n updateExisting: true,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (options.debug || totalCreated > 0 || totalUpdated > 0)\n {\n syncLogger.info('Label sync completed', {\n sections: results.length,\n created: totalCreated,\n updated: totalUpdated,\n errors: totalErrors,\n });\n }\n\n // Log errors if any\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n syncLogger.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.error('Label sync failed', err);\n throw err;\n }\n }\n };\n}\n\n/**\n * Options for incremental sync\n */\ninterface IncrementalSyncOptions\n{\n cwd: string;\n labelsPath: string;\n changedFilePath: string;\n debug?: boolean;\n}\n\n/**\n * Attempt incremental sync for a changed file\n *\n * Strategy:\n * 1. Extract section name from file path (e.g., lib/labels/layout/nav.json -> 'layout')\n * 2. Load only that section's labels\n * 3. Sync only that section\n *\n * Returns true if successful, false if full sync is needed\n */\nasync function attemptIncrementalSync(options: IncrementalSyncOptions): Promise<boolean>\n{\n const { cwd, labelsPath, changedFilePath, debug } = options;\n\n try\n {\n const fullPath = join(cwd, changedFilePath);\n\n if (!existsSync(fullPath))\n {\n // File deleted during watch, need full sync\n return false;\n }\n\n // Extract section name from path\n // Example: lib/labels/layout/nav.json\n // ^^^^^^^^^^ labelsDir ^^^^^^ section ^^^^^^^^ file\n const relativePath = relative(labelsPath, fullPath);\n const parts = relativePath.split('/');\n\n if (parts.length < 2)\n {\n // File is directly in labels dir, not in section directory\n return false;\n }\n\n const sectionName = parts[0];\n\n if (debug)\n {\n syncLogger.info('Attempting incremental sync', {\n section: sectionName,\n file: changedFilePath\n });\n }\n\n // Load all labels from this section\n const sectionPath = join(labelsPath, sectionName);\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length === 0)\n {\n if (debug)\n {\n syncLogger.warn('Section has no valid labels');\n }\n return false;\n }\n\n // Sync only this section\n const result = await syncSection(\n { section: sectionName, labels },\n { verbose: debug, updateExisting: true }\n );\n\n if (debug || result.created > 0 || result.updated > 0)\n {\n syncLogger.info(`[${sectionName}] Incremental sync completed`, {\n created: result.created,\n updated: result.updated,\n unchanged: result.unchanged,\n errors: result.errors.length\n });\n }\n\n // Log errors if any\n if (result.errors.length > 0)\n {\n result.errors.forEach((error) =>\n {\n syncLogger.error(`[${sectionName}] ${error.key}: ${error.error}`);\n });\n }\n\n return true;\n }\n catch (error)\n {\n if (debug)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn('Incremental sync failed', err);\n }\n return false;\n }\n}\n\n/**\n * Load labels from a section directory\n *\n * Extracted from loadLabelsFromJson for reuse\n */\nfunction loadSectionLabels(sectionPath: string): Record<string, any>\n{\n const labels: Record<string, any> = {};\n\n if (!existsSync(sectionPath))\n {\n return labels;\n }\n\n try\n {\n const entries = readdirSync(sectionPath);\n\n for (const entry of entries)\n {\n const filePath = join(sectionPath, entry);\n const stat = statSync(filePath);\n\n if (stat.isFile() && extname(entry) === '.json')\n {\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n const data = JSON.parse(content);\n\n // Merge labels from this file\n if (typeof data === 'object' && data !== null)\n {\n Object.assign(labels, data);\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn(`Failed to parse ${filePath}`, err);\n }\n }\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn(`Failed to read section ${sectionPath}`, err);\n }\n\n return labels;\n}"],"mappings":"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","names":["detectBrowserLanguage","serial","integer","text","timestamp","index","serial","integer","text","timestamp","index","serial","text","jsonb","timestamp","index","unique","serial","text","jsonb","timestamp","unique","index","serial","text","jsonb","integer","timestamp","index","unique","serial","text","jsonb","timestamp","integer","unique","index","serial","integer","text","jsonb","timestamp","index","serial","integer","text","jsonb","timestamp","index","findOne","findMany","create","updateOne","findMany","findOne","updateOne","create","findOne","findMany","deleteOne","deleteMany","eq","and","findOne","and","eq","upsert","findMany","deleteOne","deleteMany","findOne","findMany","deleteOne","deleteMany","upsertHelper","eq","and","findBySection","findOne","and","eq","upsert","upsertHelper","findMany","deleteOne","deleteMany","timestamp","join","extname","existsSync","readdirSync","readFileSync","statSync","join","existsSync","loadSectionLabels","readdirSync","statSync","extname","readFileSync","text","sectionData","t","sectionsMap"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/lib/contracts/published-cache.ts","../src/server/helpers/locale.actions.ts","../src/server/config/cms.config.ts","../src/lib/constants/locale.constants.ts","../src/server/helpers/sync.ts","../src/server/labels/helpers.ts","../src/server/repositories/cms-labels.repository.ts","../src/server/entities/cms-labels.ts","../src/server/entities/cms-schema.ts","../src/server/entities/cms-label-values.ts","../src/server/entities/cms-draft-cache.ts","../src/server/entities/cms-published-cache.ts","../src/server/entities/cms-audit-logs.ts","../src/server/repositories/cms-label-values.repository.ts","../src/server/repositories/cms-draft-cache.repository.ts","../src/server/repositories/cms-published-cache.repository.ts","../src/lib/constants/index.ts","../src/server/generators/label-sync-generator.ts"],"sourcesContent":["/**\n * @spfn/cms/server\n *\n * Server-side Only Module\n * 서버 전용 모듈 (서버 컴포넌트 + 백엔드)\n *\n * Includes:\n * - Server Components (getSection, getSections)\n * - Locale Management (Server Actions)\n * - Backend: Sync utilities\n * - Backend: Repositories\n * - Backend: Entities\n * - Backend: Label helpers\n * - Backend: Codegen generators\n *\n * @note This module should only be imported in server-side code\n */\n\nimport { cache } from 'react';\nimport { client } from '@spfn/core/client';\nimport { getPublishedCacheContract } from '@/lib/contracts/published-cache';\nimport { getLocale } from '@/server/helpers/locale.actions';\n\n/**\n * Section Data Type\n */\nexport type SectionData = {\n section: string;\n locale: string;\n content: Record<string, any>;\n version: number;\n publishedAt: string | null;\n};\n\n/**\n * Translation Function Type (runtime version)\n */\ntype ServerTranslationFunction = (\n key: string,\n defaultValue?: any,\n replace?: Record<string, string | number>\n) => any;\n\n/**\n * Section API Return Type\n */\nexport type SectionAPI = {\n /**\n * 라벨 값 가져오기 (변수 치환 지원)\n *\n * @param key - 라벨 키 (섹션 제외, 예: 'hero.title')\n * @param defaultValue - 기본값\n * @param replace - 변수 치환 맵 (예: { name: 'John' })\n * @returns 라벨 값 (문자열인 경우 변수 치환됨)\n */\n t: ServerTranslationFunction;\n\n /**\n * 섹션 데이터\n */\n data: SectionData;\n};\n\n/**\n * 변수 치환 헬퍼\n *\n * @param text - 치환할 텍스트 (예: 'Hello {name}!')\n * @param replace - 치환 맵 (예: { name: 'World' })\n * @returns 치환된 텍스트 (예: 'Hello World!')\n */\nfunction replaceVariables(text: string, replace: Record<string, string | number>): string\n{\n return text.replace(/\\{(\\w+)}/g, (match, key) =>\n {\n const value = replace[key];\n return value !== undefined ? String(value) : match;\n });\n}\n\n/**\n * 섹션 데이터 로드 (React cache 적용)\n *\n * 동일한 요청 내에서 같은 섹션을 여러 번 요청해도 한 번만 API 호출\n *\n * @param section - 섹션 이름 (예: 'home', 'why-futureplay')\n * @param locale - 언어 코드 (선택, 미지정시 쿠키에서 자동 조회)\n * @returns Section API ({ t, data })\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getSection } from '@spfn/cms/server';\n *\n * export default async function HomePage()\n * {\n * // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴\n * const { t } = await getSection('home');\n *\n * // 또는 명시적으로 locale 지정\n * const { t: tEn } = await getSection('home', 'en');\n *\n * return (\n * <div>\n * <h1>{t('hero.title')}</h1>\n * <p>{t('hero.subtitle', 'Default Subtitle')}</p>\n * <p>{t('hero.greeting', 'Hello {name}!', { name: 'World' })}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport const getSection = cache(async (\n section: string,\n locale?: string\n): Promise<SectionAPI> =>\n{\n // locale이 지정되지 않으면 쿠키에서 가져옴\n const actualLocale: string = locale ?? await getLocale();\n\n try\n {\n // Call SPFN API via contract (uses singleton client)\n const response = await client.call(\n getPublishedCacheContract,\n {\n query: { sections: section, locale: actualLocale },\n fetchOptions: { next: { revalidate: 60 } } // 60초마다 자동 갱신\n }\n );\n\n // Check if response has error\n if ('error' in response)\n {\n console.warn(`[CMS] ${response.error}`);\n // Return empty section data\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n\n // Response is an array, get first element\n const found = response[0];\n\n if (!found)\n {\n // Section not found, return empty\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n\n // Success response\n const sectionData: SectionData = {\n section: found.section,\n locale: found.locale,\n content: found.content || {},\n version: found.version,\n publishedAt: found.publishedAt,\n };\n\n // Translation function\n const t: ServerTranslationFunction = (key, defaultValue, replace) =>\n {\n const fullKey = `${section}.${key}`;\n let value = sectionData.content[fullKey];\n\n if (value === undefined || value === null)\n {\n value = defaultValue ?? '';\n }\n\n // text 타입 객체이면 content 필드 추출\n if (typeof value === 'object' && value !== null && value.type === 'text' && 'content' in value)\n {\n value = value.content;\n }\n\n // 문자열인 경우 변수 치환 처리\n if (typeof value === 'string')\n {\n if (replace)\n {\n value = replaceVariables(value, replace);\n }\n return value;\n }\n\n // 문자열이 아니면 원본 값 반환 (객체 타입: image, video, file, object 등)\n return value;\n };\n\n return {\n t,\n data: sectionData,\n };\n }\n catch (error)\n {\n console.error(`[CMS] Failed to fetch section \"${section}\":`, error);\n\n // Return empty section data on error\n const sectionData: SectionData = {\n section,\n locale: actualLocale,\n content: {} as Record<string, any>,\n version: 0,\n publishedAt: null,\n };\n\n const t: ServerTranslationFunction = (_key, defaultValue) => defaultValue ?? '';\n return { t, data: sectionData };\n }\n});\n\n/**\n * 여러 섹션 한번에 로드 (React cache 적용)\n * 단일 API 호출로 여러 섹션을 효율적으로 가져옵니다\n *\n * @param sections - 섹션 이름 배열\n * @param locale - 언어 코드 (선택, 미지정시 쿠키에서 자동 조회)\n * @returns Section API 맵 ({ home: { t, data }, ... })\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getSections } from '@spfn/cms/server';\n *\n * export default async function Page()\n * {\n * // locale을 지정하지 않으면 쿠키에서 자동으로 가져옴\n * const sections = await getSections(['home', 'why-futureplay']);\n *\n * // 또는 명시적으로 locale 지정\n * const sectionsEn = await getSections(['home', 'why-futureplay'], 'en');\n *\n * return (\n * <div>\n * <h1>{sections.home.t('hero.title')}</h1>\n * <p>{sections['why-futureplay'].t('intro.text')}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport const getSections = cache(async (\n sections: string[],\n locale?: string\n): Promise<Record<string, SectionAPI>> =>\n{\n // locale이 지정되지 않으면 쿠키에서 가져옴\n const actualLocale: string = locale ?? await getLocale();\n\n try\n {\n // Call SPFN API with array of sections (single HTTP request)\n const response = await client.call(\n getPublishedCacheContract,\n {\n query: { sections, locale: actualLocale },\n fetchOptions: { next: { revalidate: 60 } } // 60초마다 자동 갱신\n }\n );\n\n // Check if response has error\n if ('error' in response)\n {\n console.warn(`[CMS] ${response.error}`);\n // Return empty sections\n const sectionsMap: Record<string, SectionAPI> = {};\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n return sectionsMap;\n }\n\n // Build sections map from response\n const sectionsMap: Record<string, SectionAPI> = {};\n\n // First, create empty entries for all requested sections\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n\n // Then, fill in data for found sections\n response.forEach(sectionData =>\n {\n const createTranslationFn = (section: string, content: Record<string, any>): ServerTranslationFunction =>\n {\n return (key, defaultValue, replace) =>\n {\n const fullKey = `${section}.${key}`;\n let value = content[fullKey];\n\n if (value === undefined || value === null)\n {\n value = defaultValue ?? '';\n }\n\n // text 타입 객체이면 content 필드 추출\n if (typeof value === 'object' && value !== null && value.type === 'text' && 'content' in value)\n {\n value = value.content;\n }\n\n // 문자열인 경우 변수 치환 처리\n if (typeof value === 'string')\n {\n if (replace)\n {\n value = replaceVariables(value, replace);\n }\n return value;\n }\n\n // 문자열이 아니면 원본 값 반환 (객체 타입: image, video, file, object 등)\n return value;\n };\n };\n\n sectionsMap[sectionData.section] = {\n t: createTranslationFn(sectionData.section, sectionData.content),\n data: {\n section: sectionData.section,\n locale: sectionData.locale,\n content: sectionData.content,\n version: sectionData.version,\n publishedAt: sectionData.publishedAt,\n }\n };\n });\n\n return sectionsMap;\n }\n catch (error)\n {\n console.error(`[CMS] Failed to fetch sections:`, error);\n\n // Return empty sections on error\n const sectionsMap: Record<string, SectionAPI> = {};\n sections.forEach(section =>\n {\n sectionsMap[section] = {\n t: (_key, defaultValue) => defaultValue ?? '',\n data: {\n section,\n locale: actualLocale,\n content: {},\n version: 0,\n publishedAt: null,\n }\n };\n });\n return sectionsMap;\n }\n});\n\n// ============================================================================\n// Locale Management (Constants Only)\n// ============================================================================\n// Note: Server Actions (getLocale, setLocale, etc.) are exported from actions.ts\n// to avoid bundling \"use server\" directives into server.ts\n\nexport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n getSupportedLocales,\n getFlag,\n getDialCode,\n isRTL,\n LOCALE_INFO_MAP,\n type LocaleInfo,\n type SupportedLocale,\n} from './lib/constants/locale.constants';\n\n// ============================================================================\n// Backend: Sync Utilities (server startup, CLI scripts)\n// ============================================================================\n\nexport { syncSection, syncAll, initLabelSync, loadLabelsFromJson } from './server/helpers/sync';\n\n// ============================================================================\n// Backend: Repositories (DB access)\n// ============================================================================\n\nexport * from './server/repositories/index';\n\n// ============================================================================\n// Backend: Entities (DB schemas)\n// ============================================================================\n\nexport * from './server/entities/index';\n\n// ============================================================================\n// Backend: Label Helpers (for processing JSON labels)\n// ============================================================================\n\nexport * from './server/labels/index';\n\n// ============================================================================\n// Backend: Codegen Generators (for development)\n// ============================================================================\n\nexport { createLabelSyncGenerator } from './server/generators/label-sync-generator';","import { Type } from '@sinclair/typebox';\nimport type { RouteContract } from '@spfn/core/route';\n\nconst SectionData = Type.Object({\n section: Type.String(),\n locale: Type.String(),\n content: Type.Record(Type.String(), Type.Any()),\n version: Type.Number(),\n publishedAt: Type.Union([Type.String(), Type.Null()]),\n});\n\n/**\n * GET /_cms/published-cache\n * 발행된 콘텐츠 캐시 조회 (단일 또는 여러 섹션)\n */\nexport const getPublishedCacheContract = {\n method: 'GET' as const,\n path: '/_cms/published-cache',\n query: Type.Object({\n sections: Type.Union([\n Type.String({ description: '단일 섹션 이름 (예: home)' }),\n Type.Array(Type.String(), { description: '여러 섹션 이름 (예: [\"home\", \"footer\"])' })\n ]),\n locale: Type.Optional(Type.String({ default: 'ko', description: '언어 코드' })),\n }),\n response: Type.Union([\n // 성공: 항상 배열로 반환\n Type.Array(SectionData),\n // 에러\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;\n\n/**\n * POST /_cms/published-cache\n * 발행된 콘텐츠 캐시 업데이트/생성 (upsert)\n */\nexport const upsertPublishedCacheContract = {\n method: 'POST' as const,\n path: '/_cms/published-cache',\n body: Type.Object({\n section: Type.String({ description: '섹션 이름 (예: home)' }),\n locale: Type.String({ description: '언어 코드 (예: ko, en, ja)' }),\n content: Type.Record(Type.String(), Type.Any(), { description: '발행할 콘텐츠 (key-value 형태)' }),\n version: Type.Number({ description: '버전 번호' })\n }),\n response: Type.Union([\n SectionData,\n Type.Object({\n error: Type.String()\n })\n ])\n} as const satisfies RouteContract;","\"use server\";\n\n/**\n * Locale Management Server Actions\n *\n * Server Actions으로 구현된 locale 관리 함수\n * - 서버 컴포넌트: 일반 함수 호출로 동작\n * - 클라이언트 컴포넌트: Server Action으로 자동 처리\n */\n\nimport { cookies, headers } from 'next/headers.js';\nimport { getCmsConfig } from '@/server/config/cms.config';\nimport {\n LOCALE_COOKIE_KEY,\n getLocaleInfo,\n type LocaleInfo,\n} from '@/lib/constants/locale.constants';\n\n/**\n * 브라우저 언어 감지\n *\n * Accept-Language 헤더에서 지원하는 언어를 찾습니다.\n *\n * @returns 감지된 언어 코드 또는 null\n */\nasync function detectBrowserLanguage(): Promise<string | null>\n{\n try\n {\n const headersList = await headers();\n const acceptLanguage = headersList.get('accept-language');\n\n if (!acceptLanguage)\n {\n return null;\n }\n\n // \"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\" 형식 파싱\n const languages = acceptLanguage\n .split(',')\n .map(lang =>\n {\n const [code] = lang.split(';');\n return code.split('-')[0].trim();\n });\n\n const config = getCmsConfig();\n\n // 지원하는 언어 중 첫 번째 매칭되는 언어 반환\n for (const lang of languages)\n {\n if (config.locales.includes(lang))\n {\n return lang;\n }\n }\n\n return null;\n }\n catch (error)\n {\n // 헤더 접근 실패 시\n return null;\n }\n}\n\n/**\n * 현재 locale 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * 우선순위:\n * 1. 쿠키 (사용자가 명시적으로 선택한 언어)\n * 2. 브라우저 언어 감지 (설정에서 활성화된 경우)\n * 3. 시스템 기본 언어 (CMS 설정)\n *\n * @returns 현재 locale (예: 'ko', 'en')\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locale = await getLocale();\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locale, setLocale] = useState('');\n *\n * useEffect(() => {\n * getLocale().then(setLocale);\n * }, []);\n *\n * return <div>Current locale: {locale}</div>;\n * }\n * ```\n */\nexport async function getLocale(): Promise<string>\n{\n const config = getCmsConfig();\n\n // 1순위: 쿠키 (사용자가 명시적으로 선택한 언어)\n const cookieStore = await cookies();\n const cookieLocale = cookieStore.get(LOCALE_COOKIE_KEY)?.value;\n\n if (cookieLocale && config.locales.includes(cookieLocale))\n {\n return cookieLocale;\n }\n\n // 2순위: 브라우저 언어 감지 (설정에서 활성화된 경우)\n if (config.detectBrowserLanguage)\n {\n const browserLang = await detectBrowserLanguage();\n if (browserLang)\n {\n return browserLang;\n }\n }\n\n // 3순위: 시스템 기본 언어\n return config.defaultLocale;\n}\n\n/**\n * Locale 설정하기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n * 쿠키에 locale을 저장합니다.\n *\n * @param locale - 설정할 locale (예: 'ko', 'en')\n * @throws {Error} 지원하지 않는 locale인 경우\n *\n * @example\n * ```tsx\n * // Server Component (Server Action)\n * import { setLocale } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * await setLocale('en');\n * return <div>Locale changed</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component (Server Action)\n * 'use client';\n * import { setLocale } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const handleChange = async (newLocale: string) =>\n * {\n * await setLocale(newLocale);\n * window.location.reload(); // 페이지 새로고침\n * };\n *\n * return (\n * <button onClick={() => handleChange('en')}>\n * Switch to English\n * </button>\n * );\n * }\n * ```\n */\nexport async function setLocale(locale: string): Promise<void>\n{\n const config = getCmsConfig();\n\n // 유효성 검사\n if (!config.locales.includes(locale))\n {\n throw new Error(\n `Unsupported locale: ${locale}. Supported locales: ${config.locales.join(', ')}`\n );\n }\n\n const cookieStore = await cookies();\n\n cookieStore.set(LOCALE_COOKIE_KEY, locale, {\n path: '/',\n maxAge: 60 * 60 * 24 * 365, // 1년\n sameSite: 'lax',\n });\n}\n\n/**\n * 지원하는 locale 목록 가져오기 (Server Action)\n *\n * 서버/클라이언트 컴포넌트 모두에서 사용 가능\n *\n * @returns 지원하는 locale 배열 (예: ['ko', 'en', 'ja'])\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocales } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const locales = await getLocales();\n * return <div>Supported: {locales.join(', ')}</div>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Client Component\n * 'use client';\n * import { getLocales } from '@spfn/cms/client';\n *\n * export default function LanguageSwitcher()\n * {\n * const [locales, setLocales] = useState<string[]>([]);\n *\n * useEffect(() => {\n * getLocales().then(setLocales);\n * }, []);\n *\n * return (\n * <div>\n * {locales.map(locale => (\n * <button key={locale}>{locale}</button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocales(): Promise<string[]>\n{\n const config = getCmsConfig();\n return config.locales;\n}\n\n/**\n * 현재 locale과 상세 정보 함께 가져오기 (Server Action)\n *\n * locale 코드와 함께 국가 코드, 국기, 전화번호 코드 등의 상세 정보를 반환합니다.\n *\n * @returns Locale 코드와 LocaleInfo 객체\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocaleWithInfo } from '@spfn/cms/actions';\n *\n * export default async function Page()\n * {\n * const { locale, info } = await getLocaleWithInfo();\n *\n * return (\n * <div>\n * <span>{info?.flag}</span>\n * <span>{info?.nativeName}</span>\n * <span>{info?.dialCode}</span>\n * </div>\n * );\n * }\n * ```\n */\nexport async function getLocaleWithInfo(): Promise<{\n locale: string;\n info: LocaleInfo | undefined;\n}>\n{\n const locale = await getLocale();\n const info = getLocaleInfo(locale);\n\n return { locale, info };\n}\n\n/**\n * 지원하는 모든 locale과 상세 정보 가져오기 (Server Action)\n *\n * 시스템이 지원하는 모든 locale의 상세 정보를 배열로 반환합니다.\n * 언어 선택 UI를 만들 때 유용합니다.\n *\n * @returns LocaleInfo 배열\n *\n * @example\n * ```tsx\n * // Server Component\n * import { getLocalesWithInfo } from '@spfn/cms/actions';\n *\n * export default async function LanguageSelector()\n * {\n * const locales = await getLocalesWithInfo();\n *\n * return (\n * <select>\n * {locales.map(info => (\n * <option key={info.locale} value={info.locale}>\n * {info.flag} {info.nativeName}\n * </option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport async function getLocalesWithInfo(): Promise<LocaleInfo[]>\n{\n const config = getCmsConfig();\n const locales = config.locales;\n\n return locales\n .map(locale => getLocaleInfo(locale))\n .filter((info): info is LocaleInfo => info !== undefined);\n}","/**\n * CMS Configuration Module\n *\n * 환경변수 기반 CMS 설정 관리\n * - SPFN_CMS_DEFAULT_LOCALE: 기본 언어 (기본값: 'ko')\n * - SPFN_CMS_SUPPORTED_LOCALES: 지원 언어 목록, 쉼표로 구분 (기본값: 'ko,en')\n * - SPFN_CMS_DETECT_BROWSER_LANGUAGE: 브라우저 언어 자동 감지 (기본값: 'false')\n */\n\n/**\n * CMS 설정 타입\n */\nexport interface CmsConfig\n{\n /**\n * 기본 언어 코드\n * @example 'ko', 'en', 'ja'\n */\n defaultLocale: string;\n\n /**\n * 프로젝트에서 사용할 언어 목록\n * @example ['ko', 'en', 'ja']\n */\n locales: string[];\n\n /**\n * @deprecated Use 'locales' instead\n * @internal For backward compatibility\n */\n supportedLocales: string[];\n\n /**\n * 브라우저 언어 자동 감지 여부\n * @default true\n */\n detectBrowserLanguage: boolean;\n}\n\n/**\n * 환경변수 읽기 헬퍼\n */\nfunction getEnvVar(key: string, defaultValue: string): string\n{\n return process.env[key] || defaultValue;\n}\n\n/**\n * 환경변수에서 boolean 읽기\n */\nfunction getEnvBoolean(key: string, defaultValue: boolean): boolean\n{\n const value = process.env[key];\n if (value === undefined) return defaultValue;\n return value === 'true' || value === '1';\n}\n\n/**\n * 환경변수에서 설정 로드\n */\nfunction loadConfigFromEnv(): CmsConfig\n{\n const defaultLocale = getEnvVar('SPFN_CMS_DEFAULT_LOCALE', 'en');\n const supportedLocalesStr = getEnvVar('SPFN_CMS_SUPPORTED_LOCALES', 'en,ko');\n const detectBrowserLanguage = getEnvBoolean('SPFN_CMS_DETECT_BROWSER_LANGUAGE', true);\n\n const locales = supportedLocalesStr\n .split(',')\n .map(locale => locale.trim())\n .filter(locale => locale.length > 0);\n\n // 기본 언어가 지원 목록에 없으면 추가\n if (!locales.includes(defaultLocale))\n {\n locales.unshift(defaultLocale);\n }\n\n return {\n defaultLocale,\n locales,\n supportedLocales: locales, // backward compatibility\n detectBrowserLanguage,\n };\n}\n\n/**\n * 현재 설정 (환경변수에서 초기화)\n */\nlet currentConfig: CmsConfig = loadConfigFromEnv();\n\n/**\n * CMS 설정 조회\n *\n * @returns 현재 CMS 설정\n *\n * @example\n * ```tsx\n * import { getCmsConfig } from '@spfn/cms';\n *\n * const config = getCmsConfig();\n * console.log(config.defaultLocale); // 'en'\n * console.log(config.locales); // ['en', 'ko']\n * ```\n */\nexport function getCmsConfig(): Readonly<CmsConfig>\n{\n return currentConfig;\n}\n\n/**\n * CMS 설정 변경 (런타임 오버라이드)\n *\n * 환경변수 설정을 런타임에 오버라이드합니다.\n * 주로 테스트나 특수한 경우에 사용됩니다.\n *\n * @param config - 변경할 설정 (부분 업데이트 가능)\n *\n * @example\n * ```tsx\n * import { configureCms } from '@spfn/cms';\n *\n * // 앱 초기화 시 (선택적)\n * configureCms({\n * defaultLocale: 'en',\n * locales: ['en', 'ko', 'ja'],\n * detectBrowserLanguage: true,\n * });\n * ```\n */\nexport function configureCms(config: Partial<CmsConfig>): void\n{\n // Backward compatibility: supportedLocales → locales\n if (config.supportedLocales && !config.locales)\n {\n config = { ...config, locales: config.supportedLocales };\n }\n\n currentConfig = {\n ...currentConfig,\n ...config,\n };\n\n // Sync locales ↔ supportedLocales\n if (config.locales)\n {\n currentConfig.supportedLocales = config.locales;\n }\n else if (config.supportedLocales)\n {\n currentConfig.locales = config.supportedLocales;\n }\n\n // 기본 언어가 지원 목록에 있는지 확인\n if (config.defaultLocale && !currentConfig.locales.includes(config.defaultLocale))\n {\n console.warn(\n `[CMS Config] Default locale '${config.defaultLocale}' not in locales, adding automatically.`,\n `Locales: [${currentConfig.locales.join(', ')}]`\n );\n\n currentConfig.locales.unshift(config.defaultLocale);\n currentConfig.supportedLocales.unshift(config.defaultLocale);\n }\n}\n\n/**\n * 설정 초기화 (환경변수에서 재로드)\n *\n * @example\n * ```tsx\n * import { resetCmsConfig } from '@spfn/cms';\n *\n * // 환경변수 설정으로 되돌리기\n * resetCmsConfig();\n * ```\n */\nexport function resetCmsConfig(): void\n{\n currentConfig = loadConfigFromEnv();\n}","/**\n * Locale Constants\n *\n * Server/Client 양쪽에서 사용 가능한 locale 관련 상수\n */\n\n/**\n * Locale 쿠키 키\n */\nexport const LOCALE_COOKIE_KEY = 'spfn-locale';\n\n/**\n * 지원하는 Locale 타입 (Type-safe)\n */\nexport type SupportedLocale =\n // 아시아-태평양\n | 'ko' // 한국어\n | 'ja' // 일본어\n | 'zh' // 중국어 (간체)\n | 'zh-TW' // 중국어 (번체, 대만)\n | 'zh-HK' // 중국어 (홍콩)\n | 'hi' // 힌디어\n | 'th' // 태국어\n | 'vi' // 베트남어\n | 'id' // 인도네시아어\n | 'ms' // 말레이어\n // 영어권\n | 'en' // 영어 (미국)\n | 'en-GB' // 영어 (영국)\n | 'en-CA' // 영어 (캐나다)\n | 'en-AU' // 영어 (호주)\n | 'en-NZ' // 영어 (뉴질랜드)\n // 서유럽\n | 'es' // 스페인어 (스페인)\n | 'es-MX' // 스페인어 (멕시코)\n | 'es-AR' // 스페인어 (아르헨티나)\n | 'es-CO' // 스페인어 (콜롬비아)\n | 'fr' // 프랑스어\n | 'de' // 독일어\n | 'it' // 이탈리아어\n | 'pt' // 포르투갈어\n | 'nl' // 네덜란드어\n // 북유럽\n | 'sv' // 스웨덴어\n | 'no' // 노르웨이어\n | 'da' // 덴마크어\n | 'fi' // 핀란드어\n // 동유럽\n | 'ru' // 러시아어\n | 'pl' // 폴란드어\n | 'uk' // 우크라이나어\n | 'cs' // 체코어\n | 'hu' // 헝가리어\n | 'ro' // 루마니아어\n | 'bg' // 불가리아어\n | 'hr' // 크로아티아어\n | 'sr' // 세르비아어\n | 'sk' // 슬로바키아어\n | 'sl' // 슬로베니아어\n | 'lt' // 리투아니아어\n | 'lv' // 라트비아어\n | 'et' // 에스토니아어\n // 남유럽\n | 'el' // 그리스어\n // 중동\n | 'tr' // 터키어\n | 'ar' // 아랍어\n | 'fa' // 페르시아어\n | 'he' // 히브리어\n // 아프리카\n | 'sw'; // 스와힐리어\n\n/**\n * 국가/지역 정보 타입\n */\nexport interface LocaleInfo\n{\n /** Locale 코드 (ISO 639-1) */\n locale: SupportedLocale;\n /** 국가 코드 (ISO 3166-1 alpha-2) */\n countryCode: string;\n /** 국기 이모지 (HTML/React용) */\n flag: string;\n /** 전화번호 국가 코드 */\n dialCode: string;\n /** 네이티브 이름 (현지어) */\n nativeName: string;\n /** 영어 이름 */\n englishName: string;\n /** RTL (Right-to-Left) 여부 */\n rtl?: boolean;\n /** 통화 코드 (ISO 4217) */\n currencyCode?: string;\n /** 날짜 형식 예시 */\n dateFormat?: string;\n}\n\n/**\n * 사전 정의된 Locale 정보 맵\n *\n * 주요 언어/국가 정보를 포함합니다.\n * 프로젝트에 맞게 추가/수정 가능합니다.\n */\nexport const LOCALE_INFO_MAP: Record<SupportedLocale, LocaleInfo> = {\n // 한국어\n ko: {\n locale: 'ko',\n countryCode: 'KR',\n flag: '🇰🇷',\n dialCode: '+82',\n nativeName: '한국어',\n englishName: 'Korean',\n currencyCode: 'KRW',\n dateFormat: 'YYYY.MM.DD',\n },\n\n // 영어 (미국)\n en: {\n locale: 'en',\n countryCode: 'US',\n flag: '🇺🇸',\n dialCode: '+1',\n nativeName: 'English',\n englishName: 'English',\n currencyCode: 'USD',\n dateFormat: 'MM/DD/YYYY',\n },\n\n // 일본어\n ja: {\n locale: 'ja',\n countryCode: 'JP',\n flag: '🇯🇵',\n dialCode: '+81',\n nativeName: '日本語',\n englishName: 'Japanese',\n currencyCode: 'JPY',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 중국어 (간체)\n zh: {\n locale: 'zh',\n countryCode: 'CN',\n flag: '🇨🇳',\n dialCode: '+86',\n nativeName: '简体中文',\n englishName: 'Chinese (Simplified)',\n currencyCode: 'CNY',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 중국어 (번체, 대만)\n 'zh-TW': {\n locale: 'zh-TW',\n countryCode: 'TW',\n flag: '🇹🇼',\n dialCode: '+886',\n nativeName: '繁體中文',\n englishName: 'Chinese (Traditional)',\n currencyCode: 'TWD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 스페인어\n es: {\n locale: 'es',\n countryCode: 'ES',\n flag: '🇪🇸',\n dialCode: '+34',\n nativeName: 'Español',\n englishName: 'Spanish',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 프랑스어\n fr: {\n locale: 'fr',\n countryCode: 'FR',\n flag: '🇫🇷',\n dialCode: '+33',\n nativeName: 'Français',\n englishName: 'French',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 독일어\n de: {\n locale: 'de',\n countryCode: 'DE',\n flag: '🇩🇪',\n dialCode: '+49',\n nativeName: 'Deutsch',\n englishName: 'German',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 이탈리아어\n it: {\n locale: 'it',\n countryCode: 'IT',\n flag: '🇮🇹',\n dialCode: '+39',\n nativeName: 'Italiano',\n englishName: 'Italian',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 포르투갈어 (브라질)\n pt: {\n locale: 'pt',\n countryCode: 'BR',\n flag: '🇧🇷',\n dialCode: '+55',\n nativeName: 'Português',\n englishName: 'Portuguese',\n currencyCode: 'BRL',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 러시아어\n ru: {\n locale: 'ru',\n countryCode: 'RU',\n flag: '🇷🇺',\n dialCode: '+7',\n nativeName: 'Русский',\n englishName: 'Russian',\n currencyCode: 'RUB',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 아랍어\n ar: {\n locale: 'ar',\n countryCode: 'SA',\n flag: '🇸🇦',\n dialCode: '+966',\n nativeName: 'العربية',\n englishName: 'Arabic',\n rtl: true,\n currencyCode: 'SAR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 힌디어\n hi: {\n locale: 'hi',\n countryCode: 'IN',\n flag: '🇮🇳',\n dialCode: '+91',\n nativeName: 'हिन्दी',\n englishName: 'Hindi',\n currencyCode: 'INR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 태국어\n th: {\n locale: 'th',\n countryCode: 'TH',\n flag: '🇹🇭',\n dialCode: '+66',\n nativeName: 'ไทย',\n englishName: 'Thai',\n currencyCode: 'THB',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 베트남어\n vi: {\n locale: 'vi',\n countryCode: 'VN',\n flag: '🇻🇳',\n dialCode: '+84',\n nativeName: 'Tiếng Việt',\n englishName: 'Vietnamese',\n currencyCode: 'VND',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 인도네시아어\n id: {\n locale: 'id',\n countryCode: 'ID',\n flag: '🇮🇩',\n dialCode: '+62',\n nativeName: 'Bahasa Indonesia',\n englishName: 'Indonesian',\n currencyCode: 'IDR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 터키어\n tr: {\n locale: 'tr',\n countryCode: 'TR',\n flag: '🇹🇷',\n dialCode: '+90',\n nativeName: 'Türkçe',\n englishName: 'Turkish',\n currencyCode: 'TRY',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 폴란드어\n pl: {\n locale: 'pl',\n countryCode: 'PL',\n flag: '🇵🇱',\n dialCode: '+48',\n nativeName: 'Polski',\n englishName: 'Polish',\n currencyCode: 'PLN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 네덜란드어\n nl: {\n locale: 'nl',\n countryCode: 'NL',\n flag: '🇳🇱',\n dialCode: '+31',\n nativeName: 'Nederlands',\n englishName: 'Dutch',\n currencyCode: 'EUR',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 중국어 (홍콩)\n 'zh-HK': {\n locale: 'zh-HK',\n countryCode: 'HK',\n flag: '🇭🇰',\n dialCode: '+852',\n nativeName: '繁體中文 (香港)',\n englishName: 'Chinese (Hong Kong)',\n currencyCode: 'HKD',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 말레이어\n ms: {\n locale: 'ms',\n countryCode: 'MY',\n flag: '🇲🇾',\n dialCode: '+60',\n nativeName: 'Bahasa Melayu',\n englishName: 'Malay',\n currencyCode: 'MYR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (영국)\n 'en-GB': {\n locale: 'en-GB',\n countryCode: 'GB',\n flag: '🇬🇧',\n dialCode: '+44',\n nativeName: 'English (UK)',\n englishName: 'English (United Kingdom)',\n currencyCode: 'GBP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (캐나다)\n 'en-CA': {\n locale: 'en-CA',\n countryCode: 'CA',\n flag: '🇨🇦',\n dialCode: '+1',\n nativeName: 'English (Canada)',\n englishName: 'English (Canada)',\n currencyCode: 'CAD',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 영어 (호주)\n 'en-AU': {\n locale: 'en-AU',\n countryCode: 'AU',\n flag: '🇦🇺',\n dialCode: '+61',\n nativeName: 'English (Australia)',\n englishName: 'English (Australia)',\n currencyCode: 'AUD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 영어 (뉴질랜드)\n 'en-NZ': {\n locale: 'en-NZ',\n countryCode: 'NZ',\n flag: '🇳🇿',\n dialCode: '+64',\n nativeName: 'English (New Zealand)',\n englishName: 'English (New Zealand)',\n currencyCode: 'NZD',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (멕시코)\n 'es-MX': {\n locale: 'es-MX',\n countryCode: 'MX',\n flag: '🇲🇽',\n dialCode: '+52',\n nativeName: 'Español (México)',\n englishName: 'Spanish (Mexico)',\n currencyCode: 'MXN',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (아르헨티나)\n 'es-AR': {\n locale: 'es-AR',\n countryCode: 'AR',\n flag: '🇦🇷',\n dialCode: '+54',\n nativeName: 'Español (Argentina)',\n englishName: 'Spanish (Argentina)',\n currencyCode: 'ARS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스페인어 (콜롬비아)\n 'es-CO': {\n locale: 'es-CO',\n countryCode: 'CO',\n flag: '🇨🇴',\n dialCode: '+57',\n nativeName: 'Español (Colombia)',\n englishName: 'Spanish (Colombia)',\n currencyCode: 'COP',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스웨덴어\n sv: {\n locale: 'sv',\n countryCode: 'SE',\n flag: '🇸🇪',\n dialCode: '+46',\n nativeName: 'Svenska',\n englishName: 'Swedish',\n currencyCode: 'SEK',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 노르웨이어\n no: {\n locale: 'no',\n countryCode: 'NO',\n flag: '🇳🇴',\n dialCode: '+47',\n nativeName: 'Norsk',\n englishName: 'Norwegian',\n currencyCode: 'NOK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 덴마크어\n da: {\n locale: 'da',\n countryCode: 'DK',\n flag: '🇩🇰',\n dialCode: '+45',\n nativeName: 'Dansk',\n englishName: 'Danish',\n currencyCode: 'DKK',\n dateFormat: 'DD-MM-YYYY',\n },\n\n // 핀란드어\n fi: {\n locale: 'fi',\n countryCode: 'FI',\n flag: '🇫🇮',\n dialCode: '+358',\n nativeName: 'Suomi',\n englishName: 'Finnish',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 우크라이나어\n uk: {\n locale: 'uk',\n countryCode: 'UA',\n flag: '🇺🇦',\n dialCode: '+380',\n nativeName: 'Українська',\n englishName: 'Ukrainian',\n currencyCode: 'UAH',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 체코어\n cs: {\n locale: 'cs',\n countryCode: 'CZ',\n flag: '🇨🇿',\n dialCode: '+420',\n nativeName: 'Čeština',\n englishName: 'Czech',\n currencyCode: 'CZK',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 헝가리어\n hu: {\n locale: 'hu',\n countryCode: 'HU',\n flag: '🇭🇺',\n dialCode: '+36',\n nativeName: 'Magyar',\n englishName: 'Hungarian',\n currencyCode: 'HUF',\n dateFormat: 'YYYY.MM.DD.',\n },\n\n // 루마니아어\n ro: {\n locale: 'ro',\n countryCode: 'RO',\n flag: '🇷🇴',\n dialCode: '+40',\n nativeName: 'Română',\n englishName: 'Romanian',\n currencyCode: 'RON',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 불가리아어\n bg: {\n locale: 'bg',\n countryCode: 'BG',\n flag: '🇧🇬',\n dialCode: '+359',\n nativeName: 'Български',\n englishName: 'Bulgarian',\n currencyCode: 'BGN',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 크로아티아어\n hr: {\n locale: 'hr',\n countryCode: 'HR',\n flag: '🇭🇷',\n dialCode: '+385',\n nativeName: 'Hrvatski',\n englishName: 'Croatian',\n currencyCode: 'HRK',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 세르비아어\n sr: {\n locale: 'sr',\n countryCode: 'RS',\n flag: '🇷🇸',\n dialCode: '+381',\n nativeName: 'Српски',\n englishName: 'Serbian',\n currencyCode: 'RSD',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 슬로바키아어\n sk: {\n locale: 'sk',\n countryCode: 'SK',\n flag: '🇸🇰',\n dialCode: '+421',\n nativeName: 'Slovenčina',\n englishName: 'Slovak',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 슬로베니아어\n sl: {\n locale: 'sl',\n countryCode: 'SI',\n flag: '🇸🇮',\n dialCode: '+386',\n nativeName: 'Slovenščina',\n englishName: 'Slovenian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 리투아니아어\n lt: {\n locale: 'lt',\n countryCode: 'LT',\n flag: '🇱🇹',\n dialCode: '+370',\n nativeName: 'Lietuvių',\n englishName: 'Lithuanian',\n currencyCode: 'EUR',\n dateFormat: 'YYYY-MM-DD',\n },\n\n // 라트비아어\n lv: {\n locale: 'lv',\n countryCode: 'LV',\n flag: '🇱🇻',\n dialCode: '+371',\n nativeName: 'Latviešu',\n englishName: 'Latvian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY.',\n },\n\n // 에스토니아어\n et: {\n locale: 'et',\n countryCode: 'EE',\n flag: '🇪🇪',\n dialCode: '+372',\n nativeName: 'Eesti',\n englishName: 'Estonian',\n currencyCode: 'EUR',\n dateFormat: 'DD.MM.YYYY',\n },\n\n // 그리스어\n el: {\n locale: 'el',\n countryCode: 'GR',\n flag: '🇬🇷',\n dialCode: '+30',\n nativeName: 'Ελληνικά',\n englishName: 'Greek',\n currencyCode: 'EUR',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 페르시아어\n fa: {\n locale: 'fa',\n countryCode: 'IR',\n flag: '🇮🇷',\n dialCode: '+98',\n nativeName: 'فارسی',\n englishName: 'Persian',\n rtl: true,\n currencyCode: 'IRR',\n dateFormat: 'YYYY/MM/DD',\n },\n\n // 히브리어\n he: {\n locale: 'he',\n countryCode: 'IL',\n flag: '🇮🇱',\n dialCode: '+972',\n nativeName: 'עברית',\n englishName: 'Hebrew',\n rtl: true,\n currencyCode: 'ILS',\n dateFormat: 'DD/MM/YYYY',\n },\n\n // 스와힐리어\n sw: {\n locale: 'sw',\n countryCode: 'KE',\n flag: '🇰🇪',\n dialCode: '+254',\n nativeName: 'Kiswahili',\n englishName: 'Swahili',\n currencyCode: 'KES',\n dateFormat: 'DD/MM/YYYY',\n },\n};\n\n/**\n * Locale 정보 가져오기\n *\n * @param locale - Locale 코드 (예: 'ko', 'en', 'ja')\n * @returns LocaleInfo 또는 undefined\n *\n * @example\n * ```typescript\n * const koInfo = getLocaleInfo('ko');\n * console.log(koInfo.flag); // 🇰🇷\n * console.log(koInfo.dialCode); // +82\n * ```\n */\nexport function getLocaleInfo(locale: string): LocaleInfo | undefined\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale];\n}\n\n/**\n * 시스템이 지원 가능한 모든 Locale 목록 가져오기\n *\n * @returns Locale 코드 배열 (50+ locales available)\n */\nexport function getAllLocales(): SupportedLocale[]\n{\n return Object.keys(LOCALE_INFO_MAP) as SupportedLocale[];\n}\n\n/**\n * @deprecated Use getAllLocales() instead\n */\nexport function getSupportedLocales(): SupportedLocale[]\n{\n return getAllLocales();\n}\n\n/**\n * 국기 이모지만 가져오기\n *\n * @param locale - Locale 코드\n * @returns 국기 이모지 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getFlag('ko'); // 🇰🇷\n * getFlag('en'); // 🇺🇸\n * ```\n */\nexport function getFlag(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.flag ?? '';\n}\n\n/**\n * 전화번호 국가 코드 가져오기\n *\n * @param locale - Locale 코드\n * @returns 전화번호 코드 또는 빈 문자열\n *\n * @example\n * ```typescript\n * getDialCode('ko'); // +82\n * getDialCode('en'); // +1\n * ```\n */\nexport function getDialCode(locale: string): string\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.dialCode ?? '';\n}\n\n/**\n * RTL (Right-to-Left) 여부 확인\n *\n * @param locale - Locale 코드\n * @returns RTL 여부\n *\n * @example\n * ```typescript\n * isRTL('ar'); // true (Arabic)\n * isRTL('ko'); // false (Korean)\n * ```\n */\nexport function isRTL(locale: string): boolean\n{\n return LOCALE_INFO_MAP[locale as SupportedLocale]?.rtl ?? false;\n}","/**\n * CMS Sync Utilities\n *\n * JSON 파일 기반 라벨 동기화\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { basename, extname, join } from 'path';\nimport { extractLabels } from '@/server/labels';\nimport { cmsLabelsRepository, cmsPublishedCacheRepository } from '@/server/repositories';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\nimport type { NestedLabels, SectionDefinition, SyncOptions, SyncResult } from '@/lib/types';\n\n/**\n * 여러 섹션 동기화\n */\nexport async function syncAll(\n sections: SectionDefinition[],\n options: SyncOptions = {}\n): Promise<SyncResult[]>\n{\n const results: SyncResult[] = [];\n\n for (const definition of sections)\n {\n const result = await syncSection(definition, options);\n results.push(result);\n }\n\n return results;\n}\n\n/**\n * JSON 파일에서 라벨 로드\n */\nexport function loadLabelsFromJson(labelsDir: string): SectionDefinition[]\n{\n const sections: SectionDefinition[] = [];\n\n if (!existsSync(labelsDir))\n {\n console.warn(`[CMS] Labels directory not found: ${labelsDir}`);\n console.warn(`[CMS] Expected directory structure:`);\n console.warn(`[CMS] ${labelsDir}/`);\n console.warn(`[CMS] ├── common/ # Section directory`);\n console.warn(`[CMS] │ ├── messages.json`);\n console.warn(`[CMS] │ └── errors.json`);\n console.warn(`[CMS] └── home/ # Section directory`);\n console.warn(`[CMS] └── hero.json`);\n return sections;\n }\n\n try\n {\n const entries = readdirSync(labelsDir);\n\n if (entries.length === 0)\n {\n console.warn(`[CMS] Labels directory is empty: ${labelsDir}`);\n console.warn(`[CMS] Create section directories with JSON files inside`);\n return sections;\n }\n\n const jsonFiles = entries.filter(e => extname(e) === '.json');\n if (jsonFiles.length > 0)\n {\n console.warn(`[CMS] Found JSON files directly in ${labelsDir}:`);\n jsonFiles.forEach(f => console.warn(`[CMS] - ${f} (will be ignored)`));\n console.warn(`[CMS] JSON files should be inside section directories`);\n console.warn(`[CMS] Example: Move ${jsonFiles[0]} to ${labelsDir}/${basename(jsonFiles[0], '.json')}/${jsonFiles[0]}`);\n }\n\n for (const entry of entries)\n {\n const sectionPath = join(labelsDir, entry);\n const stat = statSync(sectionPath);\n\n if (stat.isDirectory())\n {\n const sectionName = entry;\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length > 0)\n {\n sections.push({ section: sectionName, labels });\n }\n else\n {\n console.warn(`[CMS] Section directory \"${sectionName}\" has no valid JSON files`);\n }\n }\n }\n\n if (sections.length === 0)\n {\n console.warn(`[CMS] No valid section directories found in ${labelsDir}`);\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not scan labels directory: ${labelsDir}`);\n console.error(`[CMS] Error:`, error);\n }\n\n return sections;\n}\n\nfunction loadSectionLabels(sectionPath: string): NestedLabels\n{\n const labels: NestedLabels = {};\n\n try\n {\n const files = readdirSync(sectionPath);\n\n for (const file of files)\n {\n if (extname(file) === '.json')\n {\n const filePath = join(sectionPath, file);\n const categoryName = basename(file, '.json');\n\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n labels[categoryName] = JSON.parse(content);\n }\n catch (error)\n {\n console.warn(`[CMS] Failed to parse ${filePath}`);\n }\n }\n }\n }\n catch (error)\n {\n console.warn(`[CMS] Could not read section directory: ${sectionPath}`);\n }\n\n return labels;\n}\n\n/**\n * 섹션 라벨 동기화\n */\nexport async function syncSection(\n definition: SectionDefinition,\n options: SyncOptions = {}\n): Promise<SyncResult>\n{\n const {\n dryRun = false,\n updateExisting = false,\n removeUnused = false,\n verbose = false,\n } = options;\n\n const { section } = definition;\n const result: SyncResult = {\n section,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: 0,\n errors: [],\n };\n\n try\n {\n const definedLabels = extractLabels(definition);\n const definedKeys = new Set(definedLabels.map((l) => l.key));\n const existingLabels = await cmsLabelsRepository.findBySection(section);\n const existingMap = new Map(existingLabels.map((l) => [l.key, l]));\n\n if (verbose)\n {\n console.log(`\\n[${section}] Found ${definedLabels.length} labels in definition`);\n console.log(`[${section}] Found ${existingLabels.length} labels in DB`);\n }\n\n // 생성 및 업데이트\n for (const label of definedLabels)\n {\n const existing = existingMap.get(label.key);\n\n if (!existing)\n {\n if (verbose) console.log(` [CREATE] ${label.key}`);\n\n if (!dryRun)\n {\n try\n {\n const defaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n await cmsLabelsRepository.create({\n section,\n key: label.key,\n type: label.type || 'text', // 라벨 타입 (기본값: 'text')\n defaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.created++;\n }\n else if (updateExisting)\n {\n const newDefaultValue = typeof label.defaultValue === 'object'\n ? JSON.stringify(label.defaultValue)\n : label.defaultValue;\n\n const newType = label.type || 'text';\n const hasChanged = existing.defaultValue !== newDefaultValue || existing.type !== newType;\n\n if (hasChanged)\n {\n if (verbose)\n {\n console.log(` [UPDATE] ${label.key}`);\n console.log(` Old: \"${existing.defaultValue}\"`);\n console.log(` New: \"${newDefaultValue}\"`);\n }\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.updateById(existing.id, {\n type: label.type || 'text',\n defaultValue: newDefaultValue,\n description: label.description,\n });\n }\n catch (error)\n {\n result.errors.push({\n key: label.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.updated++;\n }\n else\n {\n result.unchanged++;\n }\n }\n else\n {\n result.unchanged++;\n }\n }\n\n // 사용되지 않는 라벨 삭제\n if (removeUnused)\n {\n for (const existing of existingLabels)\n {\n if (!definedKeys.has(existing.key))\n {\n if (verbose) console.log(` [DELETE] ${existing.key}`);\n\n if (!dryRun)\n {\n try\n {\n await cmsLabelsRepository.deleteById(existing.id);\n }\n catch (error)\n {\n result.errors.push({\n key: existing.key,\n error: error instanceof Error ? error.message : String(error),\n });\n continue;\n }\n }\n\n result.deleted++;\n }\n }\n }\n\n // Published cache 업데이트\n if (!dryRun && (result.created > 0 || result.updated > 0 || result.deleted > 0))\n {\n if (verbose) console.log(` [CACHE] Updating published cache for section: ${section}`);\n await updatePublishedCache(section);\n }\n }\n catch (error)\n {\n result.errors.push({\n key: '__section__',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n return result;\n}\n\n/**\n * Published Cache 업데이트\n */\nasync function updatePublishedCache(section: string): Promise<void>\n{\n const labels = await cmsLabelsRepository.findBySection(section);\n const localesSet = new Set<string>();\n const labelsByLocale: Record<string, Record<string, any>> = {};\n const singleValueLabels: Array<{ key: string; value: any }> = [];\n\n // First pass: 다국어 객체 처리 및 사용 중인 locale 수집\n labels.forEach((label) =>\n {\n try\n {\n const parsed = JSON.parse(label.defaultValue || '{}');\n\n if (typeof parsed === 'object' && !Array.isArray(parsed))\n {\n // Multilingual object\n Object.keys(parsed).forEach((locale) => localesSet.add(locale));\n Object.entries(parsed).forEach(([locale, value]) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][label.key] = value;\n });\n }\n else\n {\n // Single value (will be distributed to all locales in second pass)\n singleValueLabels.push({ key: label.key, value: label.defaultValue });\n }\n }\n catch\n {\n // Plain string (will be distributed to all locales in second pass)\n singleValueLabels.push({ key: label.key, value: label.defaultValue });\n }\n });\n\n // 최소 기본 locale 보장 (ko, en)\n if (localesSet.size === 0)\n {\n localesSet.add('ko');\n localesSet.add('en');\n }\n\n // Second pass: 단일 값을 모든 locale에 복사\n singleValueLabels.forEach(({ key, value }) =>\n {\n localesSet.forEach((locale) =>\n {\n if (!labelsByLocale[locale]) labelsByLocale[locale] = {};\n labelsByLocale[locale][key] = value;\n });\n });\n\n const timestamp = new Date();\n for (const locale of localesSet)\n {\n await cmsPublishedCacheRepository.upsert({\n section,\n locale,\n content: labelsByLocale[locale] || {},\n publishedAt: timestamp,\n publishedBy: 'system',\n });\n }\n}\n\n/**\n * Initialize label sync for server startup\n *\n * Call this in your server.config.ts beforeRoutes hook\n *\n * @param options - Sync options\n * @param options.labelsDir - Path to labels directory (default: 'src/lib/labels')\n *\n * @example\n * ```typescript\n * import { initLabelSync } from '@spfn/cms';\n *\n * export default {\n * beforeRoutes: async (app) => {\n * await initLabelSync({ verbose: true });\n * },\n * } satisfies ServerConfig;\n * ```\n */\nexport async function initLabelSync(options: SyncOptions & { labelsDir?: string } = {}): Promise<void>\n{\n const isDevelopment = process.env.NODE_ENV === 'development';\n const verbose = options.verbose ?? isDevelopment;\n const labelsDir = options.labelsDir ?? DEFAULT_LABELS_DIR;\n\n if (verbose)\n {\n console.log('\\n🔄 Initializing label sync...\\n');\n }\n\n // Load labels from JSON files\n const sections = loadLabelsFromJson(labelsDir);\n\n if (sections.length === 0)\n {\n if (verbose)\n {\n console.log('⚠️ No labels found in', labelsDir);\n console.log('');\n }\n return;\n }\n\n const results = await syncAll(sections, {\n updateExisting: true, // 🔄 항상 업데이트 (프로덕션 포함)\n ...options,\n verbose,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalUnchanged = results.reduce((sum, r) => sum + r.unchanged, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (verbose)\n {\n console.log('✅ Label sync completed\\n');\n console.log(` Sections: ${results.length}`);\n console.log(` Created: ${totalCreated}`);\n console.log(` Updated: ${totalUpdated}`);\n console.log(` Unchanged: ${totalUnchanged}`);\n\n if (totalErrors > 0)\n {\n console.log(` Errors: ${totalErrors}\\n`);\n }\n else\n {\n console.log('');\n }\n }\n\n // Log errors\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n console.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n}","/**\n * CMS Label Helpers\n *\n * Utilities for processing label definitions from JSON files\n */\n\nimport type { SectionDefinition, NestedLabels, FlatLabel, LabelDefinition } from '@/lib/types';\n\n/**\n * 중첩된 라벨을 플랫화\n *\n * @param labels - 중첩된 라벨 객체\n * @returns 플랫화된 라벨 배열\n *\n * @example\n * ```ts\n * flattenLabels({\n * nav: {\n * home: { key: 'layout.nav.home', defaultValue: 'Home' },\n * },\n * });\n * // => [{ key: 'layout.nav.home', defaultValue: 'Home' }]\n * ```\n */\nexport function flattenLabels(labels: NestedLabels): FlatLabel[]\n{\n const result: FlatLabel[] = [];\n\n function isLabelDefinition(obj: NestedLabels | LabelDefinition): obj is LabelDefinition\n {\n return (\n 'key' in obj &&\n 'defaultValue' in obj &&\n typeof obj.key === 'string' &&\n (typeof obj.defaultValue === 'string' || typeof obj.defaultValue === 'object')\n );\n }\n\n function traverse(obj: NestedLabels | LabelDefinition)\n {\n if (isLabelDefinition(obj))\n {\n // LabelDefinition인 경우\n result.push({\n key: obj.key,\n type: obj.type,\n defaultValue: obj.defaultValue,\n description: obj.description,\n });\n }\n else\n {\n // NestedLabels인 경우\n Object.values(obj).forEach((value) =>\n {\n if (typeof value === 'object' && value !== null)\n {\n traverse(value);\n }\n });\n }\n }\n\n traverse(labels);\n return result;\n}\n\n/**\n * 섹션 정의에서 모든 라벨 추출\n *\n * @param definition - 섹션 정의\n * @returns 플랫화된 라벨 배열\n */\nexport function extractLabels(definition: SectionDefinition): FlatLabel[]\n{\n return flattenLabels(definition.labels);\n}\n","/**\n * CMS Labels Repository\n *\n * 라벨 메타데이터 관리를 위한 Repository\n */\n\nimport { findOne, findMany as findManyHelper, create as createHelper, updateOne, deleteOne, count as countHelper } from '@spfn/core/db';\nimport { asc } from 'drizzle-orm';\nimport { cmsLabels, type CmsLabel, type NewCmsLabel } from '@/server/entities';\n\n/**\n * 라벨 목록 조회\n */\nexport async function findMany(options?: {\n section?: string;\n}): Promise<CmsLabel[]>\n{\n const { section } = options || {};\n\n return findManyHelper(cmsLabels, {\n where: section ? { section } : undefined,\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 전체 라벨 수 조회\n */\nexport async function count(section?: string): Promise<number>\n{\n return countHelper(cmsLabels, section ? { section } : undefined);\n}\n\n/**\n * ID로 라벨 조회\n */\nexport async function findById(id: number): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { id });\n}\n\n/**\n * Key로 라벨 조회\n */\nexport async function findByKey(key: string): Promise<CmsLabel | null>\n{\n return findOne(cmsLabels, { key });\n}\n\n/**\n * 섹션으로 모든 라벨 조회\n */\nexport async function findBySection(section: string): Promise<CmsLabel[]>\n{\n return findManyHelper(cmsLabels, {\n where: { section },\n orderBy: asc(cmsLabels.key), // key 오름차순 정렬 (JSON 파일의 순서 유지)\n });\n}\n\n/**\n * 라벨 생성\n */\nexport async function create(data: NewCmsLabel): Promise<CmsLabel>\n{\n return createHelper(cmsLabels, data);\n}\n\n/**\n * 라벨 수정\n */\nexport async function updateById(id: number, data: Partial<NewCmsLabel>): Promise<CmsLabel | null>\n{\n return updateOne(cmsLabels, { id }, { ...data, updatedAt: new Date() });\n}\n\n/**\n * 라벨 삭제\n */\nexport async function deleteById(id: number): Promise<CmsLabel | null>\n{\n return deleteOne(cmsLabels, { id });\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelsRepository = {\n findMany,\n count,\n findById,\n findByKey,\n findBySection,\n create,\n updateById,\n deleteById\n};","/**\n * CMS Labels Entity\n *\n * 라벨의 메타데이터와 현재 발행 상태를 관리합니다.\n * - 라벨 식별 (id, key)\n * - 섹션 분류 (section)\n * - 타입 정의 (type)\n * - 발행 상태 (publishedVersion)\n */\n\nimport { index, integer, serial, text, timestamp } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\nexport const cmsLabels = cmsSchema.table('labels', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 라벨 식별자\n key: text('key').notNull().unique(),\n // 예: \"home.hero.title\", \"why-futureplay.hero.subtitle\"\n // 구조: {section}.{component}.{property}\n\n // 섹션 분류 (페이지 단위)\n section: text('section').notNull(),\n // 예: \"home\", \"why-futureplay\", \"team\"\n\n // 값 타입\n type: text('type').notNull(),\n // \"text\" | \"image\" | \"video\" | \"file\" | \"object\"\n\n // 기본값\n defaultValue: text('default_value'),\n // 라벨의 기본값 (sync 시 설정)\n\n // 설명\n description: text('description'),\n // 라벨에 대한 설명 (optional)\n\n // 현재 발행된 버전 번호\n publishedVersion: integer('published_version'),\n // null = 미발행 상태\n // 1, 2, 3... = 발행된 버전 번호\n\n // 생성자 추적\n createdBy: text('created_by'),\n\n // 타임스탬프\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: 섹션별 조회 최적화\n index('cms_labels_section_idx').on(table.section),\n\n // 인덱스: key로 조회 최적화 (unique 제약으로 자동 생성되지만 명시)\n index('cms_labels_key_idx').on(table.key),\n]);\n\n// 타입 추론\nexport type CmsLabel = typeof cmsLabels.$inferSelect;\nexport type NewCmsLabel = typeof cmsLabels.$inferInsert;","/**\n * CMS Schema Definition\n *\n * Creates isolated 'spfn_cms' PostgreSQL schema for CMS tables.\n * Export this schema so drizzle-kit can generate CREATE SCHEMA statement.\n */\nimport { createFunctionSchema } from '@spfn/core/db';\n\nexport const cmsSchema = createFunctionSchema('@spfn/cms');","/**\n * CMS Label Values Entity\n *\n * 라벨의 실제 값을 저장합니다.\n * - 다국어 지원 (locale)\n * - 반응형 지원 (breakpoint)\n * - 버전 관리 (version)\n * - JSONB로 유연한 값 저장\n */\n\nimport { serial, integer, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsLabelValues = cmsSchema.table('label_values', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels\n labelId: integer('label_id')\n .notNull()\n .references(() => cmsLabels.id, { onDelete: 'cascade' }),\n\n // 버전 번호 (null = draft, number = published version)\n version: integer('version'),\n\n // 언어 코드\n locale: text('locale').notNull().default('ko'),\n // \"ko\" | \"en\" | \"ja\"\n\n // 반응형 브레이크포인트\n breakpoint: text('breakpoint'),\n // null = 기본값 (모든 화면 크기)\n // \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\"\n\n // 실제 값 (JSONB)\n value: jsonb('value').notNull(),\n // LabelValue 타입:\n // - TextValue: { type: \"text\", content: string }\n // - ImageValue: { type: \"image\", url: string, alt?: string, width?: number, height?: number }\n // - VideoValue: { type: \"video\", url: string, thumbnail?: string, duration?: number }\n // - FileValue: { type: \"file\", url: string, filename: string, size?: number }\n // - ObjectValue: { type: \"object\", fields: Record<string, LabelValue> }\n\n // 생성 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: 같은 버전에서 locale + breakpoint 조합은 유일\n unique('cms_label_values_locale_breakpoint_unique')\n .on(table.labelId, table.version, table.locale, table.breakpoint),\n\n // 인덱스: labelId + version 복합 조회 최적화\n index('cms_label_values_label_version_idx')\n .on(table.labelId, table.version),\n\n // 인덱스: locale 필터링 최적화\n index('cms_label_values_locale_idx').on(table.locale),\n]);\n\n// 타입 추론\nexport type CmsLabelValue = typeof cmsLabelValues.$inferSelect;\nexport type NewCmsLabelValue = typeof cmsLabelValues.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 텍스트 값 저장\n * await db.insert(cmsLabelValues).values({\n * labelId: 1,\n * version: 1,\n * locale: 'ko',\n * breakpoint: null,\n * value: {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * }\n * });\n *\n * // 반응형 이미지 저장 (모바일용)\n * await db.insert(cmsLabelValues).values({\n * labelId: 2,\n * version: 1,\n * locale: 'ko',\n * breakpoint: 'sm',\n * value: {\n * type: 'image',\n * url: '/uploads/hero-mobile.jpg',\n * alt: 'Hero Image',\n * width: 640,\n * height: 480\n * }\n * });\n *\n * // 특정 버전의 한국어 값 조회\n * const values = await db.select()\n * .from(cmsLabelValues)\n * .where(and(\n * eq(cmsLabelValues.labelId, 1),\n * eq(cmsLabelValues.version, 2),\n * eq(cmsLabelValues.locale, 'ko')\n * ));\n *\n * // Object 타입 값 저장 (재귀 구조)\n * await db.insert(cmsLabelValues).values({\n * labelId: 3,\n * version: 1,\n * locale: 'ko',\n * value: {\n * type: 'object',\n * fields: {\n * title: { type: 'text', content: '특징 1' },\n * icon: { type: 'image', url: '/icons/feature1.svg', alt: 'Icon' },\n * description: { type: 'text', content: '상세 설명...' }\n * }\n * }\n * });\n */","/**\n * CMS Draft Cache Entity\n *\n * 관리자별 Draft 콘텐츠를 캐싱합니다.\n * - 사용자별 격리 (userId)\n * - 실시간 미리보기 지원\n * - 동시 편집 가능\n *\n * 핵심 기능:\n * - 여러 관리자가 같은 섹션을 동시에 편집\n * - 각자의 변경사항은 자신의 미리보기에만 표시\n * - 충돌 없이 안전하게 작업\n */\n\nimport { serial, text, jsonb, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsDraftCache = cmsSchema.table('draft_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 사용자 ID (핵심 필드!)\n userId: text('user_id').notNull(),\n // 각 관리자의 독립적인 작업 공간\n\n // Draft 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"수정 중...\" },\n // \"home.hero.subtitle\": { type: \"text\", content: \"새로운 문구\" },\n // ...\n // }\n\n // 최종 수정 시각\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // UNIQUE 제약: section + locale + userId 조합은 유일\n unique('cms_draft_cache_unique')\n .on(table.section, table.locale, table.userId),\n\n // 인덱스: section으로 조회 최적화\n index('cms_draft_cache_section_idx').on(table.section),\n\n // 인덱스: userId로 사용자의 모든 draft 조회 최적화\n index('cms_draft_cache_user_idx').on(table.userId),\n]);\n\n// 타입 추론\nexport type CmsDraftCache = typeof cmsDraftCache.$inferSelect;\nexport type NewCmsDraftCache = typeof cmsDraftCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // Draft 초기화 (편집 시작)\n * await db.insert(cmsDraftCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * userId: 'user-a@futureplay.com',\n * content: publishedContent // 발행 버전 복사\n * });\n *\n * // Draft 업데이트 (값 수정 시)\n * const cache = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ))\n * .limit(1);\n *\n * const updatedContent = {\n * ...cache[0].content,\n * 'home.hero.title': newValue // 부분 업데이트\n * };\n *\n * await db.update(cmsDraftCache)\n * .set({ content: updatedContent, updatedAt: new Date() })\n * .where(eq(cmsDraftCache.id, cache[0].id));\n *\n * // Draft 조회 (미리보기)\n * const draft = await db.select()\n * .from(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, session.user.id)\n * ))\n * .limit(1);\n *\n * // 사용자의 모든 작업 중인 섹션 조회\n * const userDrafts = await db.select()\n * .from(cmsDraftCache)\n * .where(eq(cmsDraftCache.userId, userId))\n * .orderBy(desc(cmsDraftCache.updatedAt));\n *\n * // 오래된 Draft 정리 (30일 이상)\n * const stale = await db.delete(cmsDraftCache)\n * .where(lt(\n * cmsDraftCache.updatedAt,\n * new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)\n * ))\n * .returning();\n *\n * // Draft 폐기 (변경사항 버리기)\n * await db.delete(cmsDraftCache)\n * .where(and(\n * eq(cmsDraftCache.section, 'home'),\n * eq(cmsDraftCache.locale, 'ko'),\n * eq(cmsDraftCache.userId, userId)\n * ));\n */","/**\n * CMS Published Cache Entity\n *\n * 발행된 콘텐츠를 섹션+언어 단위로 캐싱합니다.\n * - 초고속 읽기 성능 (5ms)\n * - 단일 쿼리로 섹션 전체 로드\n * - JSONB로 즉시 사용 가능한 데이터\n *\n * 성능 비교:\n * - 정규화 테이블 JOIN: 87ms\n * - 캐시 테이블: 5ms (17배 빠름!)\n */\n\nimport { serial, text, jsonb, integer, timestamp, index, unique } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsPublishedCache = cmsSchema.table('published_cache', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // 섹션 (페이지 단위)\n section: text('section').notNull(),\n // \"home\" | \"why-futureplay\" | \"team\" | \"our-companies\" | \"apply\"\n\n // 언어\n locale: text('locale').notNull(),\n // \"ko\" | \"en\" | \"ja\"\n\n // 캐시된 콘텐츠 (JSONB)\n content: jsonb('content').notNull(),\n // Record<string, LabelValue>\n // {\n // \"home.hero.title\": { type: \"text\", content: \"...\" },\n // \"home.hero.image\": { type: \"image\", url: \"...\", alt: \"...\" },\n // ...\n // }\n\n // 발행 정보\n publishedAt: timestamp('published_at', { withTimezone: true }).notNull(),\n publishedBy: text('published_by'),\n\n // 캐시 버전 (클라이언트 캐싱용)\n version: integer('version').notNull().default(1),\n}, (table) => [\n // UNIQUE 제약: section + locale 조합은 유일\n unique('cms_published_cache_unique').on(table.section, table.locale),\n\n // 인덱스: section으로 조회 최적화\n index('cms_published_cache_section_idx').on(table.section),\n]);\n\n// 타입 추론\nexport type CmsPublishedCache = typeof cmsPublishedCache.$inferSelect;\nexport type NewCmsPublishedCache = typeof cmsPublishedCache.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 캐시 생성/업데이트 (UPSERT)\n * await db.insert(cmsPublishedCache)\n * .values({\n * section: 'home',\n * locale: 'ko',\n * content: {\n * 'home.hero.title': {\n * type: 'text',\n * content: '미래를 만드는 기업'\n * },\n * 'home.hero.image': {\n * type: 'image',\n * url: '/uploads/hero.jpg',\n * alt: 'Hero',\n * width: 1920,\n * height: 1080\n * }\n * },\n * publishedAt: new Date(),\n * publishedBy: 'admin@futureplay.com'\n * })\n * .onConflictDoUpdate({\n * target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n * set: {\n * content: sql`EXCLUDED.content`,\n * publishedAt: sql`EXCLUDED.published_at`,\n * publishedBy: sql`EXCLUDED.published_by`,\n * version: sql`${cmsPublishedCache.version} + 1`\n * }\n * });\n *\n * // 캐시 조회 (초고속!)\n * const cache = await db.select()\n * .from(cmsPublishedCache)\n * .where(and(\n * eq(cmsPublishedCache.section, 'home'),\n * eq(cmsPublishedCache.locale, 'ko')\n * ))\n * .limit(1);\n *\n * const labels = cache[0].content; // 즉시 사용 가능!\n *\n * // 섹션의 모든 언어 캐시 조회\n * const allLocales = await db.select()\n * .from(cmsPublishedCache)\n * .where(eq(cmsPublishedCache.section, 'home'));\n *\n * // 오래된 캐시 감지\n * const stale = await db.select()\n * .from(cmsPublishedCache)\n * .where(lt(\n * cmsPublishedCache.publishedAt,\n * new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)\n * ));\n */","/**\n * CMS Audit Logs Entity\n *\n * CMS의 모든 변경사항을 추적합니다.\n * - 누가 (userId, userName)\n * - 언제 (createdAt)\n * - 무엇을 (action, changes)\n * - 왜 (metadata)\n */\n\nimport { serial, integer, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core';\nimport { cmsSchema } from './cms-schema';\nimport { cmsLabels } from '@/server/entities/cms-labels';\n\n// Create isolated schema for @spfn/cms\n// Schema imported from cms-schema.ts\n\nexport const cmsAuditLogs = cmsSchema.table('audit_logs', {\n // Primary Key\n id: serial('id').primaryKey(),\n\n // Foreign Key: cms_labels (nullable - 라벨 삭제 시 로그는 유지)\n labelId: integer('label_id')\n .references(() => cmsLabels.id, { onDelete: 'set null' }),\n\n // 작업 유형\n action: text('action').notNull(),\n // \"create\" | \"update\" | \"publish\" | \"unpublish\" | \"archive\" | \"delete\" | \"rollback\" | \"duplicate\"\n\n // 사용자 정보\n userId: text('user_id').notNull(),\n userName: text('user_name'),\n\n // 변경 내용 (before/after)\n changes: jsonb('changes'),\n // { before: {...}, after: {...} }\n\n // 추가 메타데이터\n metadata: jsonb('metadata'),\n // { version: number, ip: string, userAgent: string, ... }\n\n // 작업 시각\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n}, (table) => [\n // 인덱스: labelId로 이력 조회 최적화\n index('cms_audit_logs_label_id_idx').on(table.labelId),\n\n // 인덱스: userId로 사용자 활동 조회 최적화\n index('cms_audit_logs_user_id_idx').on(table.userId),\n\n // 인덱스: action 필터링 최적화\n index('cms_audit_logs_action_idx').on(table.action),\n\n // 인덱스: 시간순 조회 최적화\n index('cms_audit_logs_created_at_idx').on(table.createdAt),\n]);\n\n// 타입 추론\nexport type CmsAuditLog = typeof cmsAuditLogs.$inferSelect;\nexport type NewCmsAuditLog = typeof cmsAuditLogs.$inferInsert;\n\n/**\n * 사용 예시:\n *\n * // 라벨 생성 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'create',\n * userId: 'user123',\n * userName: '김철수',\n * changes: {\n * before: null,\n * after: {\n * key: 'home.hero.title',\n * section: 'home',\n * type: 'text'\n * }\n * },\n * metadata: {\n * ip: '192.168.1.1',\n * userAgent: 'Mozilla/5.0...'\n * }\n * });\n *\n * // 발행 로그\n * await db.insert(cmsAuditLogs).values({\n * labelId: 1,\n * action: 'publish',\n * userId: 'admin123',\n * userName: '관리자',\n * changes: {\n * before: { status: 'draft', publishedVersion: null },\n * after: { status: 'published', publishedVersion: 2 }\n * },\n * metadata: {\n * version: 2,\n * notes: '신규 브랜딩 적용'\n * }\n * });\n *\n * // 라벨별 이력 조회\n * const logs = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.labelId, 1))\n * .orderBy(desc(cmsAuditLogs.createdAt))\n * .limit(20);\n *\n * // 사용자별 활동 조회\n * const userActivity = await db.select()\n * .from(cmsAuditLogs)\n * .where(eq(cmsAuditLogs.userId, 'user123'))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n *\n * // 최근 24시간 변경 이력\n * const recent = await db.select()\n * .from(cmsAuditLogs)\n * .where(gte(cmsAuditLogs.createdAt, new Date(Date.now() - 24 * 60 * 60 * 1000)))\n * .orderBy(desc(cmsAuditLogs.createdAt));\n */","/**\n * CMS Label Values Repository\n *\n * 라벨 값 관리를 위한 Repository\n */\n\nimport { findOne, findMany, create, updateOne, deleteMany } from '@spfn/core/db';\nimport { eq, and, SQL, isNull } from 'drizzle-orm';\nimport { cmsLabelValues, type CmsLabelValue, type NewCmsLabelValue } from '@/server/entities';\n\n/**\n * 특정 라벨의 특정 버전 값들 조회\n */\nexport async function findByLabelIdAndVersion(\n labelId: number,\n version: number,\n options?: {\n locale?: string;\n breakpoint?: string | null;\n }\n): Promise<CmsLabelValue[]>\n{\n const { locale, breakpoint } = options || {};\n\n const conditions: SQL[] = [\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n ];\n\n if (locale)\n {\n conditions.push(eq(cmsLabelValues.locale, locale));\n }\n\n if (breakpoint !== undefined)\n {\n conditions.push(\n breakpoint === null\n ? isNull(cmsLabelValues.breakpoint)\n : eq(cmsLabelValues.breakpoint, breakpoint)\n );\n }\n\n return findMany(cmsLabelValues, {\n where: and(...conditions)\n });\n}\n\n/**\n * 값 저장 (upsert)\n * - version: null → Draft 저장 (덮어쓰기)\n * - version: number → Published 버전 생성 (불변)\n */\nexport async function upsert(data: NewCmsLabelValue): Promise<CmsLabelValue>\n{\n // 기존 값이 있는지 확인\n const versionCondition = data.version === null || data.version === undefined\n ? isNull(cmsLabelValues.version)\n : eq(cmsLabelValues.version, data.version as number);\n\n const existing = await findOne(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, data.labelId),\n versionCondition,\n eq(cmsLabelValues.locale, data.locale || 'ko'),\n data.breakpoint\n ? eq(cmsLabelValues.breakpoint, data.breakpoint)\n : isNull(cmsLabelValues.breakpoint)\n )\n );\n\n if (existing)\n {\n // UPDATE (only for drafts with version: null)\n if (data.version === null || data.version === undefined)\n {\n const updated = await updateOne(\n cmsLabelValues,\n { id: existing.id },\n { value: data.value }\n );\n return updated!;\n }\n else\n {\n // Published versions are immutable - this shouldn't happen\n throw new Error(`Published version ${data.version} already exists and cannot be overwritten`);\n }\n }\n else\n {\n // INSERT (both draft and new published versions)\n return create(cmsLabelValues, data);\n }\n}\n\n/**\n * Draft 값들 조회 (version = null)\n */\nexport async function findDraftsByLabelId(labelId: number): Promise<CmsLabelValue[]>\n{\n return findMany(cmsLabelValues, {\n where: and(\n eq(cmsLabelValues.labelId, labelId),\n isNull(cmsLabelValues.version)\n )\n });\n}\n\n/**\n * 여러 값 일괄 저장\n */\nexport async function upsertMany(values: NewCmsLabelValue[]): Promise<CmsLabelValue[]>\n{\n const results = [];\n for (const value of values)\n {\n const result = await upsert(value);\n results.push(result);\n }\n return results;\n}\n\n/**\n * 특정 버전의 모든 값 삭제\n */\nexport async function deleteByVersion(labelId: number, version: number): Promise<CmsLabelValue[]>\n{\n return deleteMany(\n cmsLabelValues,\n and(\n eq(cmsLabelValues.labelId, labelId),\n eq(cmsLabelValues.version, version)\n )\n );\n}\n\n// Legacy export for backward compatibility\nexport const cmsLabelValuesRepository = {\n findByLabelIdAndVersion,\n findDraftsByLabelId,\n upsert,\n upsertMany,\n deleteByVersion\n};","/**\n * CMS Draft Cache Repository\n *\n * 관리자별 초안 캐시 관리 (동시 편집 지원)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, lt } from 'drizzle-orm';\nimport { cmsDraftCache, type NewCmsDraftCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어 + 사용자로 초안 캐시 조회\n */\nexport async function findByUser(section: string, locale: string, userId: string)\n{\n return findOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 초안 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsDraftCache)\n{\n return upsertHelper(cmsDraftCache, data, {\n target: [cmsDraftCache.section, cmsDraftCache.locale, cmsDraftCache.userId],\n set: {\n content: data.content,\n updatedAt: new Date(),\n }\n });\n}\n\n/**\n * 특정 사용자의 모든 초안 조회\n */\nexport async function findAllByUser(userId: string)\n{\n return findMany(cmsDraftCache, {\n where: eq(cmsDraftCache.userId, userId)\n });\n}\n\n/**\n * 초안 삭제\n */\nexport async function deleteByUser(section: string, locale: string, userId: string)\n{\n await deleteOne(\n cmsDraftCache,\n and(\n eq(cmsDraftCache.section, section),\n eq(cmsDraftCache.locale, locale),\n eq(cmsDraftCache.userId, userId)\n )\n );\n}\n\n/**\n * 오래된 초안 정리 (30일 이상 미사용)\n */\nexport async function cleanupOldDrafts(daysOld: number = 30)\n{\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - daysOld);\n\n return deleteMany(\n cmsDraftCache,\n lt(cmsDraftCache.updatedAt, cutoffDate)\n );\n}\n\nexport const cmsDraftCacheRepository = {\n findByUser,\n upsert,\n findAllByUser,\n deleteByUser,\n cleanupOldDrafts,\n};","/**\n * CMS Published Cache Repository\n *\n * 발행된 콘텐츠 캐시 관리 (초고속 조회)\n */\n\nimport { findOne, findMany, deleteOne, deleteMany, upsert as upsertHelper } from '@spfn/core/db';\nimport { eq, and, sql } from 'drizzle-orm';\nimport { cmsPublishedCache, type NewCmsPublishedCache } from '@/server/entities';\n\n/**\n * 섹션 + 언어로 발행된 캐시 조회\n */\nexport async function findBySection(section: string, locale: string = 'ko')\n{\n return findOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n}\n\n/**\n * 캐시 생성 또는 업데이트 (UPSERT)\n */\nexport async function upsert(data: NewCmsPublishedCache)\n{\n return upsertHelper(cmsPublishedCache, data, {\n target: [cmsPublishedCache.section, cmsPublishedCache.locale],\n set: {\n content: data.content,\n publishedAt: data.publishedAt,\n publishedBy: data.publishedBy,\n version: sql`${cmsPublishedCache.version} + 1`, // 버전 증가로 클라이언트 캐시 무효화\n }\n });\n}\n\n/**\n * 섹션별 모든 언어 캐시 조회\n */\nexport async function findAllLanguages(section: string)\n{\n return findMany(cmsPublishedCache, {\n where: eq(cmsPublishedCache.section, section)\n });\n}\n\n/**\n * 캐시 삭제\n */\nexport async function deleteBySection(section: string, locale?: string)\n{\n if (locale)\n {\n await deleteOne(\n cmsPublishedCache,\n and(\n eq(cmsPublishedCache.section, section),\n eq(cmsPublishedCache.locale, locale)\n )\n );\n }\n else\n {\n await deleteMany(\n cmsPublishedCache,\n eq(cmsPublishedCache.section, section)\n );\n }\n}\n\nexport const cmsPublishedCacheRepository = {\n findBySection,\n upsert,\n findAllLanguages,\n deleteBySection,\n};","/**\n * CMS Constants\n *\n * CMS 패키지에서 사용하는 전역 상수\n */\n\n/**\n * 기본 라벨 디렉토리 경로\n *\n * JSON 라벨 파일이 저장되는 기본 디렉토리입니다.\n * 프로젝트 루트 기준 상대 경로입니다.\n *\n * @example\n * ```typescript\n * import { DEFAULT_LABELS_DIR } from '@spfn/cms';\n *\n * console.log(DEFAULT_LABELS_DIR); // 'src/lib/labels'\n * ```\n */\nexport const DEFAULT_LABELS_DIR = 'src/lib/labels';","/**\n * Label Sync Generator\n *\n * File-based label sync with JSON definitions\n *\n * Structure:\n * lib/labels/\n * layout/ # Section name\n * nav.json # Label definitions\n * footer.json\n * homepage/\n * hero.json\n *\n * Features:\n * - Incremental updates: syncs only changed section on file change\n * - Full sync on file add/delete or manual trigger\n */\n\nimport { logger } from '@spfn/core/logger';\nimport type { Generator, GeneratorOptions, GeneratorTrigger } from '@spfn/core/codegen';\nimport { join, relative, extname } from 'path';\nimport { existsSync, readdirSync, readFileSync, statSync } from 'fs';\n\nimport { syncAll, syncSection, loadLabelsFromJson } from '@/server/helpers/sync';\nimport { DEFAULT_LABELS_DIR } from '@/lib/constants';\n\nconst syncLogger = logger.child('label-sync');\n\nexport interface LabelSyncGeneratorConfig\n{\n labelsDir?: string;\n runOn?: GeneratorTrigger[];\n}\n\n/**\n * Create label sync generator\n *\n * Supports incremental updates when single files change\n */\nexport function createLabelSyncGenerator(config: LabelSyncGeneratorConfig = {}): Generator\n{\n const labelsDir = config.labelsDir ?? DEFAULT_LABELS_DIR;\n const runOn = config.runOn ?? ['watch', 'manual', 'build'];\n\n return {\n name: 'label-sync',\n watchPatterns: [`${labelsDir}/**/*.json`],\n runOn,\n\n async generate(options: GeneratorOptions): Promise<void>\n {\n const labelsPath = join(options.cwd, labelsDir);\n\n // Check if labels directory exists\n if (!existsSync(labelsPath))\n {\n if (options.debug)\n {\n syncLogger.warn(`Labels directory not found: ${labelsPath}`);\n }\n return;\n }\n\n try\n {\n // Check for incremental update opportunity\n const changedFile = options.trigger?.changedFile;\n\n if (changedFile && changedFile.event === 'change')\n {\n // Try incremental update for changed files\n const success = await attemptIncrementalSync({\n cwd: options.cwd,\n labelsPath,\n changedFilePath: changedFile.path,\n debug: options.debug\n });\n\n if (success)\n {\n if (options.debug)\n {\n syncLogger.info('Incremental sync successful');\n }\n return;\n }\n\n if (options.debug)\n {\n syncLogger.info('Incremental sync failed, doing full sync');\n }\n }\n\n // Full sync\n if (options.debug)\n {\n syncLogger.info('Starting full label sync...');\n }\n\n const sections = loadLabelsFromJson(labelsPath);\n\n if (sections.length === 0)\n {\n syncLogger.warn(`No labels found in ${labelsPath}`);\n return;\n }\n\n syncLogger.info(`Found ${sections.length} sections`);\n\n // Sync all sections\n const results = await syncAll(sections, {\n verbose: options.debug ?? false,\n updateExisting: true,\n });\n\n const totalCreated = results.reduce((sum, r) => sum + r.created, 0);\n const totalUpdated = results.reduce((sum, r) => sum + r.updated, 0);\n const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);\n\n if (options.debug || totalCreated > 0 || totalUpdated > 0)\n {\n syncLogger.info('Label sync completed', {\n sections: results.length,\n created: totalCreated,\n updated: totalUpdated,\n errors: totalErrors,\n });\n }\n\n // Log errors if any\n if (totalErrors > 0)\n {\n results.forEach((result) =>\n {\n result.errors.forEach((error) =>\n {\n syncLogger.error(`[${result.section}] ${error.key}: ${error.error}`);\n });\n });\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.error('Label sync failed', err);\n throw err;\n }\n }\n };\n}\n\n/**\n * Options for incremental sync\n */\ninterface IncrementalSyncOptions\n{\n cwd: string;\n labelsPath: string;\n changedFilePath: string;\n debug?: boolean;\n}\n\n/**\n * Attempt incremental sync for a changed file\n *\n * Strategy:\n * 1. Extract section name from file path (e.g., lib/labels/layout/nav.json -> 'layout')\n * 2. Load only that section's labels\n * 3. Sync only that section\n *\n * Returns true if successful, false if full sync is needed\n */\nasync function attemptIncrementalSync(options: IncrementalSyncOptions): Promise<boolean>\n{\n const { cwd, labelsPath, changedFilePath, debug } = options;\n\n try\n {\n const fullPath = join(cwd, changedFilePath);\n\n if (!existsSync(fullPath))\n {\n // File deleted during watch, need full sync\n return false;\n }\n\n // Extract section name from path\n // Example: lib/labels/layout/nav.json\n // ^^^^^^^^^^ labelsDir ^^^^^^ section ^^^^^^^^ file\n const relativePath = relative(labelsPath, fullPath);\n const parts = relativePath.split('/');\n\n if (parts.length < 2)\n {\n // File is directly in labels dir, not in section directory\n return false;\n }\n\n const sectionName = parts[0];\n\n if (debug)\n {\n syncLogger.info('Attempting incremental sync', {\n section: sectionName,\n file: changedFilePath\n });\n }\n\n // Load all labels from this section\n const sectionPath = join(labelsPath, sectionName);\n const labels = loadSectionLabels(sectionPath);\n\n if (Object.keys(labels).length === 0)\n {\n if (debug)\n {\n syncLogger.warn('Section has no valid labels');\n }\n return false;\n }\n\n // Sync only this section\n const result = await syncSection(\n { section: sectionName, labels },\n { verbose: debug, updateExisting: true }\n );\n\n if (debug || result.created > 0 || result.updated > 0)\n {\n syncLogger.info(`[${sectionName}] Incremental sync completed`, {\n created: result.created,\n updated: result.updated,\n unchanged: result.unchanged,\n errors: result.errors.length\n });\n }\n\n // Log errors if any\n if (result.errors.length > 0)\n {\n result.errors.forEach((error) =>\n {\n syncLogger.error(`[${sectionName}] ${error.key}: ${error.error}`);\n });\n }\n\n return true;\n }\n catch (error)\n {\n if (debug)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn('Incremental sync failed', err);\n }\n return false;\n }\n}\n\n/**\n * Load labels from a section directory\n *\n * Extracted from loadLabelsFromJson for reuse\n */\nfunction loadSectionLabels(sectionPath: string): Record<string, any>\n{\n const labels: Record<string, any> = {};\n\n if (!existsSync(sectionPath))\n {\n return labels;\n }\n\n try\n {\n const entries = readdirSync(sectionPath);\n\n for (const entry of entries)\n {\n const filePath = join(sectionPath, entry);\n const stat = statSync(filePath);\n\n if (stat.isFile() && extname(entry) === '.json')\n {\n try\n {\n const content = readFileSync(filePath, 'utf-8');\n const data = JSON.parse(content);\n\n // Merge labels from this file\n if (typeof data === 'object' && data !== null)\n {\n Object.assign(labels, data);\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn(`Failed to parse ${filePath}`, err);\n }\n }\n }\n }\n catch (error)\n {\n const err = error instanceof Error ? error : new Error(String(error));\n syncLogger.warn(`Failed to read section ${sectionPath}`, err);\n }\n\n return labels;\n}"],"mappings":"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","names":["detectBrowserLanguage","serial","integer","text","timestamp","index","serial","integer","text","timestamp","index","serial","text","jsonb","timestamp","index","unique","serial","text","jsonb","timestamp","unique","index","serial","text","jsonb","integer","timestamp","index","unique","serial","text","jsonb","timestamp","integer","unique","index","serial","integer","text","jsonb","timestamp","index","serial","integer","text","jsonb","timestamp","index","findOne","findMany","create","updateOne","findMany","findOne","updateOne","create","findOne","findMany","deleteOne","deleteMany","eq","and","findOne","and","eq","upsert","findMany","deleteOne","deleteMany","findOne","findMany","deleteOne","deleteMany","upsertHelper","eq","and","findBySection","findOne","and","eq","upsert","upsertHelper","findMany","deleteOne","deleteMany","timestamp","join","extname","existsSync","readdirSync","readFileSync","statSync","join","existsSync","loadSectionLabels","readdirSync","statSync","extname","readFileSync","text","sectionData","t","sectionsMap"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfn/cms",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.86",
|
|
4
4
|
"description": "SPFN CMS - Content Management System with type-safe labels and Next.js integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"drizzle-orm": "^0.44.7",
|
|
84
84
|
"next": "^15.0.0",
|
|
85
85
|
"react": "^18.0.0 || ^19.0.0",
|
|
86
|
-
"@spfn/core": "0.1.0-alpha.
|
|
86
|
+
"@spfn/core": "0.1.0-alpha.86"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@sinclair/typebox": "^0.34.0",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"tsx": "^4.19.2",
|
|
103
103
|
"typescript": "^5",
|
|
104
104
|
"vitest": "^4.0.6",
|
|
105
|
-
"spfn": "0.1.0-alpha.
|
|
105
|
+
"spfn": "0.1.0-alpha.86"
|
|
106
106
|
},
|
|
107
107
|
"scripts": {
|
|
108
108
|
"build": "npm run db:generate && tsup",
|