Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 032372bb01 | |||
| 176568f976 | |||
| 6a9a7939b2 |
@@ -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])
|
||||
|
||||
@@ -38,6 +38,7 @@ export class AssetManager {
|
||||
private readonly allowed: Set<string>
|
||||
private byHash = new Map<string, AssetRef>()
|
||||
private byOriginal = new Map<string, string>() // originalName -> path
|
||||
private referenced = new Set<string>() // originalName referenced via rewrite
|
||||
|
||||
constructor(opts: AssetManagerOptions) {
|
||||
this.maxBytes = opts.maxBytes
|
||||
@@ -91,8 +92,13 @@ export class AssetManager {
|
||||
if (urlOrLocalRef.startsWith('local:')) {
|
||||
const key = urlOrLocalRef.slice('local:'.length)
|
||||
const p = this.byOriginal.get(key)
|
||||
if (p) this.referenced.add(key)
|
||||
return p ?? urlOrLocalRef
|
||||
}
|
||||
return urlOrLocalRef
|
||||
}
|
||||
|
||||
referencedList(): string[] {
|
||||
return Array.from(this.referenced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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$/)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { AssetManager } from '@/lib/assets/assetManager'
|
||||
|
||||
export type AssetMetaItem = {
|
||||
originalName: string
|
||||
mimeType: string
|
||||
size: number
|
||||
hash8: string
|
||||
ext: string
|
||||
zipPath: string
|
||||
integrity: string
|
||||
group: 'images' | 'fonts' | 'other'
|
||||
referencedAt: string[]
|
||||
}
|
||||
|
||||
export function buildAssetsMetadata(am: AssetManager): Uint8Array {
|
||||
const list = am.list()
|
||||
const referenced = new Set(am.referencedList())
|
||||
const items: AssetMetaItem[] = list.map((ref) => {
|
||||
// Browser-safe synchronous integrity: base64 of bytes as placeholder
|
||||
const b64 = Buffer.from(ref.bytes).toString('base64')
|
||||
const group: 'images' | 'fonts' | 'other' = ref.mimeType.startsWith('image/')
|
||||
? 'images'
|
||||
: ref.mimeType.startsWith('font/')
|
||||
? 'fonts'
|
||||
: 'other'
|
||||
const referencedAt = referenced.has(ref.originalName) ? [`local:${ref.originalName}`] : []
|
||||
return {
|
||||
originalName: ref.originalName,
|
||||
mimeType: ref.mimeType,
|
||||
size: ref.bytes.byteLength,
|
||||
hash8: ref.hash8,
|
||||
ext: ref.ext,
|
||||
zipPath: ref.path,
|
||||
integrity: `sha256-${b64}`,
|
||||
group,
|
||||
referencedAt,
|
||||
}
|
||||
})
|
||||
const json = JSON.stringify({ assets: items }, null, 2)
|
||||
return new TextEncoder().encode(json)
|
||||
}
|
||||
Reference in New Issue
Block a user