feat(auto-thumbnail): [PKG-AUTO-THUMB-08] add compatible runtime type entry

This commit is contained in:
Harvey Zhao committed 2026-09-14 04:07:59 +08:00
1 parent 7be628a983
commit c4f5dcf17f
27 files changed
+2005 -40

No files matched your search

+15 -14
View File
@@ -1,17 +1,18 @@
interface Option {
url?: string
width?: number
number?: number
scale?: number
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace artplayerPluginAutoThumbnailDefinitions {
export interface Option {
url?: string
width?: number
number?: number
scale?: number
}
export interface Result {
name: 'artplayerPluginAutoThumbnail'
}
export const artplayerPluginAutoThumbnail: (option: Option) => (art: Artplayer) => Result
}
interface Result {
name: 'artplayerPluginAutoThumbnail'
}
declare const artplayerPluginAutoThumbnail: (option: Option) => (art: Artplayer) => Result
export default artplayerPluginAutoThumbnail
declare const artplayerPluginAutoThumbnail: typeof artplayerPluginAutoThumbnailDefinitions.artplayerPluginAutoThumbnail
declare namespace artplayerPluginAutoThumbnail { }
export = artplayerPluginAutoThumbnail
export as namespace artplayerPluginAutoThumbnail;
+4 -2
View File
@@ -85,7 +85,7 @@
"test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs",
"test:thumbnail": "node --test test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js refactor/scripts/thumbnail-contract.test.mjs refactor/scripts/thumbnail-types.test.mjs",
"test:thumbnail-types-package": "node refactor/scripts/thumbnail-package-types.mjs",
"test:auto-thumbnail": "node --test test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs",
"test:auto-thumbnail": "node --test test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js refactor/scripts/auto-thumbnail-contract.test.mjs refactor/scripts/auto-thumbnail-types.test.mjs",
"test:vtt-thumbnail": "node --test test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js refactor/scripts/vtt-thumbnail-contract.test.mjs refactor/scripts/vtt-thumbnail-types.test.mjs",
"test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs",
"test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
@@ -105,7 +105,9 @@
"test:danmuku": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs",
"test:danmuku-types": "node --test refactor/scripts/danmuku-types.test.mjs",
"test:danmuku-types-package": "node refactor/scripts/danmuku-package-types.mjs",
"test:danmuku-mask-types-package": "node refactor/scripts/danmuku-mask-package-types.mjs"
"test:danmuku-mask-types-package": "node refactor/scripts/danmuku-mask-package-types.mjs",
"test:auto-thumbnail-types": "node --test refactor/scripts/auto-thumbnail-types.test.mjs",
"test:auto-thumbnail-types-package": "node refactor/scripts/auto-thumbnail-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -46,7 +46,17 @@ for video processing.
cleanup handles every frame without accumulating per-frame cleanup closures.
- `src/types.ts` defines internal configuration, sheet data, the minimal host and
guarded extraction-job callbacks. `guard` retains argument/result types and the
inactive undefined result. No public declarations are generated from this file.
inactive undefined result. It shares the opt-in `runtime-api.d.ts` option type;
the entry's Promise result is checked against that public result. No public
declarations are generated from this file.
The original `types/artplayer-plugin-auto-thumbnail.d.ts` remains byte-equivalent
to npm 1.1.0 after newline normalization. `types/runtime-api.d.ts` owns the precise
option/result/factory contracts; `runtime.d.mts`, `runtime.d.cts`, and `runtime.d.ts`
adapt module resolution without adding runtime code or a factory `.default` alias.
The root editor declaration is generated semantically by `yarn build:ts`; its
historical synchronous callable shape is retained, without mixing default and
export-assignment syntax. See README for the optional accurate import.
Only the entry module receives ArtPlayer. Extraction receives a guarded job and a
configuration snapshot. These internal modules do not add public player fields,
@@ -90,6 +100,8 @@ Use the root Yarn toolchain:
```sh
yarn test:auto-thumbnail
yarn test:auto-thumbnail-types
yarn test:auto-thumbnail-types-package
yarn exec tsc -p packages/artplayer-plugin-auto-thumbnail/tsconfig.json --noEmit
yarn test:browser test/browser/auto-thumbnail-lifecycle.spec.js
yarn test:browser test/browser/auto-thumbnail-pixels.spec.js
@@ -101,7 +113,10 @@ regression tests. `ARTPLAYER_AUTO_THUMBNAIL_ARTIFACT` selects an actual bundle.
Historical contract/failure tests remain separate and continue to reproduce old
defects; their passing status does not mean those defects should remain.
This is an intermediate `PKG-AUTO-THUMB-03` implementation. Native lifecycle
The public type entry is tracked independently as `PKG-AUTO-THUMB-08`, split from
04 so that declaration work can proceed without weakening the unfinished 03
pixel gate. Task04 still depends on both 03 and 08. The source/runtime remains an
intermediate `PKG-AUTO-THUMB-03` implementation. Native lifecycle
validation does not prove correct pixels. The attached renderer now has native
pixel checks for all five cells when native frame callbacks are available,
including the unique first frame, real black content, changing colors, spatial
@@ -9,3 +9,49 @@ Auto thumbnail plugin for ArtPlayer
## License
MIT © Harvey Zhao
## TypeScript
The root and `/legacy` entries retain the declarations published in 1.1.0,
including the historical synchronous result type. The implementation actually
returns a Promise when registering the plugin. For accurate types, opt into
`/runtime`, which uses the same JavaScript implementation:
```ts
import Artplayer from 'artplayer'
import autoThumbnail from 'artplayer-plugin-auto-thumbnail/runtime'
import type { Option, Result } from 'artplayer-plugin-auto-thumbnail/runtime'
const option: Option = { width: 160, number: 100, scale: 1 }
const art = new Artplayer({
container: '#player',
url: '/video.mp4',
plugins: [autoThumbnail(option)],
})
```
The registrar has type `(art: Artplayer) => Promise<Result>`. It resolves with
`{ name: 'artplayerPluginAutoThumbnail' }` after subscribing to player events,
before extraction completes. Generated sheets arrive progressively through the
player's thumbnails configuration. Awaiting registration does not wait for them.
The options object is required; use `{}` for defaults. `height` is accepted by
the runtime type for historical callers but remains ignored: the video aspect
ratio determines sheet height. Numeric strings retain their old JavaScript
coercion behavior but are not advertised as numeric TypeScript options.
The runtime entry supports default ESM imports and CommonJS
`import autoThumbnail = require('artplayer-plugin-auto-thumbnail/runtime')`.
It exports the `Option`, `Result`, and `Factory` types. The factory has no
`.default` self alias and the result has no extraction-completion or destroy API.
Node10 resolution uses `typesVersions`; modern resolution uses separate ESM/CJS
declarations. The old root's NodeNext namespace behavior is preserved; `/runtime`
provides a direct default-import type without changing that old declaration.
Versions 1.0.x used `export =` declarations and advertised `height` instead of
`number`; 1.1.0 had already changed these types. This refactor retains the actual
1.1.0 root declaration rather than introducing another conflicting root shape.
Consumers needing the older ignored `height` option can use `/runtime`.
See [ARCHITECTURE.md](ARCHITECTURE.md) for module ownership, tests, and remaining
browser extraction limitations. This type entry does not resolve the documented
Windows WebKit first-frame issue.
@@ -27,11 +27,31 @@
"types": "./types/artplayer-plugin-auto-thumbnail.d.ts",
"import": "./dist/artplayer-plugin-auto-thumbnail.legacy.js",
"require": "./dist/artplayer-plugin-auto-thumbnail.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-auto-thumbnail.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-auto-thumbnail.js"
}
}
},
"main": "./dist/artplayer-plugin-auto-thumbnail.js",
"module": "./dist/artplayer-plugin-auto-thumbnail.mjs",
"types": "./types/artplayer-plugin-auto-thumbnail.d.ts",
"legacy": "./dist/artplayer-plugin-auto-thumbnail.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-auto-thumbnail.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
}
@@ -1,10 +1,11 @@
import type { Result } from '../types/runtime-api'
import type { Cleanup, ThumbnailHost, ThumbnailOptions } from './types'
import extract from './extraction'
import { readOptions } from './options'
import createSession, { cleanupAll } from './session'
export default function artplayerPluginAutoThumbnail(option: ThumbnailOptions) {
return async (art: ThumbnailHost) => {
return async (art: ThumbnailHost): Promise<Result> => {
const report = (error: unknown) => console.warn('ArtPlayer auto-thumbnail failed:', error)
const session = createSession((config) => {
art.thumbnails = config
@@ -1,11 +1,5 @@
// Internal contracts only. Public declarations retain their historical entrypoint.
export interface ThumbnailOptions {
url?: string
width?: number
number?: number
scale?: number
height?: number
}
// Root declarations retain their historical shape; the opt-in runtime view is shared.
export type { Option as ThumbnailOptions } from '../types/runtime-api'
export interface ExtractionConfig {
url: string
@@ -0,0 +1,21 @@
// Support both the core's ESM/default and CommonJS export= declarations.
type CoreModule = typeof import('artplayer')
type Artplayer = CoreModule extends { default: { prototype: infer Instance } }
? Instance
: CoreModule extends { prototype: infer Instance } ? Instance : never
export interface Option {
url?: string
width?: number
number?: number
scale?: number
/** Historical option; sheet height is derived from the video's aspect ratio. */
height?: number
}
export interface Result {
name: 'artplayerPluginAutoThumbnail'
}
/** Registration resolves before frame extraction and progressive sheet updates. */
export type Factory = (option: Option) => (art: Artplayer) => Promise<Result>
@@ -0,0 +1,10 @@
import type { Factory } from './runtime-api.js'
declare const artplayerPluginAutoThumbnail: Factory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginAutoThumbnail {
type Option = import('./runtime-api.js').Option
type Result = import('./runtime-api.js').Result
type Factory = import('./runtime-api.js').Factory
}
export = artplayerPluginAutoThumbnail
@@ -0,0 +1,5 @@
import type { Factory } from './runtime-api.js'
declare const artplayerPluginAutoThumbnail: Factory
export default artplayerPluginAutoThumbnail
export type { Factory, Option, Result } from './runtime-api.js'
@@ -0,0 +1,10 @@
import type { Factory } from './runtime-api.js'
declare const artplayerPluginAutoThumbnail: Factory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginAutoThumbnail {
type Option = import('./runtime-api.js').Option
type Result = import('./runtime-api.js').Result
type Factory = import('./runtime-api.js').Factory
}
export = artplayerPluginAutoThumbnail
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,59 @@
# PKG-AUTO-THUMB-08 兼容公开类型与异步入口
从尚未开始的 04 中拆出独立声明交付,依赖已完成的契约、测试与类型基础设施。
04 仍依赖 03 和本任务;03 的 WebKit 首两格像素等门槛没有移走或降低。
本项不等于整个 Auto Thumbnail 包完成。
## 实现与兼容边界
实际 npm 1.1.0 的根声明保持原字节(仅允许 checkout 换行格式不同),包括必填
options、同步 Result、Parameters/ReturnType、普通替代函数和模块 default 形状。
没有为修正 Promise 返回值给旧工厂追加重载、属性或改变类型提取。
新增可选 `/runtime`,通过 package exports 复用根入口的 main/ESM 文件,没有
另一套运行实现。runtime-api.d.ts 定义 Option/Result/Factory,三个薄声明入口
分别处理 NodeNext ESM、CommonJS 和旧 Node10 解析。Promise 只代表注册完成,
不代表抽帧完成。工厂实际没有 `.default` 自引用,因此声明也不提供;历史忽略
的 height 在准确选项类型中保留并注明,number/scale/width 仍有明确数字类型。
内部 option 与这份声明共用,入口显式返回 Promise<Result>,实施 fixture 校验
真实源码函数可赋给公开 Factory,避免声明与自有源码分离漂移。
新增 typesVersions 也补齐 Node10 对旧 `/legacy` 子路径的声明解析。真实 npm
1.1.0 在该路径存在 TS2307,TS4.3.5 另有依赖此 unresolved type 的 TS2344;
保留精确历史诊断,并另用根入口验证旧消费者。未把历史失败吞掉或称为旧包通过。
编辑器声明现在走已有语义生成器,避免同时出现 export default 与 export =。
旧生成方式的 TS2309 有回归;新全局 callable 保留旧同步形状。没有往原根模块
添加 Option/Result 命名导出;旧类型仍通过 Parameters/ReturnType 提取。
README、ARCHITECTURE 与 tests README 同步维护实现地图、使用方式和验证范围。
没有新依赖、锁文件变更、包版本提升、推送或发布。
## 验证与中间失败
结果及实际包/文件指纹见 [验证记录](../baselines/auto-thumbnail-types-validation.json)。
最新 npm 1.1.0 和候选实际 tarball 在仓库外安装,以固定 Yarn 离线强制 frozen
重装并核对每个归档文件字节。TS5.9.3 的 Node10/CJS/ESM/bundler 与 TS4.3.5
Node10 共十组;候选另验证三组关闭 interop 的 CommonJS 消费。
正例检查完整工厂、参数元组、结果和普通模块替换;负例逐行匹配,避免 any 或
未解析模块导致假绿。实际安装 JS 测试 root/legacy/runtime 的 require/import
形状、共享函数身份、Promise 注册、结果及候选订阅清理,不触发视频提取。
初次编辑器测试误用未从根模块导出的命名类型,已改为原有类型提取;没有扩大
根 API 来迁就测试。初次历史诊断断言未区分 TS4.3.5 与 TS5.9.3 的二级错误,
已按实际版本精确记录。真实 no-interop 安装测试发现 runtime-api 的 default
core import 在 NodeNext CJS 下失败,改用核心模块的实际 constructor prototype
类型提取后通过。原失败报告在缓存中保留。最初 Yarn exec 未找到 eslint binary,
改用固定 Node 执行已安装的 ESLint;工具链 frozen 安装和严格检查另行通过。
本项类型工作不是首帧修复,也不代表三浏览器重跑。历史 1.0.x 的 export= 与
1.1.0 的 default、height/number 声明差异已是发布历史的一部分;根保持 1.1.0,
本次没有新增旧类型破坏授权。1.0.x 的完整隔离消费/导出门面仍留在 04/06 与
AUTO-THUMB-TYPE-01、AUTO-THUMB-EXPORT-01,不能仅凭本项关闭整个历史范围。
## 回退与接续
本提交可独立 revert:移除新增类型入口、选项共享和语义编辑器生成接入,恢复
原 manifest、生成文件与测试脚本;原运行算法不变。保留 07 的画布清理修复。
接续优先处理 03 的抽帧真实像素与资源边界;04 汇总自有源码、全部旧消费范围,
05/06 再执行组合、设备、分发及文档完整验收。
@@ -1,6 +1,8 @@
import type Artplayer from 'artplayer'
import type { Factory, Result } from '../../../packages/artplayer-plugin-auto-thumbnail/types/runtime-api'
import factory from '../../../packages/artplayer-plugin-auto-thumbnail/src'
declare const art: Artplayer
const registration: Promise<{ name: string }> = factory({ width: 80, number: 5 })(art)
export { registration }
const registration: Promise<Result> = factory({ width: 80, number: 5 })(art)
const publicFactory: Factory = factory
export { publicFactory, registration }
+6 -4
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 235 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 236 项,范围 22 个包及工作区/示例。
状态:todo 62 / doing 15 / blocked 0 / done 158 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 62 / doing 15 / blocked 0 / done 159 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -19,7 +19,7 @@
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 |
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09 |
| artplayer-plugin-audio-track | 1.1.0 | PKG-AUDIO-01, PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-04, PKG-AUDIO-05, PKG-AUDIO-06 |
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07 |
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08 |
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06 |
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12 |
@@ -175,10 +175,11 @@
| PKG-AUTO-THUMB-01 | artplayer-plugin-auto-thumbnail<br>核对包契约与历史用法 | BASE-05 | url/width/number/scale、异步工厂和渐进缩略图更新 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-AUTO-THUMB-02 | artplayer-plugin-auto-thumbnail<br>建立特有行为与错误测试 | PKG-AUTO-THUMB-01, ENG-03, ENG-05 | 抽帧边界、短视频、失败、连续来源和销毁中的抽帧 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-AUTO-THUMB-03 | artplayer-plugin-auto-thumbnail<br>整理内部职责与资源 | PKG-AUTO-THUMB-02, CORE-09, CORE-19, PKG-AUTO-THUMB-07 | 隐藏 video、seek 队列、canvas 编码与 Blob URL 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | doing |
| PKG-AUTO-THUMB-04 | artplayer-plugin-auto-thumbnail<br>迁移自有源码和公开类型 | PKG-AUTO-THUMB-03, ENG-04, ENG-06, CORE-07 | 抽帧结果和真实异步插件返回类型兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-AUTO-THUMB-04 | artplayer-plugin-auto-thumbnail<br>迁移自有源码和公开类型 | PKG-AUTO-THUMB-03, ENG-04, ENG-06, CORE-07, PKG-AUTO-THUMB-08 | 抽帧结果和真实异步插件返回类型兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-AUTO-THUMB-05 | artplayer-plugin-auto-thumbnail<br>验证新旧核心和组合 | PKG-AUTO-THUMB-04, CORE-22 | 旧核心/候选核心预览、多次加载无过期缩略图覆盖 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-AUTO-THUMB-06 | artplayer-plugin-auto-thumbnail<br>验证分发并同步文档 | PKG-AUTO-THUMB-05, ENG-07 | auto.thumbnail.js、三种产物和内存/URL 证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-AUTO-THUMB-07 | artplayer-plugin-auto-thumbnail<br>终止抽帧时清空私有画布像素尺寸 | PKG-AUTO-THUMB-02, CORE-09, CORE-19 | 将私有 canvas 尺寸归入 job 清理;完成、取消、失败及分配重入均有回归,保留已编码 JPEG | 旧红新绿证明保留回调时画布归零,单项清理失败不阻断其他资源;原生取消和完成后 JPEG 仍可解码,原 API/时间公式/类型不变 | M | done |
| PKG-AUTO-THUMB-08 | artplayer-plugin-auto-thumbnail<br>保留旧声明并提供准确异步类型入口 | PKG-AUTO-THUMB-02, ENG-04, ENG-06, CORE-07 | 保留实际 npm 1.1.0 根声明;新增同实现 /runtime 类型、编辑器声明及隔离安装消费者 | 原 Parameters/ReturnType/替代函数不变;准确 Promise 类型正反例、CJS/ESM 条件入口和声明解析通过;不把像素与设备缺口计为通过 | M | done |
## 5 包迁移:artplayer-plugin-vtt-thumbnail
@@ -608,3 +609,4 @@
- PKG-JASSUB-08: [记录](changes/2026-09-14-PKG-JASSUB-08-offscreen.md) [记录](baselines/jassub-offscreen-validation.json) [记录](baselines/jassub-offscreen-patch.json)
- PKG-JASSUB-09: [记录](changes/2026-09-14-PKG-JASSUB-09-hybrid.md) [记录](baselines/jassub-hybrid-validation.json) [记录](baselines/jassub-hybrid-patch.json) [记录](changes/2026-09-14-PKG-JASSUB-09-firefox-diagnostics.md) [记录](changes/2026-09-14-PKG-JASSUB-09-display.md) [记录](baselines/jassub-display-validation.json)
- PKG-AUTO-THUMB-07: [记录](changes/2026-09-14-PKG-AUTO-THUMB-07-canvas.md) [记录](baselines/auto-thumbnail-canvas-validation.json)
- PKG-AUTO-THUMB-08: [记录](changes/2026-09-14-PKG-AUTO-THUMB-08-types.md) [记录](baselines/auto-thumbnail-types-validation.json)
+16
View File
@@ -1,5 +1,21 @@
# 进度与证据
## PKG-AUTO-THUMB-08 公开类型入口完成
保留实际 npm 1.1.0 根声明字节与旧工厂提取/替换形状,新增复用原运行文件的
`/runtime`,准确声明 Promise 注册、选项和结果;未添加不存在的 default 自引用。
内部选项与运行类型共用,源码返回值通过公开 Factory 验证。编辑器改用语义
生成器,修复混合导出声明;Node10 legacy 类型路径已补齐。最新与候选实际安装
十组编译配置和三组无 interop 验证通过,安装产物 Promise 注册与清理通过。
联合 163 项、最终三项类型回归、全仓严格类型检查(406 个生产 TS 文件)、lint、
正常构建、frozen Yarn 安装和严格工具链检查通过;构建运行代码与起点归一化
换行后完全一致。中间 no-interop 声明失败已修复并留证。没有新增依赖或发布。
见[变更](changes/2026-09-14-PKG-AUTO-THUMB-08-types.md)与
[验证](baselines/auto-thumbnail-types-validation.json)。本项从 04 独立拆出,04
仍依赖 03 与 08;03 首帧、1.0.x 完整历史隔离消费及 05/06 仍未完成,不关闭
AUTO-THUMB-PIXEL-01/TYPE-01/EXPORT-01。源码和公开类型阶段完全完成的插件仍
为 14/16;全计划 236 项中 159 done、15 doing、62 todo。
## PKG-AUTO-THUMB-07 私有画布清理完成
抽帧 canvas 现由 job 清理宽高;取消、失败和正常完成后归零,单个 reset 抛错
+1 -1
View File
@@ -183,7 +183,7 @@
| THUMB-MEDIA-01 | open / 已复现 | Windows WebKit native Blob URLs cannot load tested MP4 media despite successful HTTP controls | PKG-TOOL-THUMB-05, PKG-TOOL-THUMB-06 |
| VENDOR-09 | resolved / 源码/产物事实 | Thumbnail tiny-emitter 改写来源与最终分发许可 | PKG-TOOL-THUMB-04, PKG-TOOL-THUMB-06 |
| THUMB-EVENT-01 | resolved / 已复现 | Thumbnail 原型事件名与嵌套 once 派发缺陷 | PKG-TOOL-THUMB-04 |
| AUTO-THUMB-TYPE-01 | open / 已复现 | Published declarations misdescribe async registration and switch height/number option fields across generations | PKG-AUTO-THUMB-04 |
| AUTO-THUMB-TYPE-01 | open / 已复现 | Published declarations misdescribe async registration and switch height/number option fields across generations | PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-08 |
| AUTO-THUMB-EXPORT-01 | open / 已复现 | Published 1.0.1 CommonJS default namespace differs from the direct function exported by 1.1.0 | PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-06 |
| AUTO-THUMB-DIST-01 | open / 已复现 | Published 1.0.0 archive lacks declared main and legacy runtimes | PKG-AUTO-THUMB-06 |
| AUTO-THUMB-LIFE-01 | open / 已复现 | Extraction tasks, decoder handlers and final object URLs lack source-switch and destroy ownership | PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-05 |
+6 -3
View File
@@ -4050,14 +4050,17 @@
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-AUTO-THUMB-04"
"PKG-AUTO-THUMB-04",
"PKG-AUTO-THUMB-08"
],
"evidence": [
"refactor/baselines/auto-thumbnail-release.json",
"refactor/baselines/auto-thumbnail-contract.md",
"refactor/scripts/auto-thumbnail-contract.test.mjs"
"refactor/scripts/auto-thumbnail-contract.test.mjs",
"refactor/changes/2026-09-14-PKG-AUTO-THUMB-08-types.md",
"refactor/baselines/auto-thumbnail-types-validation.json"
],
"compatibleResolution": "Preserve actual Promise runtime and old valid consumers; explicitly retain the historically ignored height field and verify paired CJS/ESM declarations.",
"compatibleResolution": "Preserve actual Promise runtime and old valid consumers; explicitly retain the historically ignored height field and verify paired CJS/ESM declarations. PKG-AUTO-THUMB-08 retains npm 1.1.0 root bytes and adds tested /runtime types and editor generation; older 1.0.x isolated consumer/export evidence is still pending in 04/06.",
"closureCriteria": "Old and current isolated consumers, negative type cases and matching runtime registration pass; any irreconcilable inference change has an explicit user decision."
},
{
@@ -0,0 +1,43 @@
import fs from 'node:fs'
export const publicInvalid = [
'thumbnail()',
'thumbnail(undefined)',
'thumbnail({ width: "80" })',
'thumbnail({ height: 90 })',
'const asynchronous: Promise<{name: "artplayerPluginAutoThumbnail"}> = result',
'thumbnail.default({})',
]
export const runtimeInvalid = [
'runtime()',
'runtime(undefined)',
'runtime({ url: 1 })',
'runtime({ width: "80" })',
'runtime({ number: false })',
'runtime({ scale: "1" })',
'runtime({ height: "90" })',
'runtime({ unknown: true })',
'runtime({})()',
'pending.name',
'runtime.default({})',
'const synchronous: Result = pending',
'const wrong: Factory = (_option) => (_art) => ({name: "artplayerPluginAutoThumbnail"})',
]
export function publicSource(mode, raw = false) {
let source = fs.readFileSync(new URL('../../test/types/auto-thumbnail-public.ts', import.meta.url), 'utf8')
if (mode === 'nodenext-esm' && !raw) {
source = source.replace('import thumbnail from \'artplayer-plugin-auto-thumbnail\'', 'import thumbnailModule from \'artplayer-plugin-auto-thumbnail\'\nconst thumbnail = thumbnailModule.default')
.replace('import legacy from \'artplayer-plugin-auto-thumbnail/legacy\'', 'import legacyModule from \'artplayer-plugin-auto-thumbnail/legacy\'\nconst legacy = legacyModule.default')
}
return source
}
export function runtimeSource(mode) {
const source = fs.readFileSync(new URL('../../test/types/auto-thumbnail-runtime.ts', import.meta.url), 'utf8')
return mode.endsWith('-no-interop')
? source.replace('import runtime from \'artplayer-plugin-auto-thumbnail/runtime\'', 'import runtime = require(\'artplayer-plugin-auto-thumbnail/runtime\')')
.replace('import type Artplayer from \'artplayer\'', 'type Core = typeof import(\'artplayer\'); type Artplayer = Core extends { default: { prototype: infer I } } ? I : Core extends { prototype: infer I } ? I : never')
: source
}
@@ -0,0 +1,150 @@
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 { publicInvalid, publicSource, runtimeInvalid, runtimeSource } from './auto-thumbnail-consumer.mjs'
import { verifyAutoThumbnailContract } from './auto-thumbnail-contract.mjs'
import { hash, readMember } from './releases.mjs'
const name = 'artplayer-plugin-auto-thumbnail'
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim())
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/auto-thumbnail-package-types-'))
console.log(`Auto Thumbnail installed consumer evidence: ${output}`)
const contract = await verifyAutoThumbnailContract()
const packages = []
for (const packageName of ['artplayer', name]) {
const archive = path.join(output, `${packageName}.tgz`)
fs.writeFileSync(path.join(output, `${packageName}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', packageName)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, packageName === name ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name: packageName, manifest, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const [core, candidate] = packages
const declaration = `package/types/${name}.d.ts`
const normalize = value => value.toString().replaceAll('\r\n', '\n')
assert.equal(normalize(readMember(candidate.archive, declaration)), normalize(readMember(contract.archives.get('1.1.0'), declaration)))
const matrix = []
function report() {
return writeJson(path.join(output, 'report.json'), {
task: 'PKG-AUTO-THUMB-08',
node: process.version,
packages,
matrix,
scope: 'Actual npm 1.1.0 and packed candidate installed outside the workspace. Root historical type shape and conditional runtime entry are checked with exact negative lines. Registration and module identities use installed artifacts; no native extraction or device acceptance is implied.',
})
}
for (const plugin of [{ ...contract.baseline.release, archive: contract.archives.get('1.1.0'), label: 'published-1.1.0' }, { ...candidate, label: 'candidate' }]) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'auto-thumbnail-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.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
for (const pkg of [core, plugin]) {
for (const member of packedFiles(pkg.archive)) {
assert.deepEqual(fs.readFileSync(path.join(consumer, 'node_modules', pkg.name, member.replace(/^package\//, ''))), readMember(pkg.archive, member), member)
}
}
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
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,
}
let declarations = []
const compile = (source, interop = true) => {
fs.writeFileSync(filename, source)
const program = compiler.createProgram([filename], { ...options, esModuleInterop: interop })
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}`)
}
declarations = program.getSourceFiles().filter(file => !program.isSourceFileDefaultLibrary(file)).map(file => path.relative(consumer, file.fileName).replaceAll('\\', '/')).sort()
return compiler.getPreEmitDiagnostics(program).map(item => ({
file: item.file ? path.relative(consumer, item.file.fileName).replaceAll('\\', '/') : null,
code: item.code,
line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null,
message: compiler.flattenDiagnosticMessageText(item.messageText, '\n'),
}))
}
const entry = { plugin: plugin.label, compiler: compiler.version, mode }
matrix.push(entry)
let source = publicSource(mode)
entry.public = compile(source)
if (plugin.label === 'published-1.1.0' && mode === 'node10-commonjs') {
const lines = source.split('\n')
assert.deepEqual(entry.public.map(item => ({ code: item.code, line: item.line })), [
{ code: 2307, line: lines.findIndex(line => line.includes('from \'artplayer-plugin-auto-thumbnail/legacy\'')) + 1 },
...(compiler === compat ? [{ code: 2344, line: lines.findIndex(line => line.startsWith('type ExactLegacy')) + 1 }] : []),
], 'Preserve the historical Node10 legacy subpath failure')
source = source.replace('from \'artplayer-plugin-auto-thumbnail/legacy\'', 'from \'artplayer-plugin-auto-thumbnail\'')
entry.rootOnly = compile(source)
assert.deepEqual(entry.rootOnly, [])
}
else {
assert.deepEqual(entry.public, [], `${plugin.label} ${compiler.version} ${mode}`)
}
entry.publicInvalid = compile(`${source}\n${publicInvalid.join('\n')}`)
assert.deepEqual(entry.publicInvalid.map(item => item.line), publicInvalid.map((_, index) => source.split('\n').length + 1 + index))
if (plugin.label === 'candidate') {
const runtime = runtimeSource(mode)
entry.runtime = compile(runtime)
entry.runtimeDeclarations = declarations
assert.deepEqual(entry.runtime, [], `${compiler.version} ${mode}`)
assert(declarations.includes(`node_modules/${name}/types/runtime.${next ? mode.endsWith('-cjs') ? 'd.cts' : 'd.mts' : mode === 'bundler-esm' ? 'd.mts' : 'd.ts'}`))
entry.runtimeInvalid = compile(`${runtime}\n${runtimeInvalid.join('\n')}`)
assert.deepEqual(entry.runtimeInvalid.map(item => item.line), runtimeInvalid.map((_, index) => runtime.split('\n').length + 1 + index))
if (mode === 'node10-commonjs' || mode === 'nodenext-cjs') {
entry.noInterop = compile(runtimeSource(`${mode}-no-interop`), false)
assert.deepEqual(entry.noInterop, [])
}
}
report()
}
const probe = path.join(consumer, 'runtime.cjs')
fs.writeFileSync(probe, `const assert = require('node:assert/strict');
(async () => {
const names = ${JSON.stringify([name, `${name}/legacy`, ...(plugin.label === 'candidate' ? [`${name}/runtime`] : [])])};
const commonjs = names.map(name => require(name));
const modules = await Promise.all(names.map(name => import(name)));
for (const factory of [...commonjs, ...modules.map(module => module.default)]) {
assert.equal(typeof factory, 'function'); assert.equal(factory.default, undefined);
const callbacks = new Map();
const art = { option: {url: '/never-loaded.mp4'}, on(name, callback) { callbacks.set(name, callback); }, off(name, callback) { if (callbacks.get(name) === callback) callbacks.delete(name); } };
const pending = factory({})(art);
assert(pending instanceof Promise); assert.equal(pending.name, undefined);
assert.deepEqual(await pending, {name: 'artplayerPluginAutoThumbnail'});
assert(callbacks.has('video:loadedmetadata'));
${plugin.label === 'candidate' ? 'callbacks.get(\'destroy\')(); assert.equal(callbacks.size, 0);' : ''}
}
${plugin.label === 'candidate' ? 'assert.equal(commonjs[2], commonjs[0]); assert.equal(modules[2].default, modules[0].default);' : ''}
console.log('Installed root/legacy/runtime module shapes and Promise registration verified; no native extraction.');
})().catch(error => { console.error(error); process.exitCode = 1; });`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([probe], consumer))
}
finally {
report()
removeConsumer(consumer)
}
}
console.log(`Passed ${matrix.length} installed compiler profiles; ${output}`)
@@ -0,0 +1,63 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Repository compatibility 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 { publicInvalid, publicSource, runtimeInvalid, runtimeSource } from './auto-thumbnail-consumer.mjs'
import { verifyAutoThumbnailContract } from './auto-thumbnail-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('Auto Thumbnail preserves actual npm 1.1.0 root bytes and historical factory extraction', async () => {
const contract = await verifyAutoThumbnailContract()
const current = fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/types/artplayer-plugin-auto-thumbnail.d.ts', 'utf8')
const previous = readMember(contract.archives.get('1.1.0'), 'package/types/artplayer-plugin-auto-thumbnail.d.ts').toString()
assert.equal(current.replaceAll('\r\n', '\n'), previous.replaceAll('\r\n', '\n'))
for (const [compiler, mode] of modes) {
const source = publicSource(mode)
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
const invalid = checkConsumer(compiler, mode, `${source}\n${publicInvalid.join('\n')}`)
assert.deepEqual(invalid.map(item => item.line), publicInvalid.map((_, index) => source.split('\n').length + 1 + index))
}
})
test('Auto Thumbnail runtime types expose only the real asynchronous factory and options', () => {
for (const [compiler, mode] of modes) {
const source = runtimeSource(mode)
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
const invalid = checkConsumer(compiler, mode, `${source}\n${runtimeInvalid.join('\n')}`)
assert.deepEqual(invalid.map(item => item.line), runtimeInvalid.map((_, index) => source.split('\n').length + 1 + index))
}
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import runtime = require('artplayer-plugin-auto-thumbnail/runtime');
import type Artplayer from 'artplayer'; declare const art: Artplayer;
const option: runtime.Option = {}; const result: Promise<runtime.Result> = runtime(option)(art);
const factory: runtime.Factory = runtime; void [result, factory];`), [])
})
test('Auto Thumbnail generated editor globals replace conflicting export assignments and retain old calls', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-auto-thumbnail/types/artplayer-plugin-auto-thumbnail.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginAutoThumbnail')
const file = 'docs/assets/ts/artplayer-plugin-auto-thumbnail.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 = `declare const art: Artplayer;
type Option = Parameters<typeof artplayerPluginAutoThumbnail>[0];
type Result = ReturnType<ReturnType<typeof artplayerPluginAutoThumbnail>>;
const option: Option = { width: 80, number: 12 };
const result: Result = artplayerPluginAutoThumbnail(option)(art);
const replacement: typeof artplayerPluginAutoThumbnail = _option => _art => result;`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
const invalid = checkPluginEditorDeclaration(generated, core, `${consumer}\nartplayerPluginAutoThumbnail();\nconst bad: Promise<Result> = result;`, compiler)
assert.deepEqual(invalid.map(item => item.code), [2554, 2739])
const previous = `${source.replace(/^import.*$/gim, '')}\nexport = artplayerPluginAutoThumbnail;\nexport as namespace artplayerPluginAutoThumbnail;\n`
assert(checkPluginEditorDeclaration(previous, core, '', compiler).some(item => item.code === 2309))
}
})
+24 -1
View File
@@ -1811,7 +1811,8 @@
"PKG-AUTO-THUMB-03",
"ENG-04",
"ENG-06",
"CORE-07"
"CORE-07",
"PKG-AUTO-THUMB-08"
],
"status": "todo",
"risk": "H",
@@ -4838,6 +4839,28 @@
"changes/2026-09-14-PKG-AUTO-THUMB-07-canvas.md",
"baselines/auto-thumbnail-canvas-validation.json"
]
},
{
"id": "PKG-AUTO-THUMB-08",
"phase": "5 包迁移:artplayer-plugin-auto-thumbnail",
"title": "保留旧声明并提供准确异步类型入口",
"scope": [
"artplayer-plugin-auto-thumbnail"
],
"dependsOn": [
"PKG-AUTO-THUMB-02",
"ENG-04",
"ENG-06",
"CORE-07"
],
"status": "done",
"risk": "M",
"deliverable": "保留实际 npm 1.1.0 根声明;新增同实现 /runtime 类型、编辑器声明及隔离安装消费者",
"acceptance": "原 Parameters/ReturnType/替代函数不变;准确 Promise 类型正反例、CJS/ESM 条件入口和声明解析通过;不把像素与设备缺口计为通过",
"evidence": [
"changes/2026-09-14-PKG-AUTO-THUMB-08-types.md",
"baselines/auto-thumbnail-types-validation.json"
]
}
]
}
+1 -1
View File
@@ -53,7 +53,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginJassub', 'artplayerPluginDanmukuMask', 'artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const semanticPlugin = ['artplayerPluginAutoThumbnail', 'artplayerPluginJassub', 'artplayerPluginDanmukuMask', 'artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const localTypes = name === 'artplayerProxyMediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(type), 'media.d.ts'), 'utf8') } : {}
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name, localTypes)
+3
View File
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url'
import ts from 'typescript'
import compat from 'typescript-compat'
import runtimeCompat from 'typescript-runtime-compat'
import { publicSource as autoThumbnailPublic, runtimeSource as autoThumbnailRuntime } from '../refactor/scripts/auto-thumbnail-consumer.mjs'
const root = fileURLToPath(new URL('../', import.meta.url))
const relative = name => path.relative(root, name).replaceAll('\\', '/')
@@ -73,6 +74,8 @@ export function runTypechecks() {
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
}
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) {
for (const source of [autoThumbnailPublic(mode), autoThumbnailRuntime(mode)])
assert.deepEqual(checkConsumer(compiler, mode, source), [], `Auto Thumbnail consumer failed: TS ${compiler.version} ${mode}`)
for (const fixture of ['test/types/asr-public.ts', mode === 'nodenext-esm' ? 'refactor/fixtures/consumers/asr-esm-namespace.ts' : 'refactor/fixtures/consumers/asr-published.ts']) {
let source = fs.readFileSync(path.join(root, fixture), 'utf8')
if (fixture === 'test/types/asr-public.ts' && mode === 'nodenext-esm') {
+10
View File
@@ -2,6 +2,16 @@
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
`yarn test:auto-thumbnail-types` checks the preserved npm 1.1.0 declaration,
accurate `/runtime` Promise types, exact rejected consumer lines, and generated
editor globals. It also runs in `test:auto-thumbnail` and `test:baseline`.
`yarn test:auto-thumbnail-types-package` packs the candidate and installs it and
the actual npm 1.1.0 archive outside the workspace with a packed core. It verifies
frozen reinstalls, byte identity, conditional declaration resolution, old/current
compiler consumers, no-interop CommonJS, and installed factory registration.
This does not decode video or close the older 1.0.x export-shape, native pixel,
or device acceptance gaps. Candidate entry types use the same runtime files.
`yarn test:danmuku-mask` runs candidate run cancellation/resource ownership plus
frozen historical defects and public contracts. Candidate tests also run in
`test:unit`; their controlled SDK, RAF and canvas hosts do not prove native model
+23
View File
@@ -0,0 +1,23 @@
import type Artplayer from 'artplayer'
import thumbnail from 'artplayer-plugin-auto-thumbnail'
import legacy from 'artplayer-plugin-auto-thumbnail/legacy'
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type Assert<T extends true> = T
interface Option { url?: string, width?: number, number?: number, scale?: number }
interface Result { name: 'artplayerPluginAutoThumbnail' }
type HistoricalFactory = (option: Option) => (art: Artplayer) => Result
type ExactFactory = Assert<Equal<typeof thumbnail, HistoricalFactory>>
type ExactOption = Assert<Equal<Parameters<typeof thumbnail>, [option: Option]>>
type ExactResult = Assert<Equal<ReturnType<ReturnType<typeof thumbnail>>, Result>>
type ExactLegacy = Assert<Equal<typeof legacy, typeof thumbnail>>
declare const art: Artplayer
const result: Result = thumbnail({ url: '/video.mp4', width: 80, number: 12, scale: 0.5 })(art)
const replacement: typeof thumbnail = (_option: Option) => (_art: Artplayer) => result
const assignBack: HistoricalFactory = thumbnail
const legacyReplacement: typeof legacy = replacement
const moduleReplacement: typeof import('artplayer-plugin-auto-thumbnail') = { default: replacement }
const legacyName: Result['name'] = legacy({})(art).name
void [assignBack, legacyReplacement, moduleReplacement, legacyName]
export type { ExactFactory, ExactLegacy, ExactOption, ExactResult }
+18
View File
@@ -0,0 +1,18 @@
import type Artplayer from 'artplayer'
import type { Factory, Option, Result } from 'artplayer-plugin-auto-thumbnail/runtime'
import runtime from 'artplayer-plugin-auto-thumbnail/runtime'
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type Assert<T extends true> = T
type ExactFactory = Assert<Equal<typeof runtime, Factory>>
type ExactOption = Assert<Equal<Parameters<typeof runtime>, [option: Option]>>
type ExactResult = Assert<Equal<ReturnType<ReturnType<typeof runtime>>, Promise<Result>>>
declare const art: Artplayer
const option: Option = { url: '/video.mp4', width: 80, number: 12, scale: 0.5, height: 90 }
const pending: Promise<Result> = runtime(option)(art)
const name: Promise<'artplayerPluginAutoThumbnail'> = pending.then(value => value.name)
const plugins: NonNullable<Artplayer['option']['plugins']> = [runtime(option)]
const replacement: Factory = _option => async _art => ({ name: 'artplayerPluginAutoThumbnail' })
void [name, plugins, replacement]
export type { ExactFactory, ExactOption, ExactResult }