mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-22 05:11:20 -08:00
refactor(build): [SITE-BUILD-01] stage outputs and stabilize documentation builds
This commit is contained in:
1 parent
078cd9ef72
commit
a468cb2c39
72 files changed
+2396
-748
No files matched your search
@@ -0,0 +1,144 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { ownedPath, sha256 } from '../documentation/files.ts'
|
||||
|
||||
const targets = {
|
||||
i18n: ['packages/artplayer/dist/i18n', 'docs/compiled/i18n'],
|
||||
docs: ['docs/document'],
|
||||
} as const
|
||||
|
||||
export function treeFingerprint(directory: string): string | null {
|
||||
if (!fs.existsSync(directory))
|
||||
return null
|
||||
const entries: string[] = []
|
||||
function visit(current: string, relative: string): void {
|
||||
assert(
|
||||
fs.lstatSync(current).isDirectory(),
|
||||
'Artifact root must be a directory',
|
||||
)
|
||||
for (const item of fs
|
||||
.readdirSync(current, { withFileTypes: true })
|
||||
.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
const name = `${relative}/${item.name}`
|
||||
assert(
|
||||
!item.isSymbolicLink(),
|
||||
'Artifact tree must not contain symbolic links',
|
||||
)
|
||||
if (item.isDirectory()) {
|
||||
entries.push(`directory:${name}`)
|
||||
visit(path.join(current, item.name), name)
|
||||
}
|
||||
else {
|
||||
assert(item.isFile(), 'Unexpected artifact entry')
|
||||
entries.push(
|
||||
`${name}:${sha256(fs.readFileSync(path.join(current, item.name)).toString('base64'))}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(directory, '')
|
||||
return sha256(entries.join('\n'))
|
||||
}
|
||||
|
||||
export async function stageArtifacts(
|
||||
root: string,
|
||||
kind: keyof typeof targets,
|
||||
produce: (stages: string[]) => Promise<void>,
|
||||
rename = fs.renameSync,
|
||||
): Promise<void> {
|
||||
const cache = ownedPath(root, 'refactor/.cache/site-build')
|
||||
fs.mkdirSync(cache, { recursive: true })
|
||||
const lock = path.join(cache, `${kind}.lock`)
|
||||
const handle = fs.openSync(lock, 'wx')
|
||||
let run: string | undefined
|
||||
let retain = false
|
||||
try {
|
||||
run = fs.mkdtempSync(path.join(cache, `${kind}-`))
|
||||
const rows = targets[kind].map((relative, index) => {
|
||||
const destination = ownedPath(root, relative)
|
||||
const stage = path.join(run!, `output-${index}`)
|
||||
fs.mkdirSync(stage)
|
||||
return {
|
||||
relative,
|
||||
destination,
|
||||
stage,
|
||||
backup: path.join(run!, `backup-${index}`),
|
||||
before: treeFingerprint(destination),
|
||||
saved: false,
|
||||
installed: false,
|
||||
after: '',
|
||||
}
|
||||
})
|
||||
await produce(rows.map(row => row.stage))
|
||||
for (const row of rows) {
|
||||
assert(fs.readdirSync(row.stage).length, 'Build produced no artifacts')
|
||||
row.after = treeFingerprint(row.stage)!
|
||||
assert.equal(
|
||||
treeFingerprint(ownedPath(root, row.relative)),
|
||||
row.before,
|
||||
'Artifact output changed while building',
|
||||
)
|
||||
}
|
||||
try {
|
||||
for (const row of rows) {
|
||||
assert.equal(ownedPath(root, row.relative), row.destination)
|
||||
assert.equal(
|
||||
treeFingerprint(row.destination),
|
||||
row.before,
|
||||
'Artifact output changed during replacement',
|
||||
)
|
||||
fs.mkdirSync(path.dirname(row.destination), { recursive: true })
|
||||
if (row.before !== null) {
|
||||
rename(row.destination, row.backup)
|
||||
row.saved = true
|
||||
}
|
||||
rename(row.stage, row.destination)
|
||||
row.installed = true
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
const errors = [error]
|
||||
for (const row of [...rows].reverse()) {
|
||||
try {
|
||||
if (row.installed) {
|
||||
assert.equal(
|
||||
treeFingerprint(row.destination),
|
||||
row.after,
|
||||
'Concurrent output edit prevents rollback',
|
||||
)
|
||||
fs.renameSync(row.destination, row.stage)
|
||||
}
|
||||
if (row.saved) {
|
||||
assert(
|
||||
!fs.existsSync(row.destination),
|
||||
'Rollback target is occupied',
|
||||
)
|
||||
fs.renameSync(row.backup, row.destination)
|
||||
}
|
||||
}
|
||||
catch (failure) {
|
||||
retain = true
|
||||
errors.push(failure)
|
||||
}
|
||||
}
|
||||
throw new AggregateError(
|
||||
errors,
|
||||
`Artifact replacement failed. ${retain ? `Inspect retained backups: ${run}` : 'Previous outputs restored.'}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
if (run && !retain) {
|
||||
assert.equal(path.dirname(run), cache)
|
||||
assert.equal(fs.realpathSync(run), run)
|
||||
fs.rmSync(run, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
finally {
|
||||
fs.closeSync(handle)
|
||||
fs.unlinkSync(lock)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user