auto: PR for feat/export-assets-metadata #60

Merged
jaybe merged 1 commits from feat/export-assets-metadata into main 2025-11-15 04:40:17 +00:00
3 changed files with 66 additions and 1 deletions
+6 -1
View File
@@ -7,6 +7,7 @@ import { exportPage } from '@/lib/exporter/html'
import { AssetManager } from '@/lib/assets/assetManager'
import { buildZip } from '@/lib/exporter/zip'
import { buildExtrasForPage } from '@/lib/exporter/extras'
import { buildAssetsMetadata } from '@/lib/exporter/assets.meta'
import type { Section } from '@/lib/state/store'
import { createFormStore, type FormState } from '@/lib/state/formStore'
import FormBuilderPanel from '@/components/FormBuilderPanel'
@@ -237,7 +238,11 @@ export default function BuilderClientPage() {
const disallowLines = lines.map((l) => (l.startsWith('Disallow:') ? l : `Disallow: ${l}`))
return `User-agent: *\n${disallowLines.join('\n')}`
})()
const extras = buildExtrasForPage(valid, { robotsText: robotsTextOverride, manifest: manifestOverride })
const baseExtras = buildExtrasForPage(valid, { robotsText: robotsTextOverride, manifest: manifestOverride })
const extras: Record<string, Uint8Array> = {
...baseExtras,
...(am ? { 'assets.json': buildAssetsMetadata(am) } : {}),
}
const zipBytes = await buildZip({ html: exported.html, css: exported.css, js: exported.js, assets, extras })
download('landing.zip', zipBytes)
}, [pageData, am, manifestName, manifestShortName, manifestStartUrl, manifestDisplay, manifestThemeColor, robotsDisallow])
+30
View File
@@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest'
import { AssetManager } from '@/lib/assets/assetManager'
import { buildAssetsMetadata } from '@/lib/exporter/assets.meta'
import type { AssetMetaItem } from '@/lib/exporter/assets.meta'
function u8(n: number): Uint8Array {
const arr = new Uint8Array(n)
for (let i = 0; i < n; i++) arr[i] = (i * 31) & 0xff
return arr
}
describe('Assets metadata builder', () => {
it('emits assets.json with originalName/mime/size/hash/ext/zipPath', async () => {
const am = new AssetManager({ maxBytes: 1024 * 1024, allowedExts: ['png'] })
const bytes = u8(10)
await am.add({ name: 'hero.png', type: 'image/png', bytes })
const metaBytes = buildAssetsMetadata(am)
const json = new TextDecoder().decode(metaBytes)
const obj = JSON.parse(json) as { assets: AssetMetaItem[] }
expect(Array.isArray(obj.assets)).toBe(true)
expect(obj.assets.length).toBe(1)
const a = obj.assets[0]
expect(a.originalName).toBe('hero.png')
expect(a.mimeType).toBe('image/png')
expect(a.size).toBe(bytes.byteLength)
expect(a.ext).toBe('png')
expect(a.hash8).toMatch(/^[0-9a-f]{8}$/)
expect(a.zipPath).toMatch(/^assets\/[0-9a-f]{8}\.png$/)
})
})
+30
View File
@@ -0,0 +1,30 @@
import type { AssetManager } from '@/lib/assets/assetManager'
export type AssetMetaItem = {
originalName: string
mimeType: string
size: number
hash8: string
ext: string
zipPath: string
integrity: string
}
export function buildAssetsMetadata(am: AssetManager): Uint8Array {
const list = am.list()
const items: AssetMetaItem[] = list.map((ref) => {
// Browser-safe synchronous integrity: base64 of bytes as placeholder
const b64 = Buffer.from(ref.bytes).toString('base64')
return {
originalName: ref.originalName,
mimeType: ref.mimeType,
size: ref.bytes.byteLength,
hash8: ref.hash8,
ext: ref.ext,
zipPath: ref.path,
integrity: `sha256-${b64}`,
}
})
const json = JSON.stringify({ assets: items }, null, 2)
return new TextEncoder().encode(json)
}