95 lines
3.5 KiB
JavaScript
95 lines
3.5 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { readdir, readFile } from 'node:fs/promises'
|
|
|
|
const dist = new URL('../dist/', import.meta.url)
|
|
|
|
async function filesBelow(directory, prefix = '') {
|
|
const entries = await readdir(directory, { withFileTypes: true })
|
|
const files = []
|
|
for (const entry of entries) {
|
|
const relative = `${prefix}${entry.name}`
|
|
if (entry.isDirectory()) {
|
|
files.push(...(await filesBelow(new URL(`${entry.name}/`, directory), `${relative}/`)))
|
|
} else {
|
|
files.push(relative)
|
|
}
|
|
}
|
|
return files
|
|
}
|
|
|
|
const files = await filesBelow(dist)
|
|
const expectedFonts = ['noto-serif-sc', 'zcool-xiaowei', 'lxgw-wenkai']
|
|
const liyuFont = 'fonts/liyu-shoushu-v0.107.woff2'
|
|
const hongleiFont = 'fonts/honglei-xingshu-jian.woff2'
|
|
|
|
for (const font of expectedFonts) {
|
|
if (!files.some(file => file.startsWith(`assets/${font}-`) && file.endsWith('.woff2'))) {
|
|
throw new Error(`Missing bundled WOFF2 assets for ${font}`)
|
|
}
|
|
if (!files.includes(`fonts/licenses/${font}-OFL.txt`)) {
|
|
throw new Error(`Missing bundled license for ${font}`)
|
|
}
|
|
}
|
|
|
|
if (!files.includes(liyuFont)) throw new Error('Missing bundled WOFF2 asset for liyu-shoushu')
|
|
if (!files.includes('fonts/licenses/liyu-shoushu-OFL.txt')) {
|
|
throw new Error('Missing bundled license for liyu-shoushu')
|
|
}
|
|
const liyuDigest = createHash('sha256')
|
|
.update(await readFile(new URL(liyuFont, dist)))
|
|
.digest('hex')
|
|
if (liyuDigest !== 'b17f8c4fc6612a539d7a46e0eb5f57fdc732cff1d17804563b00a3f0a7eb9ab3') {
|
|
throw new Error(`Unexpected liyu-shoushu WOFF2 digest: ${liyuDigest}`)
|
|
}
|
|
if (!files.includes(hongleiFont)) throw new Error('Missing bundled WOFF2 asset for HongLei XingShu')
|
|
const hongleiDigest = createHash('sha256')
|
|
.update(await readFile(new URL(hongleiFont, dist)))
|
|
.digest('hex')
|
|
if (hongleiDigest !== '6b925c5825bbbc936e37fa1a8db9a81db99a6f09e59e0b2feea2fb6917726b6d') {
|
|
throw new Error(`Unexpected HongLei XingShu WOFF2 digest: ${hongleiDigest}`)
|
|
}
|
|
|
|
const guardVideos = new Map([
|
|
['assets/captain.webm', 'e9d2b416a582c4606cec1d613027e941bfc422f3ad12de24e88980908496d759'],
|
|
['assets/admiral.webm', '7c504469f638f321768edecb3b5ba25d6826737349f3479c89fb1776d35a8672'],
|
|
['assets/general.webm', '869b54a5104e4dfbcc97a6338e8805444e0f0034c6d8075530b2242a2c3436e1'],
|
|
])
|
|
for (const [video, expectedDigest] of guardVideos) {
|
|
if (!files.includes(video)) throw new Error(`Missing bundled membership video: ${video}`)
|
|
const digest = createHash('sha256')
|
|
.update(await readFile(new URL(video, dist)))
|
|
.digest('hex')
|
|
if (digest !== expectedDigest) throw new Error(`Unexpected membership video digest: ${video}`)
|
|
}
|
|
|
|
const legacyWoff = files.find(file => file.endsWith('.woff'))
|
|
if (legacyWoff) throw new Error(`Unexpected legacy WOFF asset: ${legacyWoff}`)
|
|
|
|
const css = (
|
|
await Promise.all(
|
|
files.filter(file => file.endsWith('.css')).map(file => readFile(new URL(file, dist), 'utf8')),
|
|
)
|
|
).join('\n')
|
|
|
|
for (const family of [
|
|
'Noto Serif SC',
|
|
'ZCOOL XiaoWei',
|
|
'LXGW WenKai',
|
|
'Liyu Shoushu',
|
|
'HongLei XingShu',
|
|
]) {
|
|
const declarations = [
|
|
`font-family:${family}`,
|
|
`font-family:${JSON.stringify(family)}`,
|
|
`font-family:'${family}'`,
|
|
]
|
|
if (!declarations.some(declaration => css.includes(declaration))) {
|
|
throw new Error(`Missing @font-face declaration for ${family}`)
|
|
}
|
|
}
|
|
if (!css.includes('font-display:block') || css.includes('font-display:swap')) {
|
|
throw new Error('Bundled fonts must block local fallback while loading')
|
|
}
|
|
|
|
console.log('Verified same-origin component fonts, licenses, and membership videos.')
|