diff --git a/lib/exporter/extras.ts b/lib/exporter/extras.ts new file mode 100644 index 0000000..84cd4dd --- /dev/null +++ b/lib/exporter/extras.ts @@ -0,0 +1,64 @@ +import type { Page } from '@/lib/schema/page' + +export type ExtrasOverrides = { + robotsText?: string + manifest?: Partial<{ + name: string + short_name: string + start_url: string + display: string + theme_color: string + }> +} + +function toBytes(s: string): Uint8Array { + return new TextEncoder().encode(s) +} + +function buildRobots(base?: string): string { + if (base && base.trim().length > 0) return base + return 'User-agent: *\n' +} + +function deriveManifest(page: Page) { + const name = page.title || 'Landing' + const short_name = name.length > 12 ? name.slice(0, 12) : name + const start_url = '/' + const display = 'standalone' + const theme_color = page.theme?.primaryColor || '#0ea5e9' + return { name, short_name, start_url, display, theme_color } +} + +export function buildExtrasForPage(page: Page, overrides?: ExtrasOverrides): Record { + const out: Record = {} + + // robots.txt + const robotsText = buildRobots(overrides?.robotsText) + out['robots.txt'] = toBytes(robotsText) + + // site.webmanifest + const manifestBase = deriveManifest(page) + const manifestMerged = { ...manifestBase, ...(overrides?.manifest || {}) } + const manifestJson = JSON.stringify(manifestMerged, null, 2) + out['site.webmanifest'] = toBytes(manifestJson) + + // Google Sheets 템플릿: actionUrl 미지정 시 포함 + const action = page.form?.actionUrl?.trim() + const hasAction = !!(action && action.length > 0) + if (!hasAction) { + const code = `/** + * Google Apps Script Web App for form submissions + * - Deploy as Web App and set URL as form action if needed. + */ +function doPost(e){ + // TODO: handle submission to Google Sheets + return ContentService.createTextOutput('OK') +} +` + const readme = `Google Sheets Apps Script Template\n\n1) Apps Script에서 새 프로젝트를 만들고 아래 Code.gs를 붙여넣으세요.\n2) 배포 > 웹 앱으로 배포 후 URL을 복사해 사용하세요.` + out['sheets/Code.gs'] = toBytes(code) + out['sheets/README.txt'] = toBytes(readme) + } + + return out +} diff --git a/lib/exporter/manifest.robots.auto.test.ts b/lib/exporter/manifest.robots.auto.test.ts new file mode 100644 index 0000000..17b21e7 --- /dev/null +++ b/lib/exporter/manifest.robots.auto.test.ts @@ -0,0 +1,32 @@ +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: 'Auto Extras', + locale: 'en', + theme: { primaryColor: '#2563eb', fontFamily: 'Inter' }, + sections: [ { type: 'hero', props: { heading: 'Hello', subheading: 'World', imageUrl: 'https://img/h.png' } } ], + form: { actionUrl: '', fields: [], spamProtection: { honeypotFieldName: '_hp', minSubmitSeconds: 2 } }, + seo: { title: 't', description: 'd' }, + }) +} + +describe('extras 자동 생성', () => { + it('robots.txt와 site.webmanifest가 extras에 포함된다', () => { + const page = makePage() + const extras = buildExtrasForPage(page) + const keys = Object.keys(extras) + expect(keys).toContain('robots.txt') + expect(keys).toContain('site.webmanifest') + + const robots = Buffer.from(extras['robots.txt']).toString('utf8') + expect(robots).toContain('User-agent: *') + + const manifestStr = Buffer.from(extras['site.webmanifest']).toString('utf8') + const manifest = JSON.parse(manifestStr) + expect(manifest.start_url).toBeDefined() + expect(manifest.display).toBeDefined() + }) +})