mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-12 15:26:16 -08:00
fix(dash): [PKG-DASH-05] align nullable SDK metadata and validate real types
This commit is contained in:
1 parent
c4b5f5f454
commit
2ad00730ee
25 files changed
+1255
-25
No files matched your search
@@ -70,7 +70,7 @@ const option: dash.Option = { quality: { getName: level => String(level.height)
|
||||
assert.equal(invalid.length, 8, 'Installed package must reject all invalid uses')
|
||||
matrix.push({ compiler: compiler.version, mode, diagnostics, invalid })
|
||||
}
|
||||
const report = { task: 'PKG-DASH-04', scope: 'Packed current core and DASH, offline Yarn install and frozen reinstall outside workspace; isolated public type acceptance only, not complete package runtime/SDK/media release validation.', packages, matrix }
|
||||
const report = { suite: 'dash-isolated-package-types', introducedBy: 'PKG-DASH-04', scope: 'Packed current core and DASH, offline Yarn install and frozen reinstall outside workspace; isolated public type acceptance only, not complete package runtime/SDK/media release validation.', packages, matrix }
|
||||
writeJson(path.join(output, 'report.json'), report)
|
||||
console.log(`DASH installed type matrix passed: ${matrix.length} modes, eight invalid uses rejected in each; ${output}`)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { archiveFiles, ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
const root = path.resolve(refactorDir, '..')
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
async function copyRelease(release, directory, members) {
|
||||
const archive = await ensureArchive(release)
|
||||
if (!members)
|
||||
assert.deepEqual(archiveFiles(archive), Object.keys(release.files).sort())
|
||||
for (const member of members || Object.keys(release.files)) {
|
||||
const bytes = readMember(archive, member)
|
||||
assert.equal(hash(bytes), release.files[member], member)
|
||||
const destination = path.resolve(directory, 'node_modules', release.name, member.slice('package/'.length))
|
||||
assert(destination.startsWith(path.resolve(directory, 'node_modules') + path.sep))
|
||||
fs.mkdirSync(path.dirname(destination), { recursive: true })
|
||||
fs.writeFileSync(destination, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDashSDKTypes() {
|
||||
const sdkInputs = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dash-sdk.json')))
|
||||
const dependencies = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dash-type-dependencies.json')))
|
||||
const directory = fs.mkdtempSync(path.join(refactorDir, '.cache/dash-sdk-types-'))
|
||||
const results = []
|
||||
try {
|
||||
for (const { release } of sdkInputs.sdks) {
|
||||
const workspace = path.join(directory, release.version)
|
||||
await copyRelease(release, workspace, ['package/package.json', 'package/index.d.ts'])
|
||||
if (release.version === '5.2.1') {
|
||||
for (const dependency of dependencies.releases)
|
||||
await copyRelease(dependency, workspace)
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(workspace, 'node_modules/dashjs/package.json')))
|
||||
assert.equal(manifest.dependencies['@svta/cml-request'], '1.0.12')
|
||||
for (const dependency of dependencies.releases) {
|
||||
const metadata = JSON.parse(fs.readFileSync(path.join(workspace, 'node_modules', dependency.name, 'package.json')))
|
||||
for (const [name, version] of Object.entries(metadata.peerDependencies || {}))
|
||||
assert(dependencies.releases.some(peer => peer.name === name && peer.version === version), `Missing exact peer ${name}@${version}`)
|
||||
}
|
||||
}
|
||||
const fixture = path.join(refactorDir, `fixtures/consumers/dash-sdk-v${release.version[0]}.ts`)
|
||||
const source = fs.readFileSync(fixture, 'utf8')
|
||||
assert(source.includes('// Candidate plugin consumer.'))
|
||||
for (const [compiler, mode] of modes) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const filename = path.join(workspace, `consumer.${next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'}`)
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
|
||||
function compile(code) {
|
||||
fs.writeFileSync(filename, code)
|
||||
const program = compiler.createProgram([filename], options)
|
||||
const files = program.getSourceFiles().map(file => path.resolve(file.fileName))
|
||||
assert(files.includes(path.join(workspace, 'node_modules/dashjs/index.d.ts')), 'Actual SDK declaration was not loaded')
|
||||
const hasPlugin = files.some(file => fs.realpathSync(file) === fs.realpathSync(path.join(root, 'packages/artplayer-plugin-dash-control/types/artplayer-plugin-dash-control.d.ts')))
|
||||
assert.equal(hasPlugin, code.includes('// Candidate plugin consumer.'), 'Only plugin consumers must load the candidate declaration')
|
||||
const diagnostics = compiler.getPreEmitDiagnostics(program).map(item => ({
|
||||
file: item.file ? path.relative(workspace, item.file.fileName).replaceAll('\\', '/').replace(/^consumer\.[cm]?ts$/, 'consumer.ts') : null,
|
||||
code: item.code,
|
||||
line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null,
|
||||
message: compiler.flattenDiagnosticMessageText(item.messageText, '\n').replaceAll(workspace.replaceAll('\\', '/'), '<consumer>').replaceAll(root.replaceAll('\\', '/'), '<workspace>'),
|
||||
}))
|
||||
return { diagnostics, files: files.filter(file => file.startsWith(path.join(workspace, 'node_modules'))).map(file => path.relative(workspace, file).replaceAll('\\', '/')) }
|
||||
}
|
||||
const sdkSource = source.split('// Candidate plugin consumer.')[0]
|
||||
.replace(/^import .+ from '(?:artplayer|artplayer-plugin-dash-control)'\r?\n/gm, '')
|
||||
const sdkOnly = compile(sdkSource)
|
||||
const candidate = compile(source)
|
||||
const invalid = compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
|
||||
results.push({ sdk: release.version, compiler: compiler.version, mode, sdkOnly, candidate, invalid })
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
finally {
|
||||
const resolved = fs.realpathSync(directory)
|
||||
assert.equal(path.dirname(resolved), fs.realpathSync(path.join(refactorDir, '.cache')))
|
||||
assert(path.basename(resolved).startsWith('dash-sdk-types-'))
|
||||
fs.rmSync(resolved, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const results = await checkDashSDKTypes()
|
||||
const report = path.join(refactorDir, '.cache/dash-sdk-type-diagnostics.json')
|
||||
fs.writeFileSync(report, `${JSON.stringify(results, null, 2)}\n`)
|
||||
console.log(results.map(row => ({ sdk: row.sdk, compiler: row.compiler, mode: row.mode, sdkOnly: row.sdkOnly.diagnostics.length, candidate: row.candidate.diagnostics.length, invalid: row.invalid.diagnostics.length })))
|
||||
console.log(report)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Compare exact actual SDK compiler diagnostics.
|
||||
import test from 'node:test'
|
||||
import { checkDashSDKTypes } from './dash-sdk-types.mjs'
|
||||
import { refactorDir } from './releases.mjs'
|
||||
|
||||
test('actual DASH SDK types retain upstream diagnostics while candidate options add none', async () => {
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dash-sdk-type-diagnostics.json')))
|
||||
const results = await checkDashSDKTypes()
|
||||
assert.equal(results.length, 10)
|
||||
assert.equal(baseline.matrix.length, results.length)
|
||||
for (const row of results) {
|
||||
const expected = baseline.matrix.find(item => item.sdk === row.sdk && item.compiler === row.compiler && item.mode === row.mode)
|
||||
assert(expected, `Missing frozen SDK row ${row.sdk}/${row.compiler}/${row.mode}`)
|
||||
assert.deepEqual(row.sdkOnly, expected.sdkOnly, 'Upstream SDK declaration diagnostics or resolved files changed')
|
||||
assert.deepEqual(row.candidate, row.sdkOnly, 'Candidate plugin added errors to an actual SDK consumer')
|
||||
const errors = row.invalid.diagnostics.filter(item => item.file === 'consumer.ts')
|
||||
assert.deepEqual(errors.map(item => item.code), [2769, 2339], 'Invalid formatter return and missing SDK field must both be rejected')
|
||||
assert.deepEqual(row.invalid.diagnostics.filter(item => item.file !== 'consumer.ts'), row.sdkOnly.diagnostics)
|
||||
}
|
||||
})
|
||||
@@ -30,8 +30,8 @@ export function readMember(archive, member) {
|
||||
}
|
||||
|
||||
export async function ensureArchive(release) {
|
||||
assert(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(release.name) && /^\d+\.\d+\.\d+$/.test(release.version), 'Invalid release identifier')
|
||||
const archive = path.join(cacheDir, `${release.name}-${release.version}.tgz`)
|
||||
assert(/^(?:@[a-z0-9]+(?:[.-][a-z0-9]+)*\/)?[a-z0-9]+(?:[.-][a-z0-9]+)*$/.test(release.name) && /^\d+\.\d+\.\d+$/.test(release.version), 'Invalid release identifier')
|
||||
const archive = path.join(cacheDir, `${release.name.replace('/', '+')}-${release.version}.tgz`)
|
||||
if (!fs.existsSync(archive)) {
|
||||
const url = new URL(release.tarball)
|
||||
assert.equal(url.origin, 'https://registry.npmjs.org', 'Unexpected registry')
|
||||
|
||||
@@ -11,7 +11,7 @@ test('release integrity rejects corrupted bytes and inconsistent frozen digests'
|
||||
})
|
||||
|
||||
test('release cache rejects unsafe package and version path identifiers before I/O', async () => {
|
||||
for (const name of ['../hls.js', '.hls', 'hls..js', 'hls/js', 'hls\\js', 'hls.js/evil', '-hls'])
|
||||
for (const name of ['../hls.js', '.hls', 'hls..js', 'hls/js', 'hls\\js', 'hls.js/evil', '-hls', '@svta/../outside', '@../outside', '@svta/cml/request', '@svta\\cml-request', '@svta+cml-request', '@svta/', '@svta/.hidden'])
|
||||
await assert.rejects(ensureArchive({ name, version: '1.5.17' }), /Invalid release identifier/)
|
||||
for (const version of ['../1.5.17', '1.5.17/evil', 'latest'])
|
||||
await assert.rejects(ensureArchive({ name: 'hls.js', version }), /Invalid release identifier/)
|
||||
|
||||
Reference in new issue
Block a user