mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-27 01:21:20 -08:00
ci(pages): [CI-02] validate staged artifacts and preserve legacy demo paths
This commit is contained in:
1 parent
b1e0b976b0
commit
72d2b9c36b
20 files changed
+6120
-13
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
export interface PagesContract { domain: string, requiredPaths: string[] }
|
||||
export interface FileIdentity { bytes: number, sha256: string }
|
||||
|
||||
export function digest(file: string): FileIdentity {
|
||||
const bytes = fs.readFileSync(file)
|
||||
return { bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') }
|
||||
}
|
||||
|
||||
export function inside(root: string, relative: string): string {
|
||||
assert(relative && !relative.includes('\\') && !path.isAbsolute(relative), `Invalid artifact path: ${relative}`)
|
||||
const result = path.resolve(root, relative)
|
||||
const actual = path.relative(path.resolve(root), result)
|
||||
assert(actual && actual !== '..' && !actual.startsWith(`..${path.sep}`) && !path.isAbsolute(actual), `Artifact path escaped root: ${relative}`)
|
||||
return result
|
||||
}
|
||||
|
||||
export function inventory(root: string): Record<string, FileIdentity> {
|
||||
assert(fs.lstatSync(root).isDirectory() && !fs.lstatSync(root).isSymbolicLink(), 'Artifact root must be a real directory')
|
||||
const files: Record<string, FileIdentity> = {}
|
||||
function walk(directory: string) {
|
||||
for (const name of fs.readdirSync(directory).sort()) {
|
||||
const file = path.join(directory, name)
|
||||
const stat = fs.lstatSync(file)
|
||||
assert(!stat.isSymbolicLink(), `Pages cannot contain symbolic links: ${file}`)
|
||||
if (stat.isDirectory()) {
|
||||
walk(file)
|
||||
}
|
||||
else {
|
||||
assert(stat.isFile() && stat.nlink === 1, `Pages requires ordinary files without hard links: ${file}`)
|
||||
const relative = path.relative(root, file).split(path.sep).join('/')
|
||||
files[relative] = digest(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(root)
|
||||
assert(Object.values(files).reduce((total, file) => total + file.bytes, 0) < 10_000_000_000, 'Pages artifact exceeds the 10GB archive ceiling')
|
||||
return files
|
||||
}
|
||||
|
||||
export function validateArtifact(root: string, contract: PagesContract) {
|
||||
const files = inventory(root)
|
||||
for (const relative of contract.requiredPaths) {
|
||||
inside(root, relative)
|
||||
assert(Object.hasOwn(files, relative), `Missing required Pages route: ${relative}`)
|
||||
assert(relative === '.nojekyll' || files[relative]!.bytes > 0, `Empty Pages route: ${relative}`)
|
||||
}
|
||||
assert.equal(fs.readFileSync(inside(root, 'CNAME'), 'utf8').trim(), contract.domain, 'Pages domain changed')
|
||||
return files
|
||||
}
|
||||
|
||||
export function compareCompiled(root: string, site: string, names: string[]) {
|
||||
const checked: string[] = []
|
||||
for (const name of names) {
|
||||
for (const suffix of ['.js', '.legacy.js', '.mjs']) {
|
||||
const relative = `${name}${suffix}`
|
||||
const source = inside(root, `packages/${name}/dist/${relative}`)
|
||||
const target = inside(site, `compiled/${relative}`)
|
||||
assert.equal(digest(target).sha256, digest(source).sha256, `Stale compiled Pages artifact: ${relative}`)
|
||||
checked.push(relative)
|
||||
}
|
||||
}
|
||||
return checked
|
||||
}
|
||||
Reference in new issue
Block a user