mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 11:56:15 -08:00
96 lines
5.2 KiB
TypeScript
96 lines
5.2 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { spawn } from 'node:child_process'
|
|
import { createHash } from 'node:crypto'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import process from 'node:process'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
export const ecosystemChecks = [
|
|
{ script: 'test:package', args: ['--include=artplayer-plugin-audio-track,artplayer-plugin-hls-control'], packages: ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-audio-track', 'artplayer-plugin-hls-control'] },
|
|
...[
|
|
['ads', 'artplayer-plugin-ads'],
|
|
['ambilight', 'artplayer-plugin-ambilight'],
|
|
['asr', 'artplayer-plugin-asr'],
|
|
['auto-thumbnail', 'artplayer-plugin-auto-thumbnail'],
|
|
['canvas', 'artplayer-proxy-canvas'],
|
|
['chromecast', 'artplayer-plugin-chromecast'],
|
|
['danmuku', 'artplayer-plugin-danmuku'],
|
|
['danmuku-mask', 'artplayer-plugin-danmuku-mask'],
|
|
['dash', 'artplayer-plugin-dash-control'],
|
|
['dpip', 'artplayer-plugin-document-pip'],
|
|
['iframe', 'artplayer-tool-iframe'],
|
|
['jassub', 'artplayer-plugin-jassub'],
|
|
['mediabunny', 'artplayer-proxy-mediabunny'],
|
|
['multiple-subtitles', 'artplayer-plugin-multiple-subtitles'],
|
|
['thumbnail', 'artplayer-tool-thumbnail'],
|
|
['vast', 'artplayer-plugin-vast'],
|
|
['vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail'],
|
|
].map(([name, pkg]) => ({ script: `test:${name}-types-package`, args: [], packages: [pkg!] })),
|
|
]
|
|
|
|
export function verifyEcosystemScope(directory: string, checks = ecosystemChecks) {
|
|
const names = fs.readdirSync(path.join(directory, 'packages')).filter(name => name !== 'artplayer-vitepress' && fs.existsSync(path.join(directory, 'packages', name, 'package.json'))).sort()
|
|
const covered = checks.flatMap(check => check.packages).sort()
|
|
assert.deepEqual(covered, names, 'Every library package needs exactly one installed type check')
|
|
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'package.json'), 'utf8')) as { scripts: Record<string, string> }
|
|
for (const check of checks)
|
|
assert(manifest.scripts[check.script], `Missing consumer command: ${check.script}`)
|
|
return names
|
|
}
|
|
|
|
export async function runConsumerCommand(executable: string, args: string[], cwd: string, log: string) {
|
|
const started = Date.now()
|
|
const file = fs.openSync(log, 'w')
|
|
try {
|
|
const result = await new Promise<{ code: number | null, signal: NodeJS.Signals | null, error?: string }>((resolve) => {
|
|
const child = spawn(executable, args, { cwd, windowsHide: true, stdio: ['ignore', file, file], env: { ...process.env, NODE_PATH: '' } })
|
|
child.once('error', error => resolve({ code: null, signal: null, error: error.message }))
|
|
child.once('close', (code, signal) => resolve({ code, signal }))
|
|
})
|
|
return { ...result, elapsedMs: Date.now() - started, log, sha256: createHash('sha256').update(fs.readFileSync(log)).digest('hex') }
|
|
}
|
|
finally {
|
|
fs.closeSync(file)
|
|
}
|
|
}
|
|
|
|
export async function checkEcosystem(directory = fileURLToPath(new URL('../../', import.meta.url))) {
|
|
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use the pinned Yarn')
|
|
const yarn = process.env.npm_execpath
|
|
assert(yarn && fs.existsSync(yarn), 'Missing Yarn executable')
|
|
const packages = verifyEcosystemScope(directory)
|
|
const parent = path.join(directory, 'refactor/.cache/ecosystem-types')
|
|
fs.mkdirSync(parent, { recursive: true })
|
|
const output = fs.mkdtempSync(path.join(parent, 'run-'))
|
|
const results: { command: string[], packages: string[], outcome: Awaited<ReturnType<typeof runConsumerCommand>> }[] = []
|
|
const save = () => fs.writeFileSync(path.join(output, 'report.json'), `${JSON.stringify({ packages, node: process.version, results, scope: 'Installed type contracts, including explicitly recorded historical diagnostics. Not runtime/device or release acceptance.' }, null, 2)}\n`)
|
|
console.log(`Ecosystem installed types: ${output}`)
|
|
for (const args of [['build:types'], ['build', 'all'], ['build:i18n']]) {
|
|
const outcome = await runConsumerCommand(process.execPath, [yarn, ...args], directory, path.join(output, `prepare-${results.length}.log`))
|
|
results.push({ command: args, packages: [], outcome })
|
|
save()
|
|
assert.equal(outcome.code, 0, `Cannot test stale packages after failed preparation; see ${outcome.log}`)
|
|
}
|
|
let failed = false
|
|
for (const [index, check] of ecosystemChecks.entries()) {
|
|
const args = [check.script, ...check.args]
|
|
console.log(`[${index + 1}/${ecosystemChecks.length}] ${args.join(' ')}`)
|
|
const outcome = await runConsumerCommand(process.execPath, [yarn, ...args], directory, path.join(output, `${check.script.replaceAll(':', '-')}.log`))
|
|
results.push({ command: args, packages: check.packages, outcome })
|
|
save()
|
|
if (outcome.code !== 0)
|
|
failed = true
|
|
console.log(`${check.script}: ${outcome.code === 0 ? 'passed' : 'FAILED'} (${Math.round(outcome.elapsedMs / 1000)}s); ${outcome.log}`)
|
|
}
|
|
assert(!failed, `Installed ecosystem type checks failed; all available results retained in ${output}`)
|
|
return { output, results }
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
checkEcosystem().catch((error: unknown) => {
|
|
console.error(error)
|
|
process.exitCode = 1
|
|
})
|
|
}
|