mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
refactor(dash): [PKG-DASH-04] migrate strict TypeScript and compatible declarations
This commit is contained in:
1 parent
7de04573b6
commit
6840544e0e
38 files changed
+1744
-102
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
|
||||
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
|
||||
import { hash, readMember } from './releases.mjs'
|
||||
|
||||
async function main() {
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:dash-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/dash-package-types-'))
|
||||
console.log(`DASH isolated package type evidence: ${output}`)
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
const packages = []
|
||||
const published = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/dash-control-release.json'))).release
|
||||
for (const name of ['artplayer', 'artplayer-plugin-dash-control']) {
|
||||
const archive = path.join(output, `${name}.tgz`)
|
||||
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
|
||||
const files = packedFiles(archive)
|
||||
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
checkFiles(manifest, files, name === published.name ? Object.keys(published.files) : [])
|
||||
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
|
||||
}
|
||||
writeJson(path.join(consumer, 'package.json'), { name: 'dash-isolated-type-consumer', private: true, dependencies: Object.fromEntries(packages.map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
|
||||
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, 'install.log'), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, 'frozen-install.log'), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
|
||||
fs.writeFileSync(path.join(output, 'yarn.lock'), lock)
|
||||
for (const pkg of packages) {
|
||||
const root = path.join(consumer, 'node_modules', pkg.name)
|
||||
assert.equal(fs.realpathSync(root), root, 'Installed package is a workspace link')
|
||||
for (const [member, expected] of Object.entries(pkg.files))
|
||||
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
|
||||
}
|
||||
const matrix = []
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const extension = 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 }
|
||||
const source = fs.readFileSync(path.join(workspace, 'test/types/dash-control.ts'), 'utf8')
|
||||
const filename = path.join(consumer, `current.${extension}`)
|
||||
const legacy = path.join(consumer, `legacy.${extension}`)
|
||||
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/dash-control-legacy.ts'), legacy)
|
||||
const files = [filename, legacy]
|
||||
if (mode === 'nodenext-cjs') {
|
||||
const commonjs = path.join(consumer, 'commonjs.cts')
|
||||
fs.writeFileSync(commonjs, `import dash = require('artplayer-plugin-dash-control'); import legacy = require('artplayer-plugin-dash-control/legacy');
|
||||
const option: dash.Option = { quality: { getName: level => String(level.height) } }; dash(option); legacy();`)
|
||||
files.push(commonjs)
|
||||
}
|
||||
function compile(code) {
|
||||
fs.writeFileSync(filename, code)
|
||||
const program = compiler.createProgram(files, options)
|
||||
for (const file of program.getSourceFiles()) {
|
||||
const actual = fs.realpathSync(file.fileName)
|
||||
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped isolated consumer: ${actual}`)
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
assert.deepEqual(diagnostics, [], `${compiler.version} ${mode}`)
|
||||
const invalid = compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
|
||||
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 }
|
||||
writeJson(path.join(output, 'report.json'), report)
|
||||
console.log(`DASH installed type matrix passed: ${matrix.length} modes, eight invalid uses rejected in each; ${output}`)
|
||||
}
|
||||
finally {
|
||||
removeConsumer(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
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 -- Run exact compiler and editor compatibility contracts.
|
||||
import test from 'node:test'
|
||||
import { ESLint } from 'eslint'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
|
||||
import { checkConsumer } from '../../scripts/typecheck.mjs'
|
||||
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
test('DASH public factories preserve root/legacy imports, inferred SDK fields and historical extraction', () => {
|
||||
const source = fs.readFileSync('test/types/dash-control.ts', 'utf8')
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const invalid = source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')
|
||||
assert.equal(checkConsumer(compiler, mode, invalid).length, 8, 'Every invalid use must remain invalid')
|
||||
}
|
||||
const commonjs = `import dash = require('artplayer-plugin-dash-control'); import legacy = require('artplayer-plugin-dash-control/legacy');
|
||||
const option: dash.Option = { quality: { getName: level => String(level.height) } }; dash(option); legacy();`
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', commonjs), [])
|
||||
})
|
||||
|
||||
test('DASH legacy consumer compiles against the actual published declaration and candidate', async () => {
|
||||
const { release } = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/dash-control-release.json')))
|
||||
const member = 'package/types/artplayer-plugin-dash-control.d.ts'
|
||||
const bytes = readMember(await ensureArchive(release), member)
|
||||
assert.equal(hash(bytes), release.files[member])
|
||||
const source = fs.readFileSync(path.join(refactorDir, 'fixtures/consumers/dash-control-legacy.ts'), 'utf8')
|
||||
const directory = fs.mkdtempSync(path.join(refactorDir, '.cache/dash-legacy-types-'))
|
||||
try {
|
||||
const filename = path.join(directory, 'consumer.ts')
|
||||
fs.writeFileSync(path.join(directory, 'legacy.d.ts'), bytes)
|
||||
fs.writeFileSync(filename, source.replace('from \'artplayer-plugin-dash-control\'', 'from \'./legacy\''))
|
||||
for (const compiler of [ts, compat]) {
|
||||
const program = compiler.createProgram([filename], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] })
|
||||
const diagnostics = compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
assert.deepEqual(diagnostics, [], `Published / TS ${compiler.version}`)
|
||||
assert(program.getSourceFiles().some(file => path.resolve(file.fileName) === path.join(directory, 'legacy.d.ts')))
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), [], `Candidate / TS ${compiler.version}`)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
const resolved = fs.realpathSync(directory)
|
||||
assert.equal(path.dirname(resolved), fs.realpathSync(path.join(refactorDir, '.cache')))
|
||||
assert(path.basename(resolved).startsWith('dash-legacy-types-'))
|
||||
fs.rmSync(resolved, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('DASH editor declarations are reproducible, standalone, precise and compatible with old TS', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-dash-control/types/artplayer-plugin-dash-control.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginDashControl')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-dash-control.d.ts'
|
||||
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
|
||||
assert.equal(formatted.errorCount, 0)
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated, 'Run yarn build:ts')
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const consumer = `
|
||||
const option: artplayerPluginDashControl.Option = { quality: { getName: level => String(level.height) }, audio: { getName: track => track.lang || String(track.id) } }
|
||||
const oldQuality: Parameters<typeof artplayerPluginDashControl>[0]['quality'] = { getName: (level: object) => String(level) }
|
||||
const factory: (art: Artplayer) => artplayerPluginDashControl.Result = artplayerPluginDashControl(option)
|
||||
artplayerPluginDashControl()
|
||||
artplayerPluginDashControl(undefined)
|
||||
// @ts-expect-error A formatter must return a string.
|
||||
artplayerPluginDashControl({ quality: { getName: level => level.height } })
|
||||
// @ts-expect-error SDK fields cannot become any.
|
||||
artplayerPluginDashControl({ audio: { getName: track => track.unknownProperty } })
|
||||
// @ts-expect-error Private definition namespace must not leak globally.
|
||||
const hidden: artplayerPluginDashControlDefinitions.Option = {}
|
||||
void [oldQuality, factory, hidden]
|
||||
`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [], compiler.version)
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user