mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-23 05:41:20 -08:00
refactor(mask): [PKG-MASK-04] migrate strict types and preserve public consumers
This commit is contained in:
1 parent
aba125262a
commit
aca4bc1cd2
30 files changed
+1848
-72
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
||||
# PKG-MASK-04 严格TS与公开类型兼容
|
||||
|
||||
## 实现与公开接口
|
||||
|
||||
五个自有运行模块迁为TS,types.ts明确最小核心宿主、原始可选参数/完整配置、SDK
|
||||
额外历史传参、输出画布及每轮可空资源。类字段declare不产生新字段;局部断言说明
|
||||
对应初始化、Promise resolver或RGBA长度约束。实际Artplayer宿主fixture通过严格检查。
|
||||
相对MASK03提交aba125262,五模块归一化JS和三种正常构建产物保持一致。
|
||||
|
||||
公开声明与npm1.0.0/1.1.0保持字节一致(忽略换行)。原参数可选、同步注册、
|
||||
Promise<void> start、void stop、固定name、完整工厂双向赋值及类型提取继续保留。
|
||||
这些返回值本就准确,不为形式统一额外增加/runtime入口或暴露内部SDK类型。
|
||||
|
||||
## 两项类型工具修复
|
||||
|
||||
实际严格消费者先复现旧Node模块解析下/legacy报2307,现以精确typesVersions路径
|
||||
映射修复;默认工厂声明与其他入口未变,历史直接声明路径仍验证。npm两版在相同
|
||||
模式均记录旧失败,候选只允许消除此特定行的2307;NodeNext ESM根命名空间诊断
|
||||
保持历史结果。根CommonJS最新是函数无default属性,1.0.0对象包装单独验证。
|
||||
|
||||
旧在线编辑器生成器把export default和export=混在一起,实际旧文件在两个编译器
|
||||
均报2309。接入现有语义生成器,生成文件正例及三项非法调用反例通过。私有Option/
|
||||
Result不改为新增公开类型;消费者用Parameters/ReturnType提取,生成内容校验防漂移。
|
||||
|
||||
SDK严格声明另复现TF4.22.0的hash_util.d.ts缺少Long import。声明专用sdk-ambient.d.ts
|
||||
引用已安装官方@types/long4.0.2,不产生运行时导入、没有重写第三方类型,也不改
|
||||
types:[]或skipLibCheck:false。包src和tsconfig均不进入tarball。
|
||||
|
||||
## 验证
|
||||
|
||||
Node24.21.0下联合99项通过:36候选生命周期、54历史缺陷、6历史契约、3公共/编辑器
|
||||
类型测试。定向lint、全仓404生产TS及当前/兼容编译器流程通过。三种产物正常重建,
|
||||
docs副本一致,生成编辑器声明通过正反例和文件一致性验证。
|
||||
|
||||
最终Yarn实际pack将旧npm1.0.0/1.1.0和候选分别安装到仓库外,复制根锁作依赖种子,
|
||||
离线安装后再次frozen安装并逐字节核对包内容/锁文件。每包TS5.9.3的Node10、NodeNext
|
||||
CJS/ESM、Bundler及TS4.3.5的Node10,共15组;每组8项非法输入/返回值独立报错。
|
||||
实际安装main和legacy执行同步注册/stop/destroy,但不启动模型;不称为播放或SDK
|
||||
验收。旧Node10/legacy失败用既有直接声明路径核实工厂,其原错误保留在报告。
|
||||
|
||||
复跑:yarn test:danmuku-mask、node --test refactor/scripts/danmuku-mask-types.test.mjs、
|
||||
yarn test:danmuku-mask-types-package、yarn typecheck、yarn build:ts artplayer-plugin-danmuku-mask。
|
||||
新增安装检查脚本复用现有Yarn/TS版本,无新增依赖或锁文件改动。包内架构及types/README
|
||||
解释维护路径和历史边界。详见[类型验证](../baselines/danmuku-mask-types-validation.json)。
|
||||
同步更新风险、SDK来源与影响映射中的当前源码路径,冻结历史JS夹具不改;风险和影响
|
||||
检查通过,没有因迁移路径失效而忽略SDK或插件组合验证。
|
||||
|
||||
## 剩余工作与回退
|
||||
|
||||
05仍负责真实模型、原支持范围核心、浏览器/设备/切源/布局/后端与GPU资源;06负责
|
||||
完整分发、许可、示例和发布验收。WebKit/Chrome设备能力和SDK的close完成仍未证明。
|
||||
本任务不关闭这些风险、不改变各包独立major发布政策,不推送或发布。
|
||||
回退此任务源码/类型解析及生成器选择后正常重建即可;03生命周期修复是独立提交。
|
||||
@@ -0,0 +1,6 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import factory from '../../../packages/artplayer-plugin-danmuku-mask/src'
|
||||
|
||||
declare const art: Artplayer
|
||||
const registration: { name: string, start: () => Promise<void>, stop: () => void } = factory({ opacity: 0, smoothSegmentation: false })(art)
|
||||
export { registration }
|
||||
@@ -123,7 +123,7 @@
|
||||
"consumer": "artplayer-plugin-danmuku-mask",
|
||||
"reason": "Mask and Danmuku share the core-owned $danmuku layer; retain combination validation for root identity, styles and lifecycle",
|
||||
"evidence": [
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.js",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
|
||||
"docs/assets/example/danmuku.mask.js"
|
||||
]
|
||||
}
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 231 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 67 / doing 14 / blocked 0 / done 150 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 66 / doing 14 / blocked 0 / done 151 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | done |
|
||||
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-MASK-03 | artplayer-plugin-danmuku-mask<br>整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-MASK-05 | artplayer-plugin-danmuku-mask<br>验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-MASK-06 | artplayer-plugin-danmuku-mask<br>验证分发并同步文档 | PKG-MASK-05, ENG-07 | danmuku.mask.js、资源版本/许可、CPU fallback 和包体积证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
@@ -535,6 +535,7 @@
|
||||
- PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md)
|
||||
- PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json)
|
||||
- PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json)
|
||||
- PKG-MASK-04: [记录](changes/2026-09-14-PKG-MASK-04-types.md) [记录](baselines/danmuku-mask-types-validation.json)
|
||||
- PKG-ASR-01: [记录](changes/2026-09-13-PKG-ASR-01-contracts.md) [记录](baselines/asr-contract.md) [记录](baselines/asr-release.json)
|
||||
- PKG-ASR-02: [记录](changes/2026-09-13-PKG-ASR-02-audio-baseline.md) [记录](baselines/asr-audio-validation.json)
|
||||
- PKG-ASR-03: [记录](changes/2026-09-13-PKG-ASR-03-audio-ownership.md) [记录](baselines/asr-ownership-validation.json)
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MASK-04 严格TS及公开类型完成
|
||||
|
||||
五个自有模块迁TS且运行产物保持MASK03字节;真实核心宿主、SDK声明桥接和私有资源
|
||||
类型通过严格检查。修复旧Node解析/legacy类型路径与编辑器混合导出,保留根工厂。
|
||||
联合99项、安装包15编译器模式和全仓404生产TS通过;[详细记录](changes/2026-09-14-PKG-MASK-04-types.md)。
|
||||
16插件中源码与类型迁移未完成的剩AutoThumbnail/VAST/JASSUB三包;所有后续组合/发布
|
||||
门槛继续按tasks.json执行,不能把迁移完成当作已可发布。Mask05/06与实际SDK资源风险待验收。
|
||||
|
||||
|
||||
## PKG-MASK-03 生命周期拆分完成
|
||||
|
||||
五个职责模块和单运行归属控制器落地;停止/销毁、迟到回调、并发启动、释放等待和
|
||||
|
||||
+1
-1
@@ -751,7 +751,7 @@
|
||||
"PKG-MASK-06"
|
||||
],
|
||||
"evidence": [
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.js",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
|
||||
"refactor/baselines/danmuku-mask-contract.md",
|
||||
"refactor/baselines/danmuku-mask-contract-validation.json",
|
||||
"refactor/baselines/danmuku-mask-release.json"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
export const maskInvalidStatements = [
|
||||
'mask({ modelSelection: "general" })',
|
||||
'mask({ smoothSegmentation: 1 })',
|
||||
'mask({ solutionPath: 42 })',
|
||||
'mask({ opacity: "1" })',
|
||||
'const invalidName: "other" = result.name',
|
||||
'const invalidStart: void = result.start()',
|
||||
'const invalidStop: Promise<void> = result.stop()',
|
||||
'mask().then(() => {})',
|
||||
]
|
||||
|
||||
export function maskConsumerSource(mode, raw = false) {
|
||||
let source = fs.readFileSync(new URL('../../test/types/danmuku-mask-public.ts', import.meta.url), 'utf8')
|
||||
if (mode === 'nodenext-esm' && !raw) {
|
||||
source = source
|
||||
.replace('import mask from \'artplayer-plugin-danmuku-mask\'', 'import maskModule from \'artplayer-plugin-danmuku-mask\'\nconst mask = maskModule.default')
|
||||
.replace('import legacy from \'artplayer-plugin-danmuku-mask/legacy\'', 'import legacyModule from \'artplayer-plugin-danmuku-mask/legacy\'\nconst legacy = legacyModule.default')
|
||||
}
|
||||
return source
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
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 { maskConsumerSource, maskInvalidStatements } from './danmuku-mask-consumer.mjs'
|
||||
import { verifyDanmukuMaskContract } from './danmuku-mask-contract.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:danmuku-mask-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/danmuku-mask-package-types-'))
|
||||
console.log(`Mask installed consumer evidence: ${output}`)
|
||||
const contract = await verifyDanmukuMaskContract()
|
||||
const packages = []
|
||||
for (const name of ['artplayer', 'artplayer-plugin-danmuku-mask']) {
|
||||
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 manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
const files = packedFiles(archive)
|
||||
checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
|
||||
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
|
||||
}
|
||||
const core = packages[0]
|
||||
const candidates = [
|
||||
...[...contract.baseline.previous, contract.baseline.release].map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
|
||||
{ ...packages[1], label: 'candidate' },
|
||||
]
|
||||
const matrix = []
|
||||
const priorDiagnostics = new Map()
|
||||
for (const plugin of candidates) {
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
writeJson(path.join(consumer, 'package.json'), { name: 'mask-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].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, `${plugin.label}-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, `${plugin.label}-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, `${plugin.label}-yarn.lock`), lock)
|
||||
for (const pkg of [core, plugin]) {
|
||||
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 runtime = path.join(consumer, 'exports.cjs')
|
||||
fs.writeFileSync(runtime, `const assert = require('node:assert/strict'); global.self = global;
|
||||
for (const name of ['artplayer-plugin-danmuku-mask', 'artplayer-plugin-danmuku-mask/legacy']) {
|
||||
const value = require(name); const old = ${plugin.version === '1.0.0'};
|
||||
assert.equal(typeof value, old ? 'object' : 'function');
|
||||
assert.equal(typeof value.default, old ? 'function' : 'undefined');
|
||||
const factory = old ? value.default : value;
|
||||
const style = {}; const handlers = new Map();
|
||||
const result = factory()({ template: { $video: {}, $danmuku: {style} }, on(name, fn) { handlers.set(name, fn); }, off(name, fn) { if (handlers.get(name) === fn) handlers.delete(name); } });
|
||||
assert.deepEqual(Object.keys(result), ['name', 'start', 'stop']);
|
||||
assert.equal(result.name, 'artplayerPluginDanmukuMask'); assert.equal(typeof result.start, 'function');
|
||||
assert.equal(result.stop(), undefined); assert.equal(style.maskImage, 'none');
|
||||
handlers.get('destroy')();
|
||||
}
|
||||
console.log('Installed CJS/legacy registration and stop/destroy passed; no model startup.');`)
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtime], consumer))
|
||||
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 filename = path.join(consumer, `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 }
|
||||
const compile = (source) => {
|
||||
fs.writeFileSync(filename, source)
|
||||
const program = compiler.createProgram([filename], 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()))), `Type escaped consumer: ${actual}`)
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(d => ({ code: d.code, file: d.file ? path.relative(consumer, d.file.fileName).replaceAll('\\', '/') : null, line: d.file && d.start !== undefined ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(d.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>') }))
|
||||
}
|
||||
const raw = compile(maskConsumerSource(mode, true))
|
||||
const key = `${compiler.version}:${mode}`
|
||||
if (plugin.label === 'published-1.1.0')
|
||||
priorDiagnostics.set(key, raw)
|
||||
if (plugin.label === 'candidate')
|
||||
assert.deepEqual(raw, priorDiagnostics.get(key).filter(item => !(mode === 'node10-commonjs' && item.code === 2307 && item.line === 3)), 'Only the recorded old legacy-path resolution failure may be corrected')
|
||||
let source = maskConsumerSource(mode)
|
||||
if (plugin.label !== 'candidate' && mode === 'node10-commonjs') {
|
||||
assert.deepEqual(raw.map(item => [item.code, item.line]), [[2307, 3]], 'Historical legacy path failure must stay precise')
|
||||
source = source.replace('from \'artplayer-plugin-danmuku-mask/legacy\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'')
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
assert.deepEqual(diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`)
|
||||
const invalid = compile(`${source}\n${maskInvalidStatements.join('\n')}\n`)
|
||||
assert.equal(invalid.length, maskInvalidStatements.length)
|
||||
assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index))
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, raw, diagnostics, invalid })
|
||||
}
|
||||
}
|
||||
finally { removeConsumer(consumer) }
|
||||
}
|
||||
writeJson(path.join(output, 'report.json'), { task: 'PKG-MASK-04', node: process.version, packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix, scope: 'Actual published and candidate packages installed outside workspace, offline/frozen lock stability, byte checks, strict types and CJS/legacy registration. No model startup, native inference, ESM browser or GPU acceptance.' })
|
||||
console.log(`Mask installed consumers passed ${matrix.length} compiler modes: ${output}`)
|
||||
}
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Public compatibility uses the repository Node runner.
|
||||
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 { maskInvalidStatements } from './danmuku-mask-consumer.mjs'
|
||||
import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
test('Mask public declaration keeps both actual published factory and result shapes', async () => {
|
||||
const contract = await verifyDanmukuMaskContract()
|
||||
const current = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8').replaceAll('\r\n', '\n')
|
||||
for (const archive of contract.archives.values())
|
||||
assert.equal(current, readMember(archive, 'package/types/artplayer-plugin-danmuku-mask.d.ts').toString().replaceAll('\r\n', '\n'))
|
||||
})
|
||||
|
||||
test('Mask strict public consumers preserve optional arguments, function replacement and reject invalid results/options', () => {
|
||||
const source = fs.readFileSync('test/types/danmuku-mask-public.ts', 'utf8')
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), [])
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source.replace('from \'artplayer-plugin-danmuku-mask\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'')), [], 'Legacy mapping must preserve historical direct declaration paths')
|
||||
const invalid = checkConsumer(compiler, 'node10-commonjs', `${source}\n${maskInvalidStatements.join('\n')}\n`)
|
||||
assert.equal(invalid.length, maskInvalidStatements.length)
|
||||
assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index))
|
||||
}
|
||||
})
|
||||
|
||||
test('Mask editor declaration is generated from the unchanged public contract and accepts optional registration', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginDanmukuMask')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-danmuku-mask.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)
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const consumer = `const option: NonNullable<Parameters<typeof artplayerPluginDanmukuMask>[0]> = {opacity: 0};
|
||||
const art = new Artplayer({container: '#player', url: '/video.mp4'});
|
||||
const result = artplayerPluginDanmukuMask(option)(art);
|
||||
const started: Promise<void> = result.start();
|
||||
const stopped: void = result.stop();
|
||||
artplayerPluginDanmukuMask();
|
||||
const replacement: typeof artplayerPluginDanmukuMask = () => () => ({name: 'artplayerPluginDanmukuMask', async start() {}, stop() {}});
|
||||
// @ts-expect-error Synchronous registration has no then method.
|
||||
artplayerPluginDanmukuMask().then(() => {});
|
||||
// @ts-expect-error The runtime factory has no default property.
|
||||
artplayerPluginDanmukuMask.default();
|
||||
// @ts-expect-error Opacity remains numeric.
|
||||
artplayerPluginDanmukuMask({opacity: '1'});`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
|
||||
}
|
||||
})
|
||||
+5
-2
@@ -2585,11 +2585,14 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "模型 adapter、canvas 和选项的精确类型",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-MASK-04-types.md",
|
||||
"baselines/danmuku-mask-types-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MASK-05",
|
||||
|
||||
@@ -1228,7 +1228,11 @@
|
||||
"PKG-MASK-06"
|
||||
],
|
||||
"sourceFiles": [
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.js"
|
||||
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/config.ts",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/sdk.ts",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/controller.ts",
|
||||
"packages/artplayer-plugin-danmuku-mask/src/output.ts"
|
||||
],
|
||||
"contract": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.",
|
||||
"validationStatus": "MASK01 verified two actual releases, six frozen Yarn/installed SDK versions, adapter source hashes and 12 existing demo resource byte matches. The default solutionPath remains an unversioned CDN. Controlled export/default/pixel probes do not establish real models, fallback, browser/GPU cleanup or notice completeness; MASK05/06 remain open.",
|
||||
|
||||
Reference in new issue
Block a user