diff --git a/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md b/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md index 35e45e3f8..0d8c7e99a 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md +++ b/packages/artplayer-plugin-vtt-thumbnail/ARCHITECTURE.md @@ -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 diff --git a/packages/artplayer-plugin-vtt-thumbnail/README.md b/packages/artplayer-plugin-vtt-thumbnail/README.md index e49295874..405b4e6ad 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/README.md +++ b/packages/artplayer-plugin-vtt-thumbnail/README.md @@ -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. diff --git a/packages/artplayer-plugin-vtt-thumbnail/package.json b/packages/artplayer-plugin-vtt-thumbnail/package.json index d6be4fc6b..4fa5f6d7b 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/package.json +++ b/packages/artplayer-plugin-vtt-thumbnail/package.json @@ -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": { diff --git a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.cts b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.cts index f05c766b0..f6457c656 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.cts +++ b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.cts @@ -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 diff --git a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.mts b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.mts index 234aa4a15..80cec654e 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.mts +++ b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.mts @@ -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' diff --git a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.ts b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.ts index 234aa4a15..f6457c656 100644 --- a/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.ts +++ b/packages/artplayer-plugin-vtt-thumbnail/types/runtime.d.ts @@ -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 diff --git a/refactor/baselines/vtt-thumbnail-approved-types.json b/refactor/baselines/vtt-thumbnail-approved-types.json new file mode 100644 index 000000000..3b6d5ab9a --- /dev/null +++ b/refactor/baselines/vtt-thumbnail-approved-types.json @@ -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." + ] +} diff --git a/refactor/changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md b/refactor/changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md new file mode 100644 index 000000000..1e645e7e2 --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-VTT-THUMB-04-approved-types.md @@ -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)。 diff --git a/refactor/plan.md b/refactor/plan.md index fae45f913..48df3ae98 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -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
核对包契约与历史用法 | BASE-05 | vtt/style、异步返回、预览控件名与样式契约 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | done | | PKG-VTT-THUMB-02 | artplayer-plugin-vtt-thumbnail
建立特有行为与错误测试 | PKG-VTT-THUMB-01, ENG-03, ENG-05 | VTT 解析、xywh、相对 URL、时间边界、请求失败和定位 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | done | | PKG-VTT-THUMB-03 | artplayer-plugin-vtt-thumbnail
整理内部职责与资源 | PKG-VTT-THUMB-02, CORE-09, CORE-19 | 纯解析/区间查找与 DOM、请求/定时器生命周期分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done | -| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail
迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | doing | +| PKG-VTT-THUMB-04 | artplayer-plugin-vtt-thumbnail
迁移自有源码和公开类型 | PKG-VTT-THUMB-03, ENG-04, ENG-06, CORE-07 | cue/rect/result 和参数可选性保持兼容 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | done | | PKG-VTT-THUMB-05 | artplayer-plugin-vtt-thumbnail
验证新旧核心和组合 | PKG-VTT-THUMB-04, CORE-22 | 鼠标和移动进度、全屏、切源、chapter 组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo | | PKG-VTT-THUMB-06 | artplayer-plugin-vtt-thumbnail
验证分发并同步文档 | 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) diff --git a/refactor/progress.md b/refactor/progress.md index e32005260..92791082b 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -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, diff --git a/refactor/risk-table.md b/refactor/risk-table.md index 3f5b72132..39f45c8ca 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -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 | diff --git a/refactor/risks.json b/refactor/risks.json index c6fa4a2a2..dbbf4209b 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -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." }, { diff --git a/refactor/scripts/vtt-thumbnail-package-types.mjs b/refactor/scripts/vtt-thumbnail-package-types.mjs index 0ea6194c7..311715d2e 100644 --- a/refactor/scripts/vtt-thumbnail-package-types.mjs +++ b/refactor/scripts/vtt-thumbnail-package-types.mjs @@ -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 }) => (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[0] = {}; +const result: ReturnType> = {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 + }) +} diff --git a/refactor/scripts/vtt-thumbnail-types.test.mjs b/refactor/scripts/vtt-thumbnail-types.test.mjs index 7aec20a59..db463ceeb 100644 --- a/refactor/scripts/vtt-thumbnail-types.test.mjs +++ b/refactor/scripts/vtt-thumbnail-types.test.mjs @@ -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', () => { diff --git a/refactor/tasks.json b/refactor/tasks.json index 121216914..263958bf3 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -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" ] }, { diff --git a/test/types/vtt-thumbnail-public.ts b/test/types/vtt-thumbnail-public.ts index b1f954a8a..9d580edfc 100644 --- a/test/types/vtt-thumbnail-public.ts +++ b/test/types/vtt-thumbnail-public.ts @@ -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, [option: Option]>> type ExactFactory = Assert> type ExactResult = Assert>, Result>> type ActualResult = Assert>, Promise>> +type HistoricalFactory = (option: { vtt?: string, style?: Partial }) => (art: Artplayer) => { name: 'artplayerPluginVttThumbnail' } +type ExactHistoricalFactory = Assert> +type ExactRuntimeFactory = Assert> +type RuntimeEntryTypes = Assert> 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[0]) => (_art: Artplayer) => ({ name: 'artplayerPluginVttThumbnail' }) +const namespaceFactory: HistoricalFactory = namespace.default +const legacyReplacement: typeof legacyEntry = fromOld legacyEntry(option) const factory: RuntimeFactory = runtime const pending: Promise = 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 }