mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
test(ci): [CI-TYPES-02] gate installed types across all libraries
This commit is contained in:
1 parent
00c131bff1
commit
01f3d00e4c
17 files changed
+974
-8
No files matched your search
@@ -33,3 +33,41 @@ Maintain the module with `yarn typecheck:docs-tools`, `yarn lint`, and
|
||||
`node --test test/installed-types.test.js` (also included in `yarn test:node`).
|
||||
The tests compile a real isolated package and reject a missing declaration,
|
||||
`any` widening, and a package link outside the consumer.
|
||||
|
||||
## Complete ecosystem gate
|
||||
|
||||
`yarn test:ecosystem-types` rebuilds public declarations, all package bundles and
|
||||
core languages, then runs the four-package shared consumer plus all 17 specialized
|
||||
package type commands sequentially. `ecosystem.ts` validates that this explicit
|
||||
roster covers every library exactly once; the VitePress site retains its separate
|
||||
site/editor checks. A new library or missing command fails instead of silently
|
||||
reducing scope. Existing historical diagnostics, conditional exports and accurate
|
||||
runtime entry fixtures remain owned by their specialized checkers.
|
||||
|
||||
Preparation failure stops before any installed validation, avoiding stale bundle
|
||||
acceptance. A package failure records its actual exit code and continues through
|
||||
the remaining packages, then fails the aggregate command. Each child writes stdout
|
||||
and stderr to a dedicated file; after each child, the report records duration,
|
||||
exit code or launch error, log fingerprint and package ownership. Results live in
|
||||
`refactor/.cache/ecosystem-types/run-*/`. Specialized checkers keep their own detailed
|
||||
reports and fingerprints under `refactor/.cache/*-package-types-*/`.
|
||||
|
||||
This command intentionally regenerates tracked distribution files using the
|
||||
normal build commands. Review any real output drift before committing; formatting
|
||||
normalization alone is not a runtime change. Do not run another source edit/build
|
||||
concurrently. Checks run one at a time to avoid competing installs and excessive
|
||||
compiler memory. The CI consumer job runs this gate after its existing runtime,
|
||||
framework, history and performance checks on each OS, with the canonical Node
|
||||
restored. CI retains the aggregate directory and detailed JSON/log artifacts on
|
||||
failure. The existing job timeout remains in force.
|
||||
|
||||
The shared four-package check updates `refactor/.cache/packages/latest.json`.
|
||||
That is why CI runs this gate after the existing runtime/browser consumers, which
|
||||
already selected their own map. Before a later full installed browser run, prepare
|
||||
its twenty-package map again with `yarn test:package --browser`; never pass the
|
||||
four-package type preparation off as the complete browser artifact roster.
|
||||
|
||||
Successful completion means every selected checker met its documented type
|
||||
contract, including exact expected historical failures. It does not approve the
|
||||
pending Thumbnail runtime compatibility choice or replace media/device/SDK tests,
|
||||
release reviews, or actual remote CI evidence.
|
||||
@@ -0,0 +1,95 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user