接口开始5%

This commit is contained in:
rain
2026-07-22 17:31:33 +08:00
parent eced3d1e6e
commit 9b0ad62df4
426 changed files with 20111 additions and 28472 deletions
@@ -0,0 +1,196 @@
import path from 'node:path'
const assertOnlyFields = (value, fields, label) => {
for (const field of Object.keys(value)) {
if (!fields.has(field)) throw new Error(`${label} contains unknown field: ${field}`)
}
}
const requireObject = (value, label) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`${label} must be an object`)
}
return value
}
const requireString = (value, label) => {
if (typeof value !== 'string' || value.trim() === '') {
throw new Error(`${label} must be a non-empty string`)
}
return value
}
const requirePositiveInteger = (value, label) => {
if (!Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`)
return value
}
const requireIntegerInRange = (value, minimum, maximum, label) => {
if (!Number.isInteger(value) || value < minimum || value > maximum) {
throw new Error(`${label} must be an integer from ${minimum} to ${maximum}`)
}
return value
}
const requireBoolean = (value, label) => {
if (typeof value !== 'boolean') throw new Error(`${label} must be boolean`)
return value
}
const requireExact = (value, expected, label) => {
if (value !== expected) throw new Error(`${label} must be ${expected}`)
return value
}
const resolveInsideWorkspace = (workspace, relativePath, label) => {
requireString(relativePath, label)
const absolutePath = path.resolve(workspace, relativePath)
const relative = path.relative(workspace, absolutePath)
if (relative.startsWith('..') || path.isAbsolute(relative)) {
throw new Error(`${label} escapes workspace: ${relativePath}`)
}
return absolutePath
}
const validatePixels = (value, label) => {
const pixels = requireObject(value, label)
assertOnlyFields(pixels, new Set(['width', 'height']), label)
requirePositiveInteger(pixels.width, `${label}.width`)
requirePositiveInteger(pixels.height, `${label}.height`)
}
const validateAlphaAndEdge = (asset, id) => {
const alpha = requireObject(asset.alpha, `${id}.alpha`)
assertOnlyFields(alpha, new Set(['required', 'transparentOuterPadding', 'cornerMaxAlpha']), `${id}.alpha`)
requireExact(alpha.required, true, `${id}.alpha.required`)
requireIntegerInRange(alpha.transparentOuterPadding, 0, 4096, `${id}.alpha.transparentOuterPadding`)
requireIntegerInRange(alpha.cornerMaxAlpha, 0, 255, `${id}.alpha.cornerMaxAlpha`)
const edge = requireObject(asset.edge, `${id}.edge`)
assertOnlyFields(
edge,
new Set(['forbidChromaResidue', 'forbidLightFringe', 'premultipliedAlphaCheck']),
`${id}.edge`,
)
requireBoolean(edge.forbidChromaResidue, `${id}.edge.forbidChromaResidue`)
requireBoolean(edge.forbidLightFringe, `${id}.edge.forbidLightFringe`)
requireBoolean(edge.premultipliedAlphaCheck, `${id}.edge.premultipliedAlphaCheck`)
}
const validateProcessing = (processing, id) => {
requireObject(processing, `${id}.processing`)
const mode = requireString(processing.mode, `${id}.processing.mode`)
if (mode === 'chroma-stretch') {
assertOnlyFields(
processing,
new Set(['mode', 'capWidth', 'keyColor', 'keyTolerance', 'paletteColors', 'indexedPng']),
`${id}.processing`,
)
requirePositiveInteger(processing.capWidth, `${id}.processing.capWidth`)
if (!/^#[A-Fa-f0-9]{6}$/.test(processing.keyColor)) {
throw new Error(`${id}.processing.keyColor must be a six-digit RGB color`)
}
requireIntegerInRange(processing.keyTolerance, 0, 441, `${id}.processing.keyTolerance`)
if (processing.paletteColors !== undefined) {
requireIntegerInRange(processing.paletteColors, 2, 256, `${id}.processing.paletteColors`)
}
if (processing.indexedPng !== undefined) {
requireBoolean(processing.indexedPng, `${id}.processing.indexedPng`)
}
return true
}
if (mode === 'opaque-resize') {
assertOnlyFields(processing, new Set(['mode', 'resample', 'outputMode']), `${id}.processing`)
requireExact(processing.resample, 'lanczos', `${id}.processing.resample`)
requireExact(processing.outputMode, 'RGB', `${id}.processing.outputMode`)
return false
}
if (mode === 'opaque-cover-crop') {
assertOnlyFields(processing, new Set(['mode', 'resample', 'anchor', 'outputMode']), `${id}.processing`)
requireExact(processing.resample, 'lanczos', `${id}.processing.resample`)
requireExact(processing.anchor, 'center', `${id}.processing.anchor`)
requireExact(processing.outputMode, 'RGB', `${id}.processing.outputMode`)
return false
}
if (mode === 'warm-gold-frame-extract') {
assertOnlyFields(
processing,
new Set([
'mode',
'borderBand',
'redGreenMin',
'greenBlueMin',
'redBlueMin',
'redMaxExclusive',
'blueMaxExclusive',
'alphaOffset',
'alphaScale',
'outputMode',
]),
`${id}.processing`,
)
requirePositiveInteger(processing.borderBand, `${id}.processing.borderBand`)
for (const field of ['redGreenMin', 'greenBlueMin', 'redBlueMin', 'alphaOffset']) {
requireIntegerInRange(processing[field], 0, 255, `${id}.processing.${field}`)
}
for (const field of ['redMaxExclusive', 'blueMaxExclusive']) {
requireIntegerInRange(processing[field], 1, 256, `${id}.processing.${field}`)
}
requirePositiveInteger(processing.alphaScale, `${id}.processing.alphaScale`)
requireExact(processing.outputMode, 'RGBA', `${id}.processing.outputMode`)
return true
}
throw new Error(`${id} has unsupported processing.mode: ${mode}`)
}
export const validateAssetBuildManifest = (manifest, workspace) => {
requireObject(manifest, 'manifest')
assertOnlyFields(manifest, new Set(['schemaVersion', 'kind', 'family', 'assets']), 'manifest')
if (manifest.schemaVersion !== 3) throw new Error('schemaVersion must be 3')
if (manifest.kind !== 'asset-build-manifest') throw new Error('kind must be asset-build-manifest')
requireString(manifest.family, 'family')
if (!Array.isArray(manifest.assets) || manifest.assets.length === 0) {
throw new Error('assets must be a non-empty array')
}
const ids = new Set()
const outputs = new Set()
for (const asset of manifest.assets) {
requireObject(asset, 'asset')
const processing = requireObject(asset.processing, 'asset.processing')
const mode = requireString(processing.mode, 'asset.processing.mode')
const transparent = ['chroma-stretch', 'warm-gold-frame-extract'].includes(mode)
const fields = new Set(['id', 'source', 'output', 'sourcePixels', 'outputPixels', 'quality', 'processing'])
if (transparent) {
fields.add('alpha')
fields.add('edge')
}
assertOnlyFields(asset, fields, 'asset')
const id = requireString(asset.id, 'asset.id')
if (ids.has(id)) throw new Error(`duplicate asset id: ${id}`)
ids.add(id)
resolveInsideWorkspace(workspace, asset.source, `${id}.source`)
resolveInsideWorkspace(workspace, asset.output, `${id}.output`)
if (outputs.has(asset.output)) throw new Error(`duplicate output: ${asset.output}`)
outputs.add(asset.output)
validatePixels(asset.sourcePixels, `${id}.sourcePixels`)
validatePixels(asset.outputPixels, `${id}.outputPixels`)
const processingNeedsAlpha = validateProcessing(processing, id)
if (processingNeedsAlpha) validateAlphaAndEdge(asset, id)
const quality = requireObject(asset.quality, `${id}.quality`)
assertOnlyFields(quality, new Set(['maxBytes', 'colorSpace']), `${id}.quality`)
requirePositiveInteger(quality.maxBytes, `${id}.quality.maxBytes`)
requireExact(quality.colorSpace, 'sRGB', `${id}.quality.colorSpace`)
}
return manifest
}