Merge pull request 'auto: PR for feat/zip-validation-phase4' (#25) from feat/zip-validation-phase4 into main
CI / test (push) Successful in 47s
CI / test (push) Successful in 47s
This commit was merged in pull request #25.
This commit is contained in:
@@ -5,6 +5,8 @@ export type BuildZipInput = {
|
|||||||
css: string
|
css: string
|
||||||
js: string
|
js: string
|
||||||
assets?: Record<string, Uint8Array>
|
assets?: Record<string, Uint8Array>
|
||||||
|
// root-level extra files like favicon.ico, site.webmanifest, robots.txt
|
||||||
|
extras?: Record<string, Uint8Array>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildZip(input: BuildZipInput): Promise<Uint8Array> {
|
export async function buildZip(input: BuildZipInput): Promise<Uint8Array> {
|
||||||
@@ -19,6 +21,22 @@ export async function buildZip(input: BuildZipInput): Promise<Uint8Array> {
|
|||||||
zip.file(path, buf as unknown as Buffer)
|
zip.file(path, buf as unknown as Buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (input.extras) {
|
||||||
|
// validate manifest if present
|
||||||
|
if (Object.prototype.hasOwnProperty.call(input.extras, 'site.webmanifest')) {
|
||||||
|
try {
|
||||||
|
const manifestBytes = input.extras['site.webmanifest']
|
||||||
|
const manifestStr = Buffer.from(manifestBytes).toString('utf8')
|
||||||
|
JSON.parse(manifestStr)
|
||||||
|
} catch {
|
||||||
|
throw new Error('Invalid site.webmanifest JSON')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [path, bytes] of Object.entries(input.extras)) {
|
||||||
|
const buf = Buffer.from(bytes)
|
||||||
|
zip.file(path, buf as unknown as Buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
const content = await zip.generateAsync({ type: 'uint8array' })
|
const content = await zip.generateAsync({ type: 'uint8array' })
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import JSZip from 'jszip'
|
||||||
|
import { buildZip } from '@/lib/exporter/zip'
|
||||||
|
import type { BuildZipInput } from '@/lib/exporter/zip'
|
||||||
|
|
||||||
|
const TE = new TextEncoder()
|
||||||
|
|
||||||
|
describe('Export ZIP 검증 강화 4차: 루트 파일(favicon/manifest/robots)', () => {
|
||||||
|
it('루트 파일들을 포함한다: favicon.ico / site.webmanifest / robots.txt', async () => {
|
||||||
|
const manifest = {
|
||||||
|
name: 'Landing Builder',
|
||||||
|
short_name: 'LB',
|
||||||
|
start_url: '/',
|
||||||
|
display: 'standalone',
|
||||||
|
icons: [{ src: '/favicon.png', sizes: '512x512', type: 'image/png' }],
|
||||||
|
}
|
||||||
|
const input: BuildZipInput = {
|
||||||
|
html: '<!DOCTYPE html><html><head><title>a</title></head><body>ok</body></html>',
|
||||||
|
css: 'body{color:#111}',
|
||||||
|
js: 'console.log("ok")',
|
||||||
|
assets: {},
|
||||||
|
extras: {
|
||||||
|
'favicon.ico': new Uint8Array([0, 1, 2, 3]),
|
||||||
|
'site.webmanifest': TE.encode(JSON.stringify(manifest)),
|
||||||
|
'robots.txt': TE.encode('User-agent: *\nDisallow:'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const zipBytes = await buildZip(input)
|
||||||
|
|
||||||
|
const zip = await JSZip.loadAsync(zipBytes)
|
||||||
|
const names = Object.keys(zip.files)
|
||||||
|
expect(names).toContain('favicon.ico')
|
||||||
|
expect(names).toContain('site.webmanifest')
|
||||||
|
expect(names).toContain('robots.txt')
|
||||||
|
|
||||||
|
const robots = await zip.files['robots.txt'].async('string')
|
||||||
|
expect(robots).toMatch(/User-agent: \*/)
|
||||||
|
|
||||||
|
const parsed = JSON.parse(await zip.files['site.webmanifest'].async('string'))
|
||||||
|
expect(parsed.name).toBe('Landing Builder')
|
||||||
|
expect(parsed.icons[0].type).toBe('image/png')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('잘못된 manifest JSON이면 실패한다', async () => {
|
||||||
|
const badInput: BuildZipInput = {
|
||||||
|
html: '<!DOCTYPE html><html><head><title>a</title></head><body>ok</body></html>',
|
||||||
|
css: 'body{color:#111}',
|
||||||
|
js: 'console.log("ok")',
|
||||||
|
extras: { 'site.webmanifest': TE.encode('{ invalid json') },
|
||||||
|
}
|
||||||
|
await expect(buildZip(badInput)).rejects.toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user