mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-24 06:31:20 -08:00
fix(canvas): [PKG-CANVAS-04] preserve public type and export compatibility
This commit is contained in:
1 parent
2b319109cd
commit
224572032a
29 files changed
+682
-41
No files matched your search
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-CANVAS-04",
|
||||
"status": "done",
|
||||
"checks": [
|
||||
{
|
||||
"command": "node --test refactor/scripts/canvas-types.test.mjs",
|
||||
"result": "pass",
|
||||
"tests": 4,
|
||||
"scope": "Exact published declarations, optional Parameters and exact Canvas result, five compiler modes with eleven negative checks, semantic editor globals and actual production exports"
|
||||
},
|
||||
{
|
||||
"command": "yarn test:canvas-types-package",
|
||||
"result": "pass with explicit historical diagnostics",
|
||||
"cases": 17,
|
||||
"candidateZeroDiagnosticCases": 7,
|
||||
"historicalZeroDiagnosticCases": 9,
|
||||
"historicalKnownFailureCases": 1,
|
||||
"expectedHistoricalCodes": [
|
||||
2349,
|
||||
2349,
|
||||
2322,
|
||||
2344,
|
||||
2344
|
||||
],
|
||||
"candidateInvalidUseRejectionsPerMode": 11,
|
||||
"report": "refactor/.cache/canvas-package-types-NZAnqz/report.json",
|
||||
"sha256": "05c8f0a0367e1478b59e839520e5c7572b3d5519df7323af21ead9e967524da8"
|
||||
},
|
||||
{
|
||||
"command": "yarn build:ts && yarn build artplayer-proxy-canvas",
|
||||
"result": "pass",
|
||||
"scope": "Semantic editor declarations and real three-format outputs"
|
||||
},
|
||||
{
|
||||
"command": "focused eslint --no-fix",
|
||||
"result": "pass"
|
||||
},
|
||||
{
|
||||
"command": "playwright test test/browser/canvas-lifecycle.spec.js",
|
||||
"result": "pass",
|
||||
"tests": 18,
|
||||
"skipped": 0,
|
||||
"browsers": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"version": "153.0.8010.12",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"version": "155.0",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"version": "26.6",
|
||||
"platform": "win32"
|
||||
}
|
||||
],
|
||||
"scope": "Candidate source with default alias, actual npm core5.4.0/candidate, native pixels/dimensions/resize/source/paused seek and terminal media/frame ownership",
|
||||
"report": "refactor/.cache/canvas04-final-results/report.json",
|
||||
"sha256": "af893ebcd22fa6312db1e02b222849135ca89568ecf196c88ce2947436cda0ae",
|
||||
"resultsDirectory": "refactor/.cache/canvas04-final-results/results"
|
||||
},
|
||||
{
|
||||
"command": "yarn ci:check",
|
||||
"result": "pass",
|
||||
"tests": {
|
||||
"unit": 938,
|
||||
"engineering": 14,
|
||||
"baseline": 100,
|
||||
"mainTotal": 1052,
|
||||
"repeatedContractExecutions": 44
|
||||
},
|
||||
"strictProductionTypeScriptFiles": 284
|
||||
}
|
||||
],
|
||||
"limitations": [
|
||||
"Published 1.1.0 NodeNext ESM has five recorded diagnostics; expected historical failure is not zero-error compilation.",
|
||||
"Full distribution/deep paths/demo and device gates remain tasks 05/06."
|
||||
],
|
||||
"sourceCommit": "2b319109cd191439d49d2cb701632c95ed04cff4",
|
||||
"inputs": [
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/src/index.ts",
|
||||
"sha256LF": "2124bfe63da80f7d0b25a2d0e397e13ca002dd74a4293a0ae023c3c3702a69ec"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/package.json",
|
||||
"sha256LF": "6c84b3b089dd341edaca26655289afcf834c5b4b67af8d56e0703155b004ae3a"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/types/artplayer-proxy-canvas.d.ts",
|
||||
"sha256LF": "85cdcf86ed12b383785f5303b41458d5d39117b83e74d9f7e99545e59ab4687b"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/types/artplayer-proxy-canvas.d.mts",
|
||||
"sha256LF": "fdff66b63ff24a93370e7f4e87ed6b19a1bd4a2305aa6db37da2c38af3415458"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/types/artplayer-proxy-canvas.d.cts",
|
||||
"sha256LF": "812d9c09a027834e45619dceb04f8b49876623771ad7ce570ab5920383679fea"
|
||||
},
|
||||
{
|
||||
"file": "docs/assets/ts/artplayer-proxy-canvas.d.ts",
|
||||
"sha256LF": "4f4163b4250bf001d7807c26e8e34e784eba70581997d43142febbd524a2a8f7"
|
||||
},
|
||||
{
|
||||
"file": "test/types/canvas.ts",
|
||||
"sha256LF": "ae59e12c59316c387bb41a443b65eae9ad747959e61e76c791356e8d68014d33"
|
||||
},
|
||||
{
|
||||
"file": "refactor/fixtures/consumers/canvas-published.ts",
|
||||
"sha256LF": "590578a232bd9b6716dc82e27d5b4d9ffd90943115a26ae6550f338daf40258d"
|
||||
},
|
||||
{
|
||||
"file": "refactor/scripts/canvas-types.test.mjs",
|
||||
"sha256LF": "df08de1af269201669dc93d5feb533f3c3cab1063bf94c98d1a3665e174e8fb1"
|
||||
},
|
||||
{
|
||||
"file": "refactor/scripts/canvas-package-types.mjs",
|
||||
"sha256LF": "71c4de28bc9595441ae2265fb4b45682acd637835315817310748aa4708885d7"
|
||||
},
|
||||
{
|
||||
"file": "scripts/build-ts.js",
|
||||
"sha256LF": "c9ece2c799cb81f7c35d4b5714c184c59a3a741379d3dcb970d8afbaaae98ad0"
|
||||
},
|
||||
{
|
||||
"file": "scripts/typecheck.mjs",
|
||||
"sha256LF": "583e4c855cb1541c7c2a9f812f91f32f5b5893c35db45cb457223c218250e2aa"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.js",
|
||||
"sha256LF": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.legacy.js",
|
||||
"sha256LF": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.mjs",
|
||||
"sha256LF": "66ca7271f52545a8796f563f2d5ca04581bed8d60d218463923325acf5b73ccf"
|
||||
},
|
||||
{
|
||||
"file": "docs/compiled/artplayer-proxy-canvas.js",
|
||||
"sha256LF": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73"
|
||||
},
|
||||
{
|
||||
"file": "docs/compiled/artplayer-proxy-canvas.legacy.js",
|
||||
"sha256LF": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97"
|
||||
},
|
||||
{
|
||||
"file": "docs/compiled/artplayer-proxy-canvas.mjs",
|
||||
"sha256LF": "66ca7271f52545a8796f563f2d5ca04581bed8d60d218463923325acf5b73ccf"
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-editor-build.log",
|
||||
"sha256": "d6014f1ae611921f7d0fd47e1a27b5d5488f7ef6056731948edff8864b6653e9"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-build.log",
|
||||
"sha256": "c9da09787163706b1f90adf55ff901b70963d7acc45df38e689789e0cea410b5"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-targeted.log",
|
||||
"sha256": "a1d89c6c052c003d23d22b0b26a749eb34522b96191e49af31531052805b89ca"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-lint-final.log",
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-installed-first.log",
|
||||
"sha256": "589a52e4720ea68d6aeed8145ea0af8b88f7220a61dfd9909fc9cf1fb9ab4663"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-installed-verified.log",
|
||||
"sha256": "e96728c91e3afe9431ab80addb09b2de45554720547c41a75ef6bbe402266e07"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-browser.log",
|
||||
"sha256": "d4450a918655a45add2589c62326ce4b4f80373e3600b8cee8c10e9379e3df94"
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/canvas04-ci.log",
|
||||
"sha256": "efc36adbaf3d23a8e90d66829afa01bf87fe8cc31eac927bbc5dc90f11093465"
|
||||
}
|
||||
],
|
||||
"firstHistoricalFailure": {
|
||||
"file": "refactor/.cache/canvas-package-types-qQ0exQ/published-1.1.0-5.9.3-nodenext-esm-diagnostics.json",
|
||||
"scope": "Preserved exact five diagnostics before updating the expected historical failure count for the Canvas ReturnType consumer"
|
||||
}
|
||||
}
|
||||
@@ -56,3 +56,20 @@ WebKit须验证intrinsic尺寸,不能仅alpha255;1px挂载曾令videoWidth/v
|
||||
失败诊断与针对性回归见[03记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md)和
|
||||
[03证据](baselines/canvas-lifecycle-validation.json)。04公开类型/导出和05设备、06
|
||||
完整分发/demo仍需完成;本步的strict内部类型不替代安装消费者验收。
|
||||
|
||||
## 04 公开类型和分发入口
|
||||
|
||||
`node --test refactor/scripts/canvas-types.test.mjs`运行四组基线:两代真实声明、五种
|
||||
编译器模式、11项非法用法、生成编辑器全局及三格式导出身份。Parameters保持可选
|
||||
callback,Result精确保持HTMLCanvasElement;MediaCanvas只作显式类型视图,不能
|
||||
用于收窄旧返回值或假设所有浏览器具备全部媒体API。不要复制Ambilight的必填末重载。
|
||||
|
||||
`yarn test:canvas-types-package`在仓库外安装两份实际发布及候选,校验文件字节和
|
||||
非workspace链接,离线安装与frozen reinstall均须通过。17个编译场景里,候选7个
|
||||
及历史9个应零诊断;另1个npm1.1.0 NodeNext ESM保留2349/2349/2322/2344/2344
|
||||
五个旧诊断。候选每种模式移除@ts-expect-error后须拒绝11项非法用法。
|
||||
|
||||
公开namespace是编辑器声明源,不手改docs/assets/ts生成文件。.default与工厂
|
||||
自身同一引用;根ESM/CJS及legacy类型入口各自测试。完整路径/内容/demo仍留06,
|
||||
真实Safari/移动设备仍留05。见[04实施记录](changes/2026-09-12-PKG-CANVAS-04-types.md)
|
||||
和[04证据](baselines/canvas-types-validation.json)。
|
||||
@@ -0,0 +1,24 @@
|
||||
# PKG-CANVAS-04 公开类型与导出兼容
|
||||
|
||||
公开namespace定义Option、Result、Callable、Factory及显式MediaCanvas视图。Callable
|
||||
只有可选callback签名,保持1.1.0的Parameters包含undefined;Result仍精确等于
|
||||
HTMLCanvasElement,不把返回值收窄为必须具备全部媒体属性的交叉类型。MediaCanvas
|
||||
供显式使用转发成员的消费者断言,Canvas自有成员优先,不声称新增浏览器能力。
|
||||
|
||||
入口default自别名兼容旧require.default与直接可调用工厂;ESM仅default运行时导出。
|
||||
新增.d.mts/.d.cts入口包装和import/require条件类型,typesVersions保留旧TS legacy路径。
|
||||
公开namespace接入语义编辑器声明生成,README和包内架构同步说明用法与边界。
|
||||
|
||||
四组专项覆盖五种旧/新编译器模式、11项非法用法、两份真实发布声明、生成全局声明
|
||||
以及实际三格式产物。1.0.0回调必填到1.1.0可选是既有发布差异,候选不恢复必填限制。
|
||||
新旧ReturnType消费者仍可赋值普通Canvas;没有借用Ads的类型推导修正授权。
|
||||
|
||||
仓库外真实安装先复现npm1.1.0在TS5.9.3 NodeNext ESM的五个精确诊断:
|
||||
2349/2349/2322/2344/2344。默认导入被当作CommonJS模块namespace而不可调用,
|
||||
额外ReturnType断言也受影响;该历史失败单独保留,不能把历史全部场景称为零诊断。
|
||||
候选安装消费者必须无诊断,且移除@ts-expect-error后拒绝全部11项非法用法。
|
||||
|
||||
结果和输入哈希见[验证证据](../baselines/canvas-types-validation.json)。05真实设备和
|
||||
06完整分发/demo验收仍需完成,本步不发布npm、不修改版本、不推送。
|
||||
|
||||
最终四组专项、编辑器/三格式构建、仓库外17场景和18项真实浏览器通过;17场景保留1项历史五诊断,不能等同17项零错误。完整CI1052项通过(938单元+14工程+100基线),另44项重复契约观察;284个生产TS文件严格检查。CANVAS-TYPE-01及CANVAS-DIST-01具体风险关闭,本任务done并独立本地提交,05/06及整体发布验收继续。
|
||||
@@ -0,0 +1,11 @@
|
||||
import Artplayer from 'artplayer'
|
||||
import canvas from 'artplayer-proxy-canvas'
|
||||
|
||||
const callback = (context: CanvasRenderingContext2D, video: HTMLVideoElement) => context.drawImage(video, 0, 0)
|
||||
const art = new Artplayer({ container: '#player', url: 'video.mp4', proxy: canvas(callback) })
|
||||
const result = canvas(callback)(art)
|
||||
const actual: HTMLCanvasElement = result
|
||||
const context: CanvasRenderingContext2D | null = result.getContext('2d')
|
||||
const accepted: Parameters<typeof canvas>[0] = callback
|
||||
const originalResult: ReturnType<ReturnType<typeof canvas>> = document.createElement('canvas')
|
||||
void [actual, context, accepted, originalResult]
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 221 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 123 / doing 6 / blocked 0 / done 92 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 122 / doing 6 / blocked 0 / done 93 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
| PKG-CANVAS-01 | artplayer-proxy-canvas<br>核对包契约与历史用法 | BASE-05 | callback、canvas 原方法、转发 media 属性/方法/事件 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-CANVAS-02 | artplayer-proxy-canvas<br>建立特有行为与错误测试 | PKG-CANVAS-01, ENG-03, ENG-05 | ready/loadeddata/canplay、play/seek、回调、resize 与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-CANVAS-03 | artplayer-proxy-canvas<br>整理内部职责与资源 | PKG-CANVAS-02, CORE-06, CORE-11, CORE-16 | video adapter/原 canvas 方法/RAF 绘制/事件订阅分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-CANVAS-04 | artplayer-proxy-canvas<br>迁移自有源码和公开类型 | PKG-CANVAS-03, ENG-04, ENG-06, CORE-07 | canvas 与媒体能力的精确组合类型,保持 Result | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-CANVAS-04 | artplayer-proxy-canvas<br>迁移自有源码和公开类型 | PKG-CANVAS-03, ENG-04, ENG-06, CORE-07 | canvas 与媒体能力的精确组合类型,保持 Result | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-CANVAS-05 | artplayer-proxy-canvas<br>验证新旧核心和组合 | PKG-CANVAS-04, CORE-22 | 真实 video/canvas 绘制、字幕和 document PiP 恢复 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-CANVAS-06 | artplayer-proxy-canvas<br>验证分发并同步文档 | PKG-CANVAS-05, ENG-07 | canvas.js、三种产物、调用兼容与资源证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
@@ -509,3 +509,4 @@
|
||||
- PKG-CANVAS-01: [记录](changes/2026-09-12-PKG-CANVAS-01-contract.md) [记录](baselines/canvas-contract.md) [记录](baselines/canvas-release.json) [记录](canvas-validation.md) [记录](baselines/canvas-contract-validation.json)
|
||||
- PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-CANVAS-03: [记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md) [记录](baselines/canvas-lifecycle-validation.json) [记录](canvas-validation.md)
|
||||
- PKG-CANVAS-04: [记录](changes/2026-09-12-PKG-CANVAS-04-types.md) [记录](baselines/canvas-types-validation.json) [记录](canvas-validation.md)
|
||||
@@ -1,5 +1,16 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:Canvas-04 公开类型与两代导出
|
||||
|
||||
公开namespace保留可选Parameters和精确HTMLCanvasElement返回,新增显式MediaCanvas
|
||||
视图;工厂.default自别名、ESM/CJS声明包装、旧TS legacy路径及语义编辑器生成同步。
|
||||
仓库外安装17场景:候选7个/历史9个零诊断,另1个历史NodeNext ESM保留五个精确
|
||||
诊断;候选每种模式拒绝11项非法用法。四组专项、18项真实浏览器及完整CI1052项
|
||||
通过,另44项重复契约观察,284个生产TS文件严格检查。类型/两代导出具体风险关闭,
|
||||
设备、Document PiP组合及完整demo/分发仍待05/06。当前221项:93 done、6 doing、
|
||||
122 todo。见 [本步记录](changes/2026-09-12-PKG-CANVAS-04-types.md) 和
|
||||
[验证证据](baselines/canvas-types-validation.json)。独立本地提交,无推送/发布。
|
||||
|
||||
## 最新完成:Canvas-03 严格TS职责与生命周期
|
||||
|
||||
生产源码拆为入口/媒体转发/底层video所有权/尺寸/绘制/调度六个严格TS模块,修复
|
||||
|
||||
@@ -156,6 +156,6 @@
|
||||
| AMBILIGHT-DIST-01 | resolved / 已复现 | Ambilight 1.0.0 CommonJS default namespace differs from callable 1.1.0 | PKG-AMBILIGHT-04, PKG-AMBILIGHT-06 |
|
||||
| AMBILIGHT-PROXY-01 | resolved / 已复现 | Canvas proxy output pixels differ from forwarded video dimensions | PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05 |
|
||||
| CANVAS-LIFE-01 | resolved / 已复现 | Canvas proxy pending draw, bitmap and delayed subscriptions lack terminal ownership | PKG-CANVAS-02, PKG-CANVAS-03, PKG-CANVAS-05 |
|
||||
| CANVAS-TYPE-01 | open / 源码/产物事实 | Canvas proxy declarations change required callback across generations and omit forwarded media capabilities | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-DIST-01 | open / 已复现 | Canvas proxy published CommonJS namespace differs from current callable export | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-TYPE-01 | resolved / 源码/产物事实 | Canvas proxy declarations change required callback across generations and omit forwarded media capabilities | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-DIST-01 | resolved / 已复现 | Canvas proxy published CommonJS namespace differs from current callable export | PKG-CANVAS-04, PKG-CANVAS-06 |
|
||||
| CANVAS-PIXEL-01 | resolved / 已复现 | Historical Canvas proxy produces transparent frames with detached native video in tested WebKit | PKG-CANVAS-03, PKG-CANVAS-05 |
|
||||
+16
-4
@@ -3376,12 +3376,18 @@
|
||||
],
|
||||
"compatibleResolution": "Keep optional current callback inference and actual canvas return while validating historical consumers and any additive media surface types.",
|
||||
"closureCriteria": "Strict old/current compiler consumers and installed callback/result/import variants verified without new inference narrowing.",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"evidence": [
|
||||
"refactor/baselines/canvas-contract.md",
|
||||
"refactor/baselines/canvas-release.json",
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-01-contract.md"
|
||||
]
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-04-types.md",
|
||||
"refactor/baselines/canvas-types-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Packed candidate compiles in seven old/current compiler configurations; Parameters stays optional and ReturnType stays exactly HTMLCanvasElement, including assigning an ordinary Canvas. Additive MediaCanvas is explicit opt-in. Eleven invalid uses are rejected in every candidate mode; exact archived declarations retain the pre-existing 1.0.0 required to 1.1.0 optional distinction. Candidate also corrects the recorded published 1.1.0 NodeNext ESM failure.",
|
||||
"workspaceState": "Packed candidate compiles in seven old/current compiler configurations; Parameters stays optional and ReturnType stays exactly HTMLCanvasElement, including assigning an ordinary Canvas. Additive MediaCanvas is explicit opt-in. Eleven invalid uses are rejected in every candidate mode; exact archived declarations retain the pre-existing 1.0.0 required to 1.1.0 optional distinction. Candidate also corrects the recorded published 1.1.0 NodeNext ESM failure."
|
||||
},
|
||||
{
|
||||
"id": "CANVAS-DIST-01",
|
||||
@@ -3393,12 +3399,18 @@
|
||||
],
|
||||
"compatibleResolution": "Preserve both require.default and callable factory with actual global, legacy and ESM paths.",
|
||||
"closureCriteria": "Packed runtime/type and real local demo/editor consumers pass.",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"evidence": [
|
||||
"refactor/baselines/canvas-contract.md",
|
||||
"refactor/baselines/canvas-release.json",
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-01-contract.md"
|
||||
]
|
||||
],
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-CANVAS-04-types.md",
|
||||
"refactor/baselines/canvas-types-validation.json"
|
||||
],
|
||||
"resolutionRationale": "Production main/legacy/browser global and ESM plus real isolated installed root/legacy imports retain callable factory and self default identities. Seven installed type consumer modes validate the correct declaration wrapper including no-interop. Full deep paths/content/demo and device checks remain tasks 05/06.",
|
||||
"workspaceState": "Production main/legacy/browser global and ESM plus real isolated installed root/legacy imports retain callable factory and self default identities. Seven installed type consumer modes validate the correct declaration wrapper including no-interop. Full deep paths/content/demo and device checks remain tasks 05/06."
|
||||
},
|
||||
{
|
||||
"id": "CANVAS-PIXEL-01",
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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 { verifyCanvasContract } from './canvas-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:canvas-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/canvas-package-types-'))
|
||||
console.log(`Canvas isolated package evidence: ${output}`)
|
||||
const contract = await verifyCanvasContract()
|
||||
const packages = []
|
||||
for (const name of ['artplayer', 'artplayer-proxy-canvas']) {
|
||||
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[0], 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: 'canvas-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, plugin.label === 'candidate'
|
||||
? `const assert = require('node:assert/strict');
|
||||
for (const name of ['artplayer-proxy-canvas', 'artplayer-proxy-canvas/legacy']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(typeof factory(), 'function'); }
|
||||
import('artplayer-proxy-canvas').then(module => { assert.deepEqual(Object.keys(module), ['default']); assert.equal(module.default.default, module.default); }).catch(error => { console.error(error); process.exitCode = 1; });`
|
||||
: `const assert = require('node:assert/strict'); const value = require('artplayer-proxy-canvas'); assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}'); assert.equal(typeof (value.default || value)(() => {}), 'function');`)
|
||||
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 source = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/canvas.ts' : 'refactor/fixtures/consumers/canvas-published.ts'), 'utf8')
|
||||
const filename = path.join(consumer, `consumer.${extension}`)
|
||||
const historical = path.join(consumer, `historical.${extension}`)
|
||||
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/canvas-published.ts'), historical)
|
||||
const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
|
||||
if (mode === 'nodenext-cjs' && plugin.label !== 'published-1.1.0') {
|
||||
const commonjs = path.join(consumer, 'commonjs.cts')
|
||||
fs.writeFileSync(commonjs, `import canvas = require('artplayer-proxy-canvas'); canvas((ctx, video) => ctx.drawImage(video, 0, 0)); ${plugin.label === 'candidate' ? 'canvas.default();' : ''}`)
|
||||
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}`)
|
||||
}
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
|
||||
}
|
||||
const diagnostics = compile(source)
|
||||
const historicalFailure = plugin.label === 'published-1.1.0' && mode === 'nodenext-esm'
|
||||
writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}-diagnostics.json`), { diagnostics, historicalFailure })
|
||||
assert.deepEqual(diagnostics.map(item => item.code), historicalFailure ? [2349, 2349, 2322, 2344, 2344] : [], `${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, 11, '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: 'canvas-isolated-package-types', introducedBy: 'PKG-CANVAS-04', scope: 'Two actual published proxies and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, compiler/default/import= contracts. Published 1.0.0 required callback became optional in 1.1.0; candidate retains optional Parameters and exact HTMLCanvasElement return inference. This is not native proxy media, device or complete distribution acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
|
||||
console.log(`Canvas installed types passed: ${matrix.length} cases; ${output}`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
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 -- Node baseline runner.
|
||||
import test from 'node:test'
|
||||
import vm from 'node:vm'
|
||||
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 { verifyCanvasContract } from './canvas-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
test('Canvas current types preserve optional Parameters and exact Canvas result, while rejecting eleven invalid uses', () => {
|
||||
const source = fs.readFileSync('test/types/canvas.ts', 'utf8')
|
||||
for (const [compiler, mode] of modes) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
assert.equal(checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')).length, 11)
|
||||
}
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import canvas = require('artplayer-proxy-canvas'); import legacy = require('artplayer-proxy-canvas/legacy');
|
||||
const callback: canvas.Option = (ctx, video) => ctx.drawImage(video, 0, 0); canvas(callback); canvas.default(); legacy(); legacy.default(callback);`), [])
|
||||
})
|
||||
|
||||
test('Canvas published declarations distinguish required 1.0.0 and optional 1.1.0 callbacks without changing Canvas return assignability', async () => {
|
||||
const contract = await verifyCanvasContract()
|
||||
const source = fs.readFileSync('refactor/fixtures/consumers/canvas-published.ts', 'utf8')
|
||||
const optional = `const omitted: Parameters<typeof canvas>[0] = undefined; canvas(); void omitted;`
|
||||
for (const version of ['1.0.0', '1.1.0']) {
|
||||
const old = readMember(contract.archives.get(version), 'package/types/artplayer-proxy-canvas.d.ts').toString()
|
||||
for (const compiler of [ts, compat]) {
|
||||
const filename = path.resolve('test/types/canvas-history.ts')
|
||||
const declaration = path.resolve('test/types/canvas-history.d.ts')
|
||||
function historical(code) {
|
||||
const host = compiler.createCompilerHost({})
|
||||
const original = host.getSourceFile.bind(host)
|
||||
host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
|
||||
? compiler.createSourceFile(file, code.replace('from \'artplayer-proxy-canvas\'', 'from \'./canvas-history\''), language, true)
|
||||
: path.resolve(file) === declaration ? compiler.createSourceFile(file, old, language, true) : original(file, language, ...rest)
|
||||
const exists = host.fileExists.bind(host)
|
||||
host.fileExists = file => path.resolve(file) === declaration || exists(file)
|
||||
const program = compiler.createProgram([filename, declaration], { 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'] }, host)
|
||||
assert(program.getSourceFile(declaration))
|
||||
return compiler.getPreEmitDiagnostics(program).map(item => item.code)
|
||||
}
|
||||
assert.deepEqual(historical(source), [])
|
||||
assert.deepEqual(historical(`${source}\n${optional}`), version === '1.0.0' ? [2322, 2554] : [])
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', `${source}\n${optional}`), [])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('Canvas editor globals are generated from public types with optional callback, media view and negative checking', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-proxy-canvas/types/artplayer-proxy-canvas.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerProxyCanvas')
|
||||
const file = 'docs/assets/ts/artplayer-proxy-canvas.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 = `artplayerProxyCanvas(); artplayerProxyCanvas.default((ctx, video) => ctx.drawImage(video, 0, 0));
|
||||
const result: artplayerProxyCanvas.Result = document.createElement('canvas');
|
||||
const media = result as artplayerProxyCanvas.MediaCanvas; media.play();
|
||||
// @ts-expect-error Callback must be a function in public types.
|
||||
artplayerProxyCanvas(42);`
|
||||
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, 1)
|
||||
}
|
||||
})
|
||||
|
||||
test('Canvas production main, legacy, browser global and ESM preserve both callable and historical default identities', async () => {
|
||||
for (const suffix of ['js', 'legacy.js']) {
|
||||
const source = fs.readFileSync(`packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.${suffix}`, 'utf8')
|
||||
const module = { exports: {} }
|
||||
vm.runInNewContext(source, { module, exports: module.exports, window: {} })
|
||||
assert.equal(typeof module.exports, 'function')
|
||||
assert.equal(module.exports.default, module.exports)
|
||||
assert.equal(typeof module.exports.default(), 'function')
|
||||
const global = { window: {} }
|
||||
vm.runInNewContext(source, global)
|
||||
const factory = global.artplayerProxyCanvas || global.window.artplayerProxyCanvas
|
||||
assert.equal(factory.default, factory)
|
||||
}
|
||||
const source = fs.readFileSync('packages/artplayer-proxy-canvas/dist/artplayer-proxy-canvas.mjs', 'utf8')
|
||||
const module = await import(`data:text/javascript,${encodeURIComponent(source)}`)
|
||||
assert.deepEqual(Object.keys(module), ['default'])
|
||||
assert.equal(module.default.default, module.default)
|
||||
})
|
||||
+6
-2
@@ -3314,11 +3314,15 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "canvas 与媒体能力的精确组合类型,保持 Result",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-CANVAS-04-types.md",
|
||||
"baselines/canvas-types-validation.json",
|
||||
"canvas-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-CANVAS-05",
|
||||
|
||||
Reference in new issue
Block a user