fix(vtt-thumbnail): [PKG-VTT-THUMB-04] preserve historical type shapes

This commit is contained in:
Harvey Zhao committed 2026-09-13 16:57:38 +08:00
1 parent c4c7491e16
commit 58d2c95d8e
16 files changed
+235 -57

No files matched your search

@@ -58,21 +58,29 @@ legacy parameter/result/factory extraction, replacement factories and accurate a
through `/runtime`, including negative cases. The source factory explicitly returns Promise;
its final assertion only describes the self-reference that Object.assign creates.
Root and legacy use paired `.d.mts`/`.d.cts` default declarations. `/runtime` uses an ESM
default and a CommonJS export assignment, supporting direct and `.default` require calls.
Classic resolution uses `.d.ts` and typesVersions, including TypeScript 4.3. The generated
Root and legacy select the original `.d.ts` shape in every export condition, preserving
latest npm 1.1.0's NodeNext ESM namespace and plain factory replacements. Do not add a
required self alias or silently make the root ESM namespace callable. Previously added
root `.d.mts`/`.d.cts` files remain packaged but are not selected by these entrypoints.
`/runtime` uses an ESM default and a CommonJS export assignment, supporting direct and
`.default` calls. Its Node10 `.d.ts` also uses export=, including raw require without
interop on TS 4.3/5.9. Each runtime declaration exports the same four named types.
Classic resolution uses typesVersions. The generated
editor global retains the legacy callable type; its RuntimeFactory namespace member is only
a type, not an additional runtime global. Do not hand-edit generated editor declarations.
The installed-consumer command packs both core and plugin, installs outside the workspace,
verifies member hashes, and repeats an offline frozen install. It tests five actual old npm
declarations in five modes on TS 4.3/5.9 and seven candidate modes: 32 default-import cells,
including one expected 1.1.0 NodeNext ESM declaration failure. Candidate ESM passes that cell.
including one expected 1.1.0 raw NodeNext ESM declaration failure. Candidate root keeps
the old namespace shape and verifies its legal calls and whole-module replacements;
accurate direct ESM calls use /runtime. Negative cases are checked at their exact lines.
Another 36 direct/default CommonJS extraction/replacement forms reproduce the opposing 1.0.x
export-assignment and 1.1.0 default declaration shapes. Candidate preserves the latter.
Earlier type-only direct module extraction remains an explicit decision in task 04; a broken
historical JS direct call does not waive that type-only consumer. See the module-forms change
record for the concrete required/optional default-property alternatives and migration proposal.
Earlier type-only direct module extraction now follows the user's approved rule in
../../refactor/type-compatibility-policy.md: keep latest 1.1.0 types and document the
1.0.x export= migration without changing valid JavaScript behavior. Historical failures
remain in the installed reports; runtime failures never stand in for type evidence.
## Compatibility and intentional fixes
@@ -102,9 +110,9 @@ plugin. Empty or comment-only input remains a valid empty preview.
The exported callable has a writable self `.default` alias, restoring the usable 1.0.x
CommonJS default access while retaining direct calls. It does not add a name property to the
registration Promise. Earlier `thumbnails` control names and remaining historical declaration
forms are separately frozen and remain compatibility work for tasks 04-06. The full package
refactor is not yet finished.
registration Promise. Earlier `thumbnails` control names are separately frozen and remain
combination/distribution work for tasks 05-06. The approved declaration migration does not
complete the remaining package or device acceptance.
## Verification and future changes
@@ -27,6 +27,33 @@ returned a Promise at runtime. Reading `result.name` requires awaiting registrat
to `/runtime` opts into that accurate type without changing runtime behavior. The option
object remains required, and its `vtt` and `style` fields remain optional.
The root factory keeps the latest published 1.1.0 type shape, with no required `.default`
property. Plain replacement functions remain assignable in both directions. `/runtime`
exports `Option`, `Result`, `Factory` and `RuntimeFactory` in all resolution modes; its
factory describes the actual writable `.default` self alias and asynchronous registration.
### Earlier declaration migration
The 1.0.x declarations used `export =`, while 1.1.0 uses `export default`. Following the
approved compatibility policy, the root retains 1.1.0. Older TypeScript code that directly
calls or extracts the type of `import vtt = require('artplayer-plugin-vtt-thumbnail')`
should use the accurate runtime entry:
```ts
import vtt = require('artplayer-plugin-vtt-thumbnail/runtime')
const register = vtt({ vtt: '/thumbnails/index.vtt' })
const result = await register(art)
console.log(result.name)
```
This export-assignment form supports Node10 resolution without `esModuleInterop`.
Default `/runtime` imports support interoperability and native NodeNext ESM. Root
NodeNext ESM intentionally preserves the old declaration namespace shape (the factory
is `root.default` after a default import); that historical declaration does not accurately
describe the actual ESM export. Use `/runtime` for accurate ESM calls. Valid historical
JavaScript default access, the direct factory, and legacy distribution entrypoints remain.
See [ARCHITECTURE.md](ARCHITECTURE.md) for module ownership, testing commands and remaining
compatibility validation.
@@ -19,24 +19,14 @@
],
"exports": {
".": {
"import": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.mts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.mjs"
},
"require": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.cts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.js"
}
"types": "./types/artplayer-plugin-vtt-thumbnail.d.ts",
"import": "./dist/artplayer-plugin-vtt-thumbnail.mjs",
"require": "./dist/artplayer-plugin-vtt-thumbnail.js"
},
"./legacy": {
"import": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.mts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-vtt-thumbnail.d.cts",
"default": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
}
"types": "./types/artplayer-plugin-vtt-thumbnail.d.ts",
"import": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js",
"require": "./dist/artplayer-plugin-vtt-thumbnail.legacy.js"
},
"./runtime": {
"import": {
@@ -1,4 +1,11 @@
import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginVttThumbnail {
type Option = import('./artplayer-plugin-vtt-thumbnail.js').Option
type Result = import('./artplayer-plugin-vtt-thumbnail.js').Result
type Factory = import('./artplayer-plugin-vtt-thumbnail.js').Factory
type RuntimeFactory = import('./artplayer-plugin-vtt-thumbnail.js').RuntimeFactory
}
export = artplayerPluginVttThumbnail
@@ -2,4 +2,4 @@ import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
export default artplayerPluginVttThumbnail
export type { Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
export type { Factory, Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
+8 -2
View File
@@ -1,5 +1,11 @@
import type { RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
declare const artplayerPluginVttThumbnail: RuntimeFactory
export default artplayerPluginVttThumbnail
export type { Option, Result, RuntimeFactory } from './artplayer-plugin-vtt-thumbnail.js'
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginVttThumbnail {
type Option = import('./artplayer-plugin-vtt-thumbnail.js').Option
type Result = import('./artplayer-plugin-vtt-thumbnail.js').Result
type Factory = import('./artplayer-plugin-vtt-thumbnail.js').Factory
type RuntimeFactory = import('./artplayer-plugin-vtt-thumbnail.js').RuntimeFactory
}
export = artplayerPluginVttThumbnail
@@ -0,0 +1,31 @@
{
"schemaVersion": 1,
"task": "PKG-VTT-THUMB-04",
"status": "done",
"capturedAt": "2026-09-13T08:57:34.487Z",
"policy": "type-compatibility-policy.md",
"baseline": "Latest actual npm 1.1.0 root type shape; earlier 1.0.x conflicts migrated under ADR-025.",
"checks": {
"isolatedReport": "refactor/.cache/vtt-thumbnail-package-types-JQwPa2/report.json",
"reportSha256": "2080b138fa9c9b0e654941b2373862ad2d23ce7caa7b7186cfb2cd6efbe32ead",
"candidateArchiveSha256": "8884ad821cee9f1c6792db68bca9cb2f31b092cc8385e0b8fd0299ec726628ad",
"configurations": 32,
"candidatePositiveConfigurations": 7,
"invalidStatementsPerCandidateConfiguration": 12,
"log": "refactor/.cache/vtt04-approved-types.log",
"logSha256": "3f9283f2387db3db329384e6d59a530b30c6b4aa13c9111714194a7d2971f72a",
"focusedTests": 5
},
"preserves": [
"Whole factory replacement in both directions, required arguments and historical sync result.",
"Latest published valid NodeNext namespace and plain module replacement, old direct-call diagnostics retained.",
"Runtime accurate async result, named types and CommonJS/default identity including old no-interop resolution.",
"Isolated actual tarball installation, exact installed bytes, offline frozen reinstall."
],
"sourceAndDistChanged": false,
"browserRerun": false,
"limitations": [
"Historical thumbnails/vtt-thumbnail control names and complete core/distribution validation remain 05/06.",
"Existing runtime/source evidence is linked in the completion note; these checks do not establish devices or final npm release readiness."
]
}
@@ -0,0 +1,26 @@
# PKG-VTT-THUMB-04 类型兼容实施完成
用户已批准ADR-025,之前检查点中的待确认状态由本记录取代。根/legacy保留真实npm
1.1.0的必填Option、同步name结果和完整可替换工厂类型;实际异步行为继续通过
/runtime表达。修正根入口条件映射,保留旧NodeNext ESM合法namespace及整个模块
的双向替换关系,不能只让默认调用通过就声称类型兼容。
/runtime的Node10声明改为export=并补全Option/Result/Factory/RuntimeFactory命名
类型,与NodeNext CommonJS、ESM入口一致。精确异步结果与可写self.default均验证;
较早1.0.x的export=冲突、1.1的模块类型及建议迁移已在README和架构文档写明。
最终真实安装报告包含五个历史版本与候选的32配置,候选7配置全部正例通过,每个
配置按语句行拒绝12个反例。保留旧1.1 NodeNext直接调用的7个错误及36组历史/候选
CommonJS直接/default对照;1.0.0发布包原始SyntaxError单独记录。最新合法namespace
和候选一致,实际JS调用身份单独核验。安装位于工作区外,归档成员逐字核对、离线
安装及frozen重装通过。5项专项测试含独立编辑器、历史声明和替代签名验证通过。
生产TS迁移、模块拆分及先前运行时验证见
[运行时检查点](2026-09-13-PKG-VTT-THUMB-04-runtime-types.md)与
[公共声明检查点](2026-09-13-PKG-VTT-THUMB-04-public-types.md)。此次源码/dist未变,
根公共声明内容未变,生成编辑器一致性已验证,无需重复构建或浏览器播放。
本任务04完成,VTT-THUMB-TYPE-01关闭;涉及历史控件名称、核心组合和完整分发的
VTT-THUMB-EXPORT-01继续交给05/06。没有把类型冲突批准扩大为运行时行为变更,
没有新增设备证据,也没有推送或npm发布。详见
[最终证据](../baselines/vtt-thumbnail-approved-types.json)。
+3 -3
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。
状态:todo 78 / doing 17 / blocked 0 / done 133 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 78 / doing 16 / blocked 0 / done 134 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -186,7 +186,7 @@
| PKG-VTT-THUMB-01 | artplayer-plugin-vtt-thumbnail<br>核对包契约与历史用法 | BASE-05 | vtt/style、异步返回、预览控件名与样式契约 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done |
| PKG-VTT-THUMB-02 | artplayer-plugin-vtt-thumbnail<br>建立特有行为与错误测试 | PKG-VTT-THUMB-01, ENG-03, ENG-05 | VTT 解析、xywh、相对 URL、时间边界、请求失败和定位 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done |
| PKG-VTT-THUMB-03 | artplayer-plugin-vtt-thumbnail<br>整理内部职责与资源 | PKG-VTT-THUMB-02, CORE-09, CORE-19 | 纯解析/区间查找与 DOM、请求/定时器生命周期分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done |
| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail<br>迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | doing |
| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail<br>迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | done |
| PKG-VTT-THUMB-05 | artplayer-plugin-vtt-thumbnail<br>验证新旧核心和组合 | PKG-VTT-THUMB-04, CORE-22 | 鼠标和移动进度、全屏、切源、chapter 组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
| PKG-VTT-THUMB-06 | artplayer-plugin-vtt-thumbnail<br>验证分发并同步文档 | PKG-VTT-THUMB-05, ENG-07 | vtt.thumbnail.js、VTT/图片资源解析和产物验证 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
@@ -512,7 +512,7 @@
- PKG-VTT-THUMB-01: [记录](baselines/vtt-thumbnail-release.json) [记录](baselines/vtt-thumbnail-contract.md) [记录](changes/2026-09-13-PKG-VTT-THUMB-01-contract.md)
- PKG-VTT-THUMB-02: [记录](baselines/vtt-thumbnail-failures.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-02-failures.md)
- PKG-VTT-THUMB-03: [记录](changes/2026-09-13-PKG-VTT-THUMB-03-resources.md) [记录](baselines/vtt-thumbnail-resources.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-03-parser.md) [记录](baselines/vtt-thumbnail-parser.json)
- PKG-VTT-THUMB-04: [记录](changes/2026-09-13-PKG-VTT-THUMB-04-runtime-types.md) [记录](baselines/vtt-thumbnail-runtime-types.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-04-public-types.md) [记录](baselines/vtt-thumbnail-public-types.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-04-module-forms.md) [记录](baselines/vtt-thumbnail-module-forms.json)
- PKG-VTT-THUMB-04: [记录](changes/2026-09-13-PKG-VTT-THUMB-04-runtime-types.md) [记录](baselines/vtt-thumbnail-runtime-types.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-04-public-types.md) [记录](baselines/vtt-thumbnail-public-types.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-04-module-forms.md) [记录](baselines/vtt-thumbnail-module-forms.json) [记录](type-compatibility-policy.md) [记录](baselines/vtt-thumbnail-approved-types.json) [记录](changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md)
- PKG-HLS-01: [记录](baselines/hls-control-contract.md) [记录](baselines/hls-control-release.json) [记录](changes/2026-09-11-PKG-HLS-01-contract.md)
- PKG-HLS-02: [记录](hls-validation.md) [记录](baselines/hls-sdk.json) [记录](baselines/hls-validation.json) [记录](changes/2026-09-11-PKG-HLS-02-tests.md)
- PKG-HLS-03: [记录](changes/2026-09-12-PKG-HLS-03-modules.md) [记录](baselines/hls-modules-validation.json) [记录](hls-validation.md)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## PKG-VTT-THUMB-04 VTT Thumbnail 类型兼容实施完成
已落实用户批准的最新npm 1.1.0根类型及较早1.0.x迁移规则;/runtime准确
描述异步结果和模块形式。32个真实安装配置、7个候选配置正例通过,每配置拒绝
12个指定反例,5项专项测试含编辑器通过。见[完成记录](changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md)及[证据](baselines/vtt-thumbnail-approved-types.json)。
VTT-THUMB-TYPE-01关闭,VTT-THUMB-EXPORT-01仍保留组合/完整分发验证。源码与dist未变,
不新增浏览器设备通过声明。228项:134 done、16 doing、78 todo;不推送或发布。
## PKG-FACTORY-01 历史工厂类型修复完成
Canvas/Ambilight落实已批准规则:保留真实npm1.1.0根工厂与NodeNext namespace,
+1 -1
View File
@@ -192,7 +192,7 @@
| VTT-THUMB-DIST-01 | open / 已复现 | Historical 1.0.0 main/legacy contain invalid regular expressions and cannot load | PKG-VTT-THUMB-06 |
| VTT-THUMB-PARSE-01 | resolved / 已复现 | Historical compiled arrows differ from shipped source; parsing floors bounds and assumes alternating lines | PKG-VTT-THUMB-02, PKG-VTT-THUMB-03 |
| VTT-THUMB-LIFE-01 | open / 已复现 | Fetch completion, setBar listeners and mobile timers have no destroy ownership | PKG-VTT-THUMB-02, PKG-VTT-THUMB-03, PKG-VTT-THUMB-05 |
| VTT-THUMB-TYPE-01 | open / 已复现 | Published declarations falsely describe async registration as a synchronous result | PKG-VTT-THUMB-04 |
| VTT-THUMB-TYPE-01 | resolved / 已复现 | Published declarations falsely describe async registration as a synchronous result | PKG-VTT-THUMB-04 |
| VTT-THUMB-EXPORT-01 | open / 已复现 | Older CommonJS default objects and thumbnails control names differ from latest direct export and vtt-thumbnail name | PKG-VTT-THUMB-04, PKG-VTT-THUMB-05, PKG-VTT-THUMB-06 |
| MULTI-SUB-TYPE-01 | open / 已复现 | Declarations omit async registration, tracks/reset and make the subtitles array required | PKG-MULTI-SUB-04 |
| MULTI-SUB-MERGE-01 | open / 已复现 | 1.0.0 merges cues by index while later versions concatenate independently timed track cues | PKG-MULTI-SUB-02, PKG-MULTI-SUB-03, PKG-MULTI-SUB-05 |
+13 -5
View File
@@ -4222,7 +4222,7 @@
"id": "VTT-THUMB-TYPE-01",
"title": "Published declarations falsely describe async registration as a synchronous result",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"PKG-VTT-THUMB-04"
],
@@ -4238,8 +4238,14 @@
"refactor/changes/2026-09-13-PKG-VTT-THUMB-04-module-forms.md",
"refactor/baselines/vtt-thumbnail-module-forms.json"
],
"compatibleResolution": "Preserve valid consumers and actual async runtime, with explicit decisions for irreconcilable inference changes.",
"closureCriteria": "Isolated old/new compiler and installed consumers verify options, styles, callback and result forms."
"compatibleResolution": "Approved latest npm 1.1.0 root factory/namespace stays exact; additive runtime supplies accurate asynchronous and module forms. Earlier 1.0.x type conflicts have explicit migration under ADR-025.",
"closureCriteria": "Isolated old/new compiler and installed consumers verify options, styles, callback and result forms.",
"resolutionEvidence": [
"refactor/baselines/vtt-thumbnail-approved-types.json",
"refactor/changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md",
"refactor/type-compatibility-policy.md"
],
"resolutionRationale": "32 isolated installation configurations, seven zero-diagnostic candidate modes with 12 exact invalid statements each, 5 focused tests. Legal latest namespace and complete factory replacements retained, historical incompatible forms recorded. No production JS changed."
},
{
"id": "VTT-THUMB-EXPORT-01",
@@ -4259,9 +4265,11 @@
"refactor/baselines/vtt-thumbnail-public-types.json",
"refactor/scripts/vtt-thumbnail-package-types.mjs",
"refactor/changes/2026-09-13-PKG-VTT-THUMB-04-module-forms.md",
"refactor/baselines/vtt-thumbnail-module-forms.json"
"refactor/baselines/vtt-thumbnail-module-forms.json",
"refactor/baselines/vtt-thumbnail-approved-types.json",
"refactor/changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md"
],
"compatibleResolution": "Validate historical import forms and control integrations before selecting compatibility adapters.",
"compatibleResolution": "Approved latest npm 1.1.0 root factory/namespace stays exact; additive runtime supplies accurate asynchronous and module forms. Earlier 1.0.x type conflicts have explicit migration under ADR-025. Historical thumbnails/vtt-thumbnail control names and complete core/distribution validation remain 05/06.",
"closureCriteria": "Actual package consumers and old/new cores prove supported forms; unresolved differences stay explicit."
},
{
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
@@ -9,7 +10,46 @@ import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '..
import { hash, readMember } from './releases.mjs'
import { verifyVttThumbnailContract } from './vtt-thumbnail-contract.mjs'
export function vttThumbnailConsumerSource(source, mode) {
if (mode === 'nodenext-esm') {
source = source.replace('import legacy from \'artplayer-plugin-vtt-thumbnail\'', 'import legacyModule from \'artplayer-plugin-vtt-thumbnail\'\nconst legacy = legacyModule.default')
.replace('import legacyEntry from \'artplayer-plugin-vtt-thumbnail/legacy\'', 'import legacyEntryModule from \'artplayer-plugin-vtt-thumbnail/legacy\'\nconst legacyEntry = legacyEntryModule.default')
.replace('import vtt from \'artplayer-plugin-vtt-thumbnail\'', 'import vttModule from \'artplayer-plugin-vtt-thumbnail\'\nconst vtt = vttModule.default')
.replace('namespace.default', 'namespace.default.default')
}
if (mode.endsWith('-no-interop'))
source = source.replace('import runtime from \'artplayer-plugin-vtt-thumbnail/runtime\'', 'import runtime = require(\'artplayer-plugin-vtt-thumbnail/runtime\')')
return source
}
export const vttThumbnailNamespaceConsumer = `import type Artplayer from 'artplayer';
import vttModule from 'artplayer-plugin-vtt-thumbnail';
import * as namespace from 'artplayer-plugin-vtt-thumbnail';
import legacyModule from 'artplayer-plugin-vtt-thumbnail/legacy';
type Factory = (option: { vtt?: string, style?: Partial<CSSStyleDeclaration> }) => (art: Artplayer) => {name: 'artplayerPluginVttThumbnail'};
const replacement: Factory = () => () => ({name: 'artplayerPluginVttThumbnail'});
vttModule.default({}); namespace.default.default({}); legacyModule.default({});
const fromOld: typeof vttModule = {default: replacement};
const toOld: {default: Factory} = vttModule;
const namespaceReplacement: typeof namespace.default = {default: replacement};
const oldNamespace: {default: Factory} = namespace.default;
const option: Parameters<typeof vttModule.default>[0] = {};
const result: ReturnType<ReturnType<typeof vttModule.default>> = {name: 'artplayerPluginVttThumbnail'};
void [fromOld, toOld, namespaceReplacement, oldNamespace, option, result];`
export function checkInvalidVttStatements(source, diagnostics) {
const expected = []
let line = 1
for (const text of source.split('\n')) {
if (text.startsWith('// @ts-expect-error'))
expected.push(line)
else line++
}
assert.deepEqual(diagnostics.map(item => item.line).sort((a, b) => a - b), expected, 'Each invalid VTT statement must fail at its own line')
}
async function main() {
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use the repository Node version')
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:vtt-thumbnail-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
@@ -66,14 +106,16 @@ Promise.all(['artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail/r
for (const [compiler, mode] of (plugin.label === 'candidate' ? candidateModes : historicalModes)) {
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/vtt-thumbnail-public.ts' : 'refactor/fixtures/consumers/vtt-thumbnail-published.ts'), 'utf8')
const fixture = fs.readFileSync(path.join(workspace, plugin.label === 'candidate' ? 'test/types/vtt-thumbnail-public.ts' : 'refactor/fixtures/consumers/vtt-thumbnail-published.ts'), 'utf8')
const source = plugin.label === 'candidate' ? vttThumbnailConsumerSource(fixture, mode) : fixture
const filename = path.join(consumer, `consumer.${extension}`)
const historical = path.join(consumer, `historical.${extension}`)
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/vtt-thumbnail-published.ts'), historical)
fs.writeFileSync(historical, vttThumbnailConsumerSource(fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/vtt-thumbnail-published.ts'), 'utf8'), mode))
const files = plugin.label === 'candidate' ? [filename, historical] : [filename]
if (mode === 'nodenext-cjs' && plugin.label === 'candidate') {
const commonjs = path.join(consumer, 'commonjs.cts')
fs.writeFileSync(commonjs, `import vtt = require('artplayer-plugin-vtt-thumbnail'); import legacy = require('artplayer-plugin-vtt-thumbnail/legacy'); import runtime = require('artplayer-plugin-vtt-thumbnail/runtime'); vtt.default({}); legacy.default({}); runtime({}); runtime.default({});`)
if (plugin.label === 'candidate' && mode !== 'nodenext-esm' && mode !== 'bundler-esm') {
const commonjs = path.join(consumer, `commonjs.${extension}`)
fs.writeFileSync(commonjs, `import vtt = require('artplayer-plugin-vtt-thumbnail'); import legacy = require('artplayer-plugin-vtt-thumbnail/legacy'); import runtime = require('artplayer-plugin-vtt-thumbnail/runtime'); vtt.default({}); legacy.default({}); runtime({}); runtime.default({});
const option: runtime.Option = {}; const factory: runtime.RuntimeFactory = runtime; const old: runtime.Factory = () => () => ({name: 'artplayerPluginVttThumbnail'}); const result: runtime.Result = {name: 'artplayerPluginVttThumbnail'}; void [option, factory, old, result];`)
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 }
@@ -84,7 +126,7 @@ Promise.all(['artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail/r
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') }))
return compiler.getPreEmitDiagnostics(program).map(item => ({ file: item.file && path.relative(consumer, item.file.fileName), line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
}
const diagnostics = compile(source)
const historicalFailure = plugin.version === '1.1.0' && mode === 'nodenext-esm'
@@ -93,7 +135,9 @@ Promise.all(['artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail/r
assert.deepEqual(diagnostics.map(item => item.code), expected, `${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, 10, 'Installed declarations must reject all invalid uses')
checkInvalidVttStatements(source, invalid)
const namespace = mode === 'nodenext-esm' && (plugin.label === 'candidate' || plugin.version === '1.1.0') ? compile(vttThumbnailNamespaceConsumer) : []
assert.deepEqual(namespace, [], 'Latest published and candidate NodeNext namespace replacements must remain valid')
const commonjs = {}
if (mode === 'node10-commonjs' || mode === 'nodenext-cjs') {
const fixture = fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/vtt-thumbnail-commonjs.ts'), 'utf8')
@@ -108,18 +152,20 @@ Promise.all(['artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail/r
commonjs[form] = { diagnostics, typeChecks: diagnostics.length === 0 }
}
}
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, commonjs })
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, commonjs, namespace })
}
}
finally {
removeConsumer(consumer)
}
}
writeJson(path.join(output, 'report.json'), { suite: 'vtt-thumbnail-isolated-package-types', introducedBy: 'PKG-VTT-THUMB-04', scope: 'Five actual published packages in five compiler modes and packed candidate in seven modes, installed outside the workspace with packed core. Offline/frozen reinstall and byte identity are verified. One historical NodeNext ESM declaration failure is expected, not accepted as candidate success. Direct/default import=require extraction and replacement forms reproduce opposing 1.0.x and 1.1.0 declarations; the candidate preserves 1.1.0 forms. The 1.0.x direct module type difference remains unresolved, although old actual CommonJS direct calls already fail. Device and full distribution acceptance remain separate.', packages, published: candidates.slice(0, 5).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)), missingEntrypoints: pkg.missingEntrypoints })), matrix })
writeJson(path.join(output, 'report.json'), { suite: 'vtt-thumbnail-isolated-package-types', introducedBy: 'PKG-VTT-THUMB-04', node: process.version, scope: 'Five actual published packages in five compiler modes and packed candidate in seven modes, installed outside the workspace with packed core. Offline/frozen reinstall and byte identity are verified. Root preserves latest 1.1.0 factory and NodeNext ESM namespace, including plain module replacements. Historical raw direct-import diagnostics remain recorded. Direct/default import=require forms retain opposing 1.0.x and 1.1.0 evidence; the candidate follows 1.1.0 under approved type-compatibility-policy.md and documents earlier migration. Runtime describes Promise results, writable self identity and all named types with NodeNext and Node10 including no-interop consumers. Device and full distribution acceptance remain separate.', packages, published: candidates.slice(0, 5).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)), missingEntrypoints: pkg.missingEntrypoints })), matrix })
console.log(`VTT thumbnail installed matrix verified: ${matrix.length} cases, including ${matrix.filter(item => item.historicalFailure).length} expected historical declaration failure; ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
}
@@ -10,6 +10,7 @@ import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '.
import { checkConsumer } from '../../scripts/typecheck.mjs'
import { readMember } from './releases.mjs'
import { verifyVttThumbnailContract } from './vtt-thumbnail-contract.mjs'
import { checkInvalidVttStatements, vttThumbnailConsumerSource, vttThumbnailNamespaceConsumer } from './vtt-thumbnail-package-types.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
@@ -39,9 +40,11 @@ artplayerPluginVttThumbnail({ vtt: 12 });`
test('VTT public legacy extraction remains exact while runtime entry gives cast-free Promise types', () => {
const source = fs.readFileSync('test/types/vtt-thumbnail-public.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, 10)
const consumer = vttThumbnailConsumerSource(source, mode)
assert.deepEqual(checkConsumer(compiler, mode, consumer), [], `${compiler.version} ${mode}`)
checkInvalidVttStatements(consumer, checkConsumer(compiler, mode, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')))
}
assert.deepEqual(checkConsumer(ts, 'nodenext-esm', vttThumbnailNamespaceConsumer), [])
})
test('VTT runtime CommonJS declaration supports direct and default calls without an interop cast', () => {
+5 -2
View File
@@ -1927,7 +1927,7 @@
"ENG-06",
"CORE-07"
],
"status": "doing",
"status": "done",
"risk": "M",
"deliverable": "cue/rect/result 和参数可选性保持兼容",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
@@ -1937,7 +1937,10 @@
"changes/2026-09-13-PKG-VTT-THUMB-04-public-types.md",
"baselines/vtt-thumbnail-public-types.json",
"changes/2026-09-13-PKG-VTT-THUMB-04-module-forms.md",
"baselines/vtt-thumbnail-module-forms.json"
"baselines/vtt-thumbnail-module-forms.json",
"type-compatibility-policy.md",
"baselines/vtt-thumbnail-approved-types.json",
"changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md"
]
},
{
+16 -2
View File
@@ -1,6 +1,8 @@
import type { Factory, Option, Result, RuntimeFactory } from 'artplayer-plugin-vtt-thumbnail'
import type { Factory as EntryFactory, Option as EntryOption, Result as EntryResult, RuntimeFactory as EntryRuntimeFactory } from 'artplayer-plugin-vtt-thumbnail/runtime'
import Artplayer from 'artplayer'
import legacy from 'artplayer-plugin-vtt-thumbnail'
import * as namespace from 'artplayer-plugin-vtt-thumbnail'
import legacyEntry from 'artplayer-plugin-vtt-thumbnail/legacy'
import runtime from 'artplayer-plugin-vtt-thumbnail/runtime'
@@ -10,11 +12,19 @@ type ExactOptions = Assert<Equal<Parameters<typeof legacy>, [option: Option]>>
type ExactFactory = Assert<Equal<typeof legacy, Factory>>
type ExactResult = Assert<Equal<ReturnType<ReturnType<typeof legacy>>, Result>>
type ActualResult = Assert<Equal<ReturnType<ReturnType<typeof runtime>>, Promise<Result>>>
type HistoricalFactory = (option: { vtt?: string, style?: Partial<CSSStyleDeclaration> }) => (art: Artplayer) => { name: 'artplayerPluginVttThumbnail' }
type ExactHistoricalFactory = Assert<Equal<typeof legacy, HistoricalFactory>>
type ExactRuntimeFactory = Assert<Equal<typeof runtime, RuntimeFactory>>
type RuntimeEntryTypes = Assert<Equal<[EntryFactory, EntryOption, EntryResult, EntryRuntimeFactory], [Factory, Option, Result, RuntimeFactory]>>
const option: Option = { vtt: '/cues.vtt', style: { opacity: '0.8' } }
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [legacy(option), runtime(option)] })
const old: Result = legacy(option)(art)
const oldReplacement: typeof legacy = (_option: Option) => (_art: Artplayer) => ({ name: 'artplayerPluginVttThumbnail' })
const toOld: HistoricalFactory = legacy
const fromOld: typeof legacy = (_option: Parameters<HistoricalFactory>[0]) => (_art: Artplayer) => ({ name: 'artplayerPluginVttThumbnail' })
const namespaceFactory: HistoricalFactory = namespace.default
const legacyReplacement: typeof legacyEntry = fromOld
legacyEntry(option)
const factory: RuntimeFactory = runtime
const pending: Promise<Result> = factory(option)(art)
@@ -41,5 +51,9 @@ runtime({ unknown: true })
runtime({})()
// @ts-expect-error The runtime entry cannot be replaced with a synchronous registrar.
const wrong: typeof runtime = oldReplacement
void [alias, awaitedName, absent, changed, wrong]
export type { ActualResult, ExactFactory, ExactOptions, ExactResult }
// @ts-expect-error The historical root factory has no self default property.
legacy.default({})
// @ts-expect-error The legacy factory has the same pure function shape.
legacyEntry.default({})
void [toOld, fromOld, namespaceFactory, legacyReplacement, alias, awaitedName, absent, changed, wrong]
export type { ActualResult, ExactFactory, ExactHistoricalFactory, ExactOptions, ExactResult, ExactRuntimeFactory, RuntimeEntryTypes }