From 1a405a54fa3dc3605868d5ae8d14a4a307ea6228 Mon Sep 17 00:00:00 2001 From: Jaybe Date: Sat, 15 Nov 2025 02:26:54 +0900 Subject: [PATCH] =?UTF-8?q?feat(export):=20extras=20=EC=98=A4=EB=B2=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=93=9C=20=EC=A7=80=EC=9B=90(robots/manifes?= =?UTF-8?q?t)=20TDD=20=EB=B0=8F=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/exporter/extras.overrides.test.ts | 47 +++++++++++++++++++++++++++ lib/exporter/extras.ts | 23 +++++++++++-- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 lib/exporter/extras.overrides.test.ts diff --git a/lib/exporter/extras.overrides.test.ts b/lib/exporter/extras.overrides.test.ts new file mode 100644 index 0000000..e1c6bbd --- /dev/null +++ b/lib/exporter/extras.overrides.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest' +import { pageSchema, type Page } from '@/lib/schema/page' +import { buildExtrasForPage } from '@/lib/exporter/extras' + +function makePage(): Page { + return pageSchema.parse({ + title: 'Overrides Demo', + locale: 'en', + theme: { primaryColor: '#123456', fontFamily: 'Inter' }, + sections: [ + { type: 'hero', props: { heading: 'Hello', subheading: 'World', imageUrl: 'https://img.example/hero.png' } }, + ], + form: { actionUrl: '', fields: [], spamProtection: { honeypotFieldName: '_hp', minSubmitSeconds: 2 } }, + seo: { title: 'Overrides', description: 'Desc' }, + }) +} + +describe('extras overrides', () => { + it('allows overriding robots.txt content', () => { + const page = makePage() + const extras = buildExtrasForPage(page, { + robotsText: 'User-agent: *\nDisallow: /private', + }) + const robots = new TextDecoder().decode(extras['robots.txt']) + expect(robots).toContain('Disallow: /private') + }) + + it('allows overriding manifest fields (name, short_name, start_url, display, theme_color)', () => { + const page = makePage() + const extras = buildExtrasForPage(page, { + manifest: { + name: 'Custom Name', + short_name: 'Custom', + start_url: '/home', + display: 'minimal-ui', + theme_color: '#abcdef', + }, + }) + const manifestRaw = new TextDecoder().decode(extras['site.webmanifest']) + const manifest = JSON.parse(manifestRaw) + expect(manifest.name).toBe('Custom Name') + expect(manifest.short_name).toBe('Custom') + expect(manifest.start_url).toBe('/home') + expect(manifest.display).toBe('minimal-ui') + expect(manifest.theme_color).toBe('#abcdef') + }) +}) diff --git a/lib/exporter/extras.ts b/lib/exporter/extras.ts index e112772..8b9fa4d 100644 --- a/lib/exporter/extras.ts +++ b/lib/exporter/extras.ts @@ -1,13 +1,25 @@ import type { Page } from '@/lib/schema/page' +type ExtrasOverrides = { + robotsText?: string + manifest?: Partial<{ + name: string + short_name: string + start_url: string + display: string + theme_color: string + }> +} + // Sheets 템플릿 등 ZIP 루트/서브 경로에 포함할 추가 파일(extras)을 구성한다. // - actionUrl이 비어있을 때만 Google Apps Script 템플릿을 포함한다. -export function buildExtrasForPage(page: Page): Record { +export function buildExtrasForPage(page: Page, overrides?: ExtrasOverrides): Record { const extras: Record = {} const te = new TextEncoder() // Always provide a baseline robots.txt - const robots = `User-agent: *\nDisallow:` + const robotsDefault = `User-agent: *\nDisallow:` + const robots = overrides?.robotsText ?? robotsDefault extras['robots.txt'] = te.encode(robots) // Provide a minimal site.webmanifest derived from Page @@ -20,6 +32,13 @@ export function buildExtrasForPage(page: Page): Record { start_url: '/', display: 'standalone', theme_color: page.theme?.primaryColor ?? '#000000', + ...((overrides?.manifest as object) || {}), + } as { + name: string + short_name: string + start_url: string + display: string + theme_color: string } extras['site.webmanifest'] = te.encode(JSON.stringify(manifest)) } catch {}