mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-25 16:11:20 -08:00
refactor(mediabunny): [PKG-MB-08] checkpoint typed entry and compatible media declarations
This commit is contained in:
1 parent
8a2b56e829
commit
5e28dfee95
39 files changed
+3007
-287
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"task": "PKG-MB-08",
|
||||
"sourceCommit": "8a2b56e829a3fa9af5e440a11cede8d1775ded10",
|
||||
"sourceSha256LF": {
|
||||
"packages/artplayer-proxy-mediabunny/src/index.js": "4e1be8937908ff6e6ef8a4e863e22b4468312cf31d66ac299322e475757773bb",
|
||||
"packages/artplayer-proxy-mediabunny/src/VideoShim.ts": "1fef3028a3937182c31503129655efdb88118ea0f6d3be96e9be99dcbcc5fdc7",
|
||||
"packages/artplayer-proxy-mediabunny/src/engine-types.ts": "6714070f954f7345f33cd93655facc6aa94fd34fa1be0375361962d54fa6e180",
|
||||
"packages/artplayer-proxy-mediabunny/types/artplayer-proxy-mediabunny.d.ts": "af2083224d0760752d83eb2ac86b1dea5586455b81d47e4ba70f9f6a874a75d8",
|
||||
"packages/artplayer-proxy-mediabunny/package.json": "96b09ce19ea7914485aa9780329a0cfc43b18e38232ea1dd92d2e6dc27310055",
|
||||
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js": "303b1a5d71b661c7d973c52850f5e68766990c86c59fe438f0070bd38edbfb0e"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# PKG-MB-08 检查点:入口、Canvas 转发与公开声明
|
||||
|
||||
任务保持doing。本包自有源码已全部迁移为34个strict TS模块,但不可解码轨道的回退与
|
||||
就绪能力审查尚未完成;不能据此把整个MB-08标done。进入本轮的源码/产物冻结于
|
||||
[入口基线](../baselines/mb-entry-source.json),对应8a2b56e8。
|
||||
|
||||
## 实现与兼容边界
|
||||
|
||||
入口从index.js迁至index.ts,Canvas转发、入口生命周期和通用清理分别进入
|
||||
canvas-bridge.ts、entry-lifecycle.ts、cleanup.ts。后者从HLS菜单层下移,避免入口依赖
|
||||
UI层的通用释放实现。HLS初始化支持部分注册失败时撤销,返回内部destroy供入口回滚。
|
||||
|
||||
保留createElement脱离receiver调用、原生可枚举方法getter读取两次、Canvas原生方法优先、
|
||||
shim own/直接prototype转发、每次读取绑定方法、getter-only写入TypeError等旧行为。
|
||||
资源状态不增加Canvas公开字段。销毁取消入口resize/metadata/destroy监听,已保存回调失效;
|
||||
只删除仍属于当前shim的art.mediabunny,保留调用方替换的别名。一个清理步骤抛错仍尝试其余步骤,
|
||||
setup失败保留原始异常。缺少off的历史自定义host仍可用,残留回调在销毁后保持无副作用。
|
||||
|
||||
公开默认工厂继续是可选Option到同步initializer、精确HTMLCanvasElement的签名;不将Result
|
||||
缩窄成Canvas与shim交叉类型,不增加强制default属性,也不全局增强Artplayer。
|
||||
新增type-only Option/Result、MediaBunnyShim、MediaBunnyCanvas、MediaBunnyPlayer、HLS状态和
|
||||
SyntheticFrameCallback等。MediaBunnyPlayer的别名可缺省;Canvas视图明确原生DOM方法优先。
|
||||
VideoShim实际implements媒体视图,getter显式返回类型防止unknown setter污染读取推导。
|
||||
|
||||
声明不引入MediaBunny SDK的现代DOM/Disposable依赖;track对象保持unknown,由消费者按
|
||||
自用SDK缩窄。SyntheticFrameMetadata保留历史估算语义,尤其presentationTime是媒体秒数。
|
||||
canPlayType仍恒为maybe,TimeRanges不代表真实网络缓冲;新类型不制造能力承诺。
|
||||
|
||||
新增.d.mts及条件types路由修正NodeNext ESM不可调用问题,typesVersions补齐经典解析的legacy。
|
||||
CommonJS继续保留旧default声明门面;运行时直接工厂增加不可枚举、可写、可配置的default自身别名,
|
||||
同时支持1.0.0的namespace.default和1.2.0的直接调用。整工厂双向赋值使用实际两个npm冻结声明检查。
|
||||
|
||||
## 验证与迭代
|
||||
|
||||
15条入口断言在旧main上6通过/9失败;候选包专项241通过,main/legacy各217通过,其中
|
||||
各213条候选断言和4条冻结正常控制。HLS测试将清理断言限定为其自己拥有的5个listener;
|
||||
新增入口断言单独检查额外3个listener,不再错误假设resize回调应一直残留。
|
||||
|
||||
新增12浏览器用例使用真实Canvas与发布版/候选核心,覆盖方法receiver、DOM事件/属性优先、
|
||||
清理和替换别名。旧main同12项全部复现缺陷;候选通过。首轮夹具错误地把后续核心尺寸处理后的
|
||||
inline style当作入口独占结果,现单独调用已注册入口resize callback验证其行为;首轮18份失败
|
||||
report/results保留,并合并了两组重复场景。未增加重试或放宽超时。最终8个MediaBunny浏览器文件
|
||||
共120通过:新增12项为Canvas/生命周期检查,原有29项为Windows WebKit能力对照,均不冒充播放。
|
||||
|
||||
新增yarn test:mediabunny-types-package:实际pack核心与代理,在工作区外安装两个冻结发布包及
|
||||
候选,offline/frozen重装、锁文件和安装文件哈希检查。17组编译/导出矩阵通过;候选7组各拒绝
|
||||
15条非法用法,并验证原始两份声明的整工厂双向赋值。保留旧版NodeNext ESM的准确诊断作为负例。
|
||||
最初pack发现tsconfig泄漏,补入.npmignore后修复。旧TS不读取SDK声明,所有解析文件限于安装
|
||||
目录或编译器标准库。该检查并非MB-10完整分发/许可验收。
|
||||
|
||||
最终源码、打包安装、构建与浏览器证据见[检查点记录](../baselines/mb-entry-checkpoint.json)。
|
||||
完整CI1402项(1247单元+14工程+141基线)通过,另44重复契约,324生产TS。
|
||||
首轮全局noUncheckedIndexedAccess发现消费者夹具直接读取空数组首项,改为可选访问后
|
||||
完整CI和17组实际安装矩阵重跑通过;运行时产物未变化。本轮未添加依赖,Yarn和SDK锁版本不变。
|
||||
|
||||
## 立即接续
|
||||
|
||||
继续检查已有track但codec/canDecode不可用时,VideoEngine/AudioEngine的历史空轨道回退。
|
||||
必须保留有效audio-only/video-only播放,阻止没有可用解码器时假就绪,并覆盖加载、HLS替换、
|
||||
晚能力查询、pause/source/destroy和错误事件顺序。不要仅凭新增接口或现有WebKit负例宣布修复。
|
||||
长播放/AV sync/设备及组合由MB-09,示例、安装分发及MPL/source notice由MB-10继续。
|
||||
本检查点独立本地提交,回退该提交即可恢复本轮前状态;冻结历史输入保持不变,不推送或发布。
|
||||
|
||||
接续缺陷已用实际SDK解析的video-only MP4复现:受控canDecode=false与AudioContext下,
|
||||
旧/候选均为videoSink=null、audioSink=null,却readyState=4并发loadedmetadata/data/canplay/through;
|
||||
replaceTracks后同样假就绪。该受控复现见检查点记录,不能当作真实浏览器解码结果。
|
||||
@@ -0,0 +1,11 @@
|
||||
import Artplayer from 'artplayer'
|
||||
import factory from 'artplayer-proxy-mediabunny'
|
||||
|
||||
type Option = Parameters<typeof factory>[0]
|
||||
const art = new Artplayer({ container: '#player', url: 'movie.mp4', proxy: factory() })
|
||||
const replacement: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas')
|
||||
const canvas: ReturnType<ReturnType<typeof factory>> = document.createElement('canvas')
|
||||
factory()
|
||||
factory(undefined)
|
||||
factory({ volume: 0.7, source: new Blob([]), loadTimeout: 1000 })(art)
|
||||
void [replacement, canvas]
|
||||
@@ -0,0 +1,34 @@
|
||||
import Artplayer from 'artplayer'
|
||||
import factory from 'artplayer-proxy-mediabunny'
|
||||
|
||||
const art = new Artplayer({ container: '#player', url: 'movie.mp4', proxy: factory() })
|
||||
type Option = NonNullable<Parameters<typeof factory>[0]>
|
||||
type HistoricalFactory = (option?: Option) => (art: Artplayer) => HTMLCanvasElement
|
||||
const assignOld: HistoricalFactory = factory
|
||||
const assignNew: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas')
|
||||
const initializer: ReturnType<typeof factory> = () => document.createElement('canvas')
|
||||
const canvas: ReturnType<ReturnType<typeof factory>> = document.createElement('canvas')
|
||||
const options: Option = {
|
||||
source: new ReadableStream<Uint8Array>(),
|
||||
loadTimeout: 1000,
|
||||
timeupdateInterval: 250,
|
||||
avSyncTolerance: 0.12,
|
||||
dropLateFrames: false,
|
||||
poster: 'poster.jpg',
|
||||
preflightRange: false,
|
||||
volume: 0.7,
|
||||
muted: false,
|
||||
autoplay: false,
|
||||
loop: false,
|
||||
crossOrigin: 'anonymous',
|
||||
m3u8: {
|
||||
quality: { control: true, setting: true, title: 'Quality', auto: 'Auto', getName: level => `${level.id}/${level.index}/${level.name}/${level.height}/${level.bitrate}` },
|
||||
audio: { control: true, setting: true, title: 'Audio', auto: 'Auto', getName: track => `${track.id}/${track.index}/${track.name}/${track.lang}/${track.language}/${track.bitrate}` },
|
||||
},
|
||||
}
|
||||
factory()
|
||||
factory(undefined)
|
||||
factory(options)(art).getContext('2d')
|
||||
factory({ source: new Blob([]) })
|
||||
factory({ source: 'movie.mp4' })
|
||||
void [assignOld, assignNew, initializer, canvas]
|
||||
@@ -80,3 +80,14 @@ main/legacy各159通过。最终78浏览器含16新增原生音频场景及8新
|
||||
11通过/32失败,候选专项226及main/legacy各202通过。最终108浏览器含20新增原生HLS
|
||||
新旧核心/UI/SDK场景与10新增能力对照。旧main同30项14通过/16失败,未把能力缺失计作播放。
|
||||
全部报告/results已归档;完整CI1387项及44重复契约通过,320生产TS;完成后立即专用提交,MB-LIFE-01剩余组合/长播范围仍open。
|
||||
|
||||
## PKG-MB-08 入口与声明检查点(doing)
|
||||
|
||||
index实际迁移,入口/Canvas桥/生命周期/清理拆为四模块,全包34生产TS。
|
||||
精确默认Canvas和整工厂保留;可选shim/HLS/RAF视图、ESM声明及旧legacy解析已验证。
|
||||
15条新断言旧main6通过/9失败,候选专项241及main/legacy各217通过。新增12真实Canvas/
|
||||
新旧核心场景通过,旧main同12项失败;最终8文件120浏览器通过,其中29能力对照不计作播放。
|
||||
实际打包安装17类型矩阵通过,候选7组各拒绝15非法用法;旧NodeNext失败保留负例。
|
||||
见[变更](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md)和[证据](baselines/mb-entry-checkpoint.json)。
|
||||
完整CI1402项及44重复契约通过,324生产TS。受控canDecode=false已复现旧/候选
|
||||
video-only输入及替换无sink仍readyState=4;任务保持doing,下一步修复该能力边界。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 111 / doing 7 / blocked 0 / done 104 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 110 / doing 8 / blocked 0 / done 104 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -336,7 +336,7 @@
|
||||
| PKG-MB-05 | artplayer-proxy-mediabunny<br>整理视频解码、seek 和帧释放 | PKG-MB-04 | VideoEngine 调度、队列、晚帧处理和资源释放 | 暂停/seek/切源无旧帧覆盖,默认丢帧策略不变 | H | done |
|
||||
| PKG-MB-06 | artplayer-proxy-mediabunny<br>整理音频解码、时钟与同步 | PKG-MB-04 | AudioEngine 时钟、缓冲和 AudioContext 归属 | AV sync、倍速、无音轨、静音/音量和暂停恢复通过 | H | done |
|
||||
| PKG-MB-07 | artplayer-proxy-mediabunny<br>整理 HLS 配对轨道与 selector | PKG-MB-05, PKG-MB-06, CORE-14 | m3u8 配对、质量/音频选择及拓扑清理 | 实际选择高亮、切到无轨道来源清理、无重复 readiness | H | done |
|
||||
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | todo |
|
||||
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | doing |
|
||||
| PKG-MB-09 | artplayer-proxy-mediabunny<br>完成新旧核心和真实媒体组合 | PKG-MB-08, CORE-22 | 跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告 | 资源释放、事件顺序、AV sync 与支持范围满足基线 | H | todo |
|
||||
| PKG-MB-10 | artplayer-proxy-mediabunny<br>完成分发和文档 | PKG-MB-09, ENG-07 | mediabunny.js、README、依赖版本、三产物和许可 | tarball 可消费、无意外依赖升级、旧调用与回退可用 | H | todo |
|
||||
|
||||
@@ -527,4 +527,5 @@
|
||||
- PKG-MB-05: [记录](baselines/mb-video-source.json) [记录](changes/2026-09-12-PKG-MB-05-video.md) [记录](baselines/mb-video-validation.json)
|
||||
- PKG-MB-06: [记录](baselines/mb-audio-source.json) [记录](changes/2026-09-12-PKG-MB-06-audio.md) [记录](baselines/mb-audio-validation.json)
|
||||
- PKG-MB-07: [记录](baselines/mb-hls-source.json) [记录](changes/2026-09-12-PKG-MB-07-hls.md) [记录](baselines/mb-hls-validation.json)
|
||||
- PKG-MB-08: [记录](baselines/mb-entry-source.json) [记录](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md) [记录](baselines/mb-entry-checkpoint.json)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MB-08检查点:入口TS与兼容媒体声明(doing)
|
||||
|
||||
全包34个自有生产模块已转TS,入口/Canvas桥/生命周期/清理分离。默认工厂仍可选Option、
|
||||
同步初始化与精确Canvas结果;新增可选媒体/HLS/RAF类型,修复旧legacy及NodeNext ESM解析。
|
||||
15条入口断言旧main6通过/9失败,候选专项241及main/legacy各217通过。最终120浏览器
|
||||
全部通过,含12原生Canvas/新旧核心场景及29能力对照;旧main相同12入口场景全部失败。
|
||||
实际安装17组类型/导出矩阵通过,候选7组各拒绝15非法用法;整工厂按冻结声明双向赋值。
|
||||
完整CI1402项及44重复契约通过,324生产TS。源码/产物/浏览器/安装包哈希已核验。
|
||||
见[变更](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md)与[证据](baselines/mb-entry-checkpoint.json)。
|
||||
222项:104 done、8 doing、110 todo。本地检查点提交,不计任务完成。
|
||||
下一步:修复受控canDecode=false时video-only输入及替换无可用sink却发布就绪;
|
||||
该问题旧/候选均已复现。MB-08、能力风险及后续分发/许可/设备门槛仍未关闭。
|
||||
|
||||
## PKG-MB-07完成:HLS配对、选择意图与UI拓扑
|
||||
|
||||
m3u8实际迁移为四个TS模块,包内30生产TS,仅剩index.js待MB-08。
|
||||
|
||||
+7
-4
@@ -3679,13 +3679,15 @@
|
||||
"PKG-MB-08",
|
||||
"PKG-MB-10"
|
||||
],
|
||||
"compatibleResolution": "Preserve optional whole-factory and exact Canvas assignment, model explicit shim capabilities separately and verify historical default namespace and callable consumers.",
|
||||
"compatibleResolution": "Preserve optional whole-factory and exact Canvas assignment, model explicit shim capabilities separately and verify historical default namespace and callable consumers. MB-08 entry checkpoint preserves exact optional factory/Canvas assignment, adds opt-in checked media types without SDK imports, and fixes classic legacy/NodeNext ESM resolution. Seventeen installed compiler cases plus runtime exports passed against two frozen releases and candidate; seven candidate cases reject fifteen invalid uses each. Type facade/runtime export part is verified; final MB-08 capability and MB-10 distribution gates remain open.",
|
||||
"closureCriteria": "Actual installed old/candidate compiler and runtime export matrices pass with negative controls; any incompatible declaration generation requires an explicit decision.",
|
||||
"status": "open",
|
||||
"evidence": [
|
||||
"refactor/baselines/mb-release.json",
|
||||
"refactor/baselines/mb-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md"
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
|
||||
"refactor/baselines/mb-entry-checkpoint.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3715,13 +3717,14 @@
|
||||
"PKG-MB-04",
|
||||
"PKG-MB-09"
|
||||
],
|
||||
"compatibleResolution": "Use explicit coordinated readiness and failure state, preserving valid event order while preventing duplicate or stale callbacks; evaluate trackless behavior separately from normal media. MB-04 coordinates metadata once, suppresses stale readiness and rejects trackless input before decoder setup. Existing-track unsupported-codec behavior and full native combinations remain MB-05/06/09; risk stays open.",
|
||||
"compatibleResolution": "Use explicit coordinated readiness and failure state, preserving valid event order while preventing duplicate or stale callbacks; evaluate trackless behavior separately from normal media. MB-04 coordinates metadata once, suppresses stale readiness and rejects trackless input before decoder setup. Existing-track unsupported-codec behavior and full native combinations remain MB-05/06/09; risk stays open. MB-08 entry checkpoint reproduces remaining false readiness in both previous and candidate builds: actual SDK-parsed video-only MP4 with controlled canDecode=false and AudioContext yields null audio/video sinks but readyState 4 and all readiness events, including replaceTracks. Keep open for the next MB-08 capability implementation; this controlled case is not native decoding evidence.",
|
||||
"closureCriteria": "Candidate metadata/data/canplay ordering and counts pass normal, absent-track and failure interleavings in native browsers and controlled race tests.",
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-12-PKG-MB-02-baseline.md",
|
||||
"refactor/baselines/mb-behavior-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-04-coordination.md",
|
||||
"refactor/baselines/mb-coordination-validation.json"
|
||||
"refactor/baselines/mb-coordination-validation.json",
|
||||
"refactor/baselines/mb-entry-checkpoint.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
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 { verifyMbContract } from './mb-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:mediabunny-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/mb-package-types-'))
|
||||
console.log(`MediaBunny isolated package evidence: ${output}`)
|
||||
const contract = await verifyMbContract()
|
||||
const packages = []
|
||||
for (const name of ['artplayer', 'artplayer-proxy-mediabunny']) {
|
||||
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 matrix = []
|
||||
const core = packages[0]
|
||||
const candidates = [
|
||||
...contract.baseline.previous.concat(contract.baseline.release).map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
|
||||
{ ...packages[1], label: 'candidate' },
|
||||
]
|
||||
for (const plugin of candidates) {
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
writeJson(path.join(consumer, 'package.json'), { name: 'mediabunny-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 must not be 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 runtimeFile = path.join(consumer, 'exports.cjs')
|
||||
fs.writeFileSync(runtimeFile, `const assert = require('node:assert/strict');
|
||||
for (const name of ['artplayer-proxy-mediabunny', 'artplayer-proxy-mediabunny/legacy']) {
|
||||
const value = require(name);
|
||||
assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}');
|
||||
assert.equal(typeof (value.default || value)(), 'function');
|
||||
${plugin.label === 'candidate' ? 'assert.equal(value.default, value); assert.equal(typeof value.default(), "function"); assert.deepEqual(Object.keys(value), []);' : plugin.version === '1.2.0' ? 'assert.equal(value.default, undefined);' : 'assert.equal(typeof value.default, "function");'}
|
||||
}
|
||||
Promise.all(['artplayer-proxy-mediabunny', 'artplayer-proxy-mediabunny/legacy'].map(async name => {
|
||||
const module = await import(name);
|
||||
const value = module.default;
|
||||
assert.equal(typeof (value.default || value)(), 'function');
|
||||
if (name === 'artplayer-proxy-mediabunny') assert.deepEqual(Object.keys(module), ['default']);
|
||||
})).catch(error => { console.error(error); process.exitCode = 1; });`)
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
if (plugin.label === 'candidate')
|
||||
modes.push([ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop'])
|
||||
for (const [compiler, mode] of modes) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
|
||||
const fixture = plugin.label === 'candidate' ? 'test/types/mediabunny.ts' : plugin.version === '1.0.0' ? 'refactor/fixtures/consumers/mediabunny-1.0.ts' : 'refactor/fixtures/consumers/mediabunny-published.ts'
|
||||
const source = fs.readFileSync(path.join(workspace, fixture), 'utf8')
|
||||
const filename = path.join(consumer, `consumer.${extension}`)
|
||||
const files = [filename]
|
||||
if (plugin.label === 'candidate') {
|
||||
for (const generation of ['1.0', 'published']) {
|
||||
const historical = path.join(consumer, `historical-${generation}.${extension}`)
|
||||
fs.copyFileSync(path.join(workspace, `refactor/fixtures/consumers/mediabunny-${generation}.ts`), historical)
|
||||
files.push(historical)
|
||||
}
|
||||
// Compare complete factories against the actual frozen declaration bytes.
|
||||
for (const release of contract.baseline.previous.concat(contract.baseline.release)) {
|
||||
const name = `frozen-${release.version}`
|
||||
fs.writeFileSync(path.join(consumer, `${name}.d.ts`), readMember(contract.archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`))
|
||||
const assignment = path.join(consumer, `${name}-assignment.cts`)
|
||||
fs.writeFileSync(assignment, `import current from 'artplayer-proxy-mediabunny'; import frozen from './${name}'; const forward: typeof frozen = current; const backward: typeof current = frozen; void [forward, backward];`)
|
||||
// TS 4.3 predates .cts. Use an ordinary .ts for classic resolution.
|
||||
const actual = next ? assignment : assignment.replace(/\.cts$/, '.ts')
|
||||
if (actual !== assignment)
|
||||
fs.renameSync(assignment, actual)
|
||||
files.push(actual)
|
||||
}
|
||||
if (mode === 'nodenext-cjs') {
|
||||
const commonjs = path.join(consumer, 'commonjs.cts')
|
||||
fs.writeFileSync(commonjs, `import factory = require('artplayer-proxy-mediabunny'); import legacy = require('artplayer-proxy-mediabunny/legacy'); factory.default(); legacy.default({ volume: 0.5 });`)
|
||||
files.push(commonjs)
|
||||
}
|
||||
}
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), 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(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 installed consumer: ${actual}`)
|
||||
assert(!/[\\/]node_modules[\\/]mediabunny[\\/]/.test(actual), 'Public declarations must not pull modern SDK types into older compilers')
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
const historicalFailure = plugin.label !== 'candidate' && mode === 'nodenext-esm'
|
||||
writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}-diagnostics.json`), { diagnostics, historicalFailure })
|
||||
if (historicalFailure) {
|
||||
assert.deepEqual(diagnostics.map(item => item.code), plugin.version === '1.0.0'
|
||||
? [2344, 2349, 2322, 2344, 2349, 2349, 2349]
|
||||
: [2349, 2344, 2322, 2322, 2344, 2344, 2322, 7006, 7006, 2349, 2349, 2349, 2349, 2349], 'Unexpected historical NodeNext failure')
|
||||
}
|
||||
else {
|
||||
assert.deepEqual(diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`)
|
||||
}
|
||||
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
|
||||
if (plugin.label === 'candidate')
|
||||
assert.equal(invalid.length, 15, 'Installed declarations must reject all invalid uses')
|
||||
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid })
|
||||
}
|
||||
}
|
||||
finally {
|
||||
removeConsumer(consumer)
|
||||
}
|
||||
}
|
||||
writeJson(path.join(output, 'report.json'), { suite: 'mediabunny-isolated-package-types', introducedBy: 'PKG-MB-08', scope: 'Two frozen published packages and packed candidate with packed candidate core, installed outside workspace; offline/frozen reinstall, exact bytes, default/legacy CJS/ESM runtime and compiler matrices. Exact optional whole-factory assignments checked against actual historical declaration bytes. New shim types are opt-in, without SDK declaration leakage. Historical NodeNext ESM failures remain explicit negative controls. Not native decoding, old-core runtime combinations or full distribution/license acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix })
|
||||
console.log(`MediaBunny installed types passed: ${matrix.length} cases; ${output}`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
+6
-2
@@ -3546,11 +3546,15 @@
|
||||
"ENG-04",
|
||||
"ENG-06"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "8 个自有 JS 模块迁移与 Result/shim 类型",
|
||||
"acceptance": "解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"baselines/mb-entry-source.json",
|
||||
"changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
|
||||
"baselines/mb-entry-checkpoint.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-MB-09",
|
||||
|
||||
Reference in new issue
Block a user