refactor(vast): [PKG-VAST-04] retain published types and add accurate runtime declarations

This commit is contained in:
Harvey Zhao committed 2026-09-14 23:07:52 +08:00
1 parent c162d2b3b3
commit e5c73fa620
34 files changed
+7819 -1495

No files matched your search

File diff suppressed because it is too large. Load diff
+11
View File
@@ -12,4 +12,15 @@ export default antfu({
rules: {
"eslint-comments/no-unlimited-disable": "off",
},
}, {
name: 'artplayer/frozen-vast-public-declaration',
files: ['packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts'],
// Keep actual npm 1.0.0 declaration bytes; strict consumers verify its contracts.
rules: {
'style/semi': 'off',
'style/indent': 'off',
'style/member-delimiter-style': 'off',
'ts/no-use-before-define': 'off',
'ts/consistent-type-definitions': 'off',
},
});
+3 -1
View File
@@ -148,7 +148,9 @@
"check:vconsole": "node scripts/build-vconsole.mjs --check",
"test:vconsole": "node --test test/vconsole-lifecycle.test.js",
"test:browser:source": "node scripts/browser-check.mjs source",
"test:browser:installed": "node scripts/browser-check.mjs installed"
"test:browser:installed": "node scripts/browser-check.mjs installed",
"test:vast-types": "node --test refactor/scripts/vast-types.test.mjs",
"test:vast-types-package": "node refactor/scripts/vast-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+2 -1
View File
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
+22 -8
View File
@@ -1,7 +1,7 @@
# VAST implementation and maintenance
The runtime follows the accepted published-versus-workspace initialization decision.
Public declaration reconciliation and real IMA validation remain separate release gates.
The runtime and public types follow the accepted published-versus-workspace decisions.
Real IMA validation and full distribution acceptance remain separate release gates.
## Module map
@@ -17,8 +17,13 @@ Public declaration reconciliation and real IMA validation remain separate releas
active-ad request suppression; default mode delegates those behaviors to the SDK.
- `src/view.ts` creates each mode's historical overlay styles and distinct per-module container
IDs, including multiple instances initialized in the same millisecond.
- `src/types.ts` describes the implementation context and SDK boundary. Public legacy
declarations in `types/` remain separate pending PKG-VAST-04 reconciliation.
- `src/types.ts` imports shared fields from `types/runtime-api.d.ts` for the implementation
context, request configuration and result, and adds only private utilities/callback typing.
- `types/artplayer-plugin-vast.d.ts` is the exact npm1.0.0 declaration compatibility surface.
`types/runtime-api.d.ts` is the accurate SDK/context/Promise source; runtime.d.ts/.d.cts
expose CJS types, while runtime.d.mts exposes native ESM types. All resolve to the same
runtime artifacts. CJS declarations intentionally duplicate a small export facade;
changes must keep runtime.d.ts and runtime.d.cts identical.
Dependency direction is entry -> session -> SDK/view/types. The session passes the
original `art` through the callback for compatibility; only template elements are
@@ -36,7 +41,7 @@ secondary cleanup failures are logged without replacing the original error.
Explicit plugin `destroy()` releases the current ad session, resets active-ad state,
and permits a later `init`/`playUrl`/`playRes` while the core remains alive. Core destroy
and failed attachment are terminal: request methods are inert and `init()` returns
null. Public declaration accuracy for this invalid-lifetime case belongs to04.
null. The accurate runtime declaration includes that terminal null result.
Each session has an owner identity. Its callbacks verify that identity and core
liveness before changing visibility. Cleanup detaches the current owner and clears
@@ -73,7 +78,8 @@ resource ownership. Never use a retained disposed SDK as an active session.
Both modes retain void request methods, async registration and SDK/callback rejection.
The old `require(...).default(...)` call and current callable export remain available;
this does not recreate the old namespace object's reflection shape. Contradictory
public return declarations and the new second-argument declaration remain task04.
historical return declarations remain unchanged at the root; `/runtime` supplies the
accurate Promise, both mode-specific contexts and the second argument.
See [the accepted decision](../../refactor/vast-compatibility-decision.md).
Overlay IDs use the historical `art-` default prefix or `art-vast-` workspace prefix,
@@ -103,9 +109,17 @@ import, using an already destroyed host to verify registration without loading a
Set `ARTPLAYER_VAST_ARTIFACT` and `ARTPLAYER_VAST_ESM_ARTIFACT` to test actual build
files; otherwise it builds source via the repository helper. The live alias path
uses the controlled SDK in `test/vast.test.js` and the browser registration case.
Public alias declarations and complete installed entry validation remain04/06.
Accurate alias types are exposed only by `/runtime`; the root remains a pure historical
factory type. Installed root/legacy/runtime imports and strict SDK type dependency
resolution are checked by `yarn test:vast-types-package`. This does not replace task06's
complete browser/distribution acceptance.
For state or cleanup changes start in session, for callback timing in entry, and for
SDK request fields in sdk. Keep Node candidate assertions, historical observations,
browser counterparts and [test maintenance](../../refactor/vast-validation.md) aligned.
Actual IMA/media/device validation remains05, public types04, and full distribution06.
`yarn test:vast-types` verifies unchanged root bytes, historical type conflicts, negative
runtime consumers and editor generation in TS5.9.3/4.3.5. `yarn build:ts artplayer-plugin-vast`
regenerates the root-compatible editor global. Frozen workspace editor tests continue to
exercise the SDK declaration bundler; they are labelled historical. Public migration
is documented in [the accepted type decision](../../refactor/vast-type-decision.md).
Actual IMA/media/device validation remains05 and full distribution06.
+34 -2
View File
@@ -25,8 +25,40 @@ artplayerPluginVast((context) => {
The callback may be asynchronous. Core destruction releases the ad session and prevents
later requests or recreation. Explicit plugin `destroy()` allows a new ad session while
the core is alive. See the architecture guide for mode-specific fields and defaults.
Public TypeScript declarations for the second argument are still being reconciled in
PKG-VAST-04; these are JavaScript usage examples, not completed declaration acceptance.
## TypeScript
The root and `/legacy` declarations preserve npm 1.0.0's historical factory shape,
including its required callback, `any` SDK fields and inaccurate synchronous result.
Registration has always been asynchronous: await it even when using the old declarations.
Use `/runtime` for accurate SDK types, Promise results, optional callbacks and the
compatibility option. This entry uses the same JavaScript implementation:
```ts
import vast from 'artplayer-plugin-vast/runtime'
vast(({ imaPlayer }) => {
// The default mode allocates this player before the callback.
imaPlayer.addEventListener('AdStarted', onAdStarted)
})
vast((context) => {
context.playerOptions.autoResize = false
context.init()?.addEventListener('AdStarted', onAdStarted)
}, { compatibility: 'workspace-1.2' })
```
Code using unpublished workspace types should move its imports to `/runtime` as well.
`ArtplayerPluginVastOption` describes the workspace callback; `ArtplayerPluginVastInstance`
describes the awaited result with `destroy()`. A workspace resource getter can be null,
and `init()` returns null after core destruction. Default data fields retain the last
allocation after release; retaining their values does not keep that allocation alive.
CommonJS TypeScript without interop can use
`import vast = require('artplayer-plugin-vast/runtime')`. Both `vast` and `vast.default`
are typed there. The old root declaration intentionally keeps its original factory
type without adding a required `.default` member, so plain replacement functions and
historical `Parameters`/`ReturnType` extraction remain compatible.
## Maintenance
@@ -27,6 +27,16 @@
"types": "./types/artplayer-plugin-vast.d.ts",
"import": "./dist/artplayer-plugin-vast.legacy.js",
"require": "./dist/artplayer-plugin-vast.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-vast.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-vast.js"
}
}
},
"main": "./dist/artplayer-plugin-vast.js",
@@ -37,5 +47,15 @@
"dependencies": {
"@alugha/ima": "2.1.0",
"@glomex/vast-ima-player": "^1.21.2"
},
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-vast.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
}
+3 -27
View File
@@ -1,30 +1,6 @@
import type { google, ImaSdk } from '@alugha/ima'
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
import type { WorkspaceContext } from '../types/runtime-api.js'
export type Utilities = Pick<typeof Artplayer.utils, 'createElement' | 'setStyles'>
export type RequestConfig = Record<string, unknown>
export interface CompatibilityOptions {
compatibility?: 'workspace-1.2'
}
export interface Context {
art: Artplayer
playUrl: (url: string, config?: RequestConfig) => void
playRes: (response: string, config?: RequestConfig) => void
init: () => Player | null
ima: ImaSdk
adsRenderingSettings: google.ima.AdsRenderingSettings
playerOptions: PlayerOptions
readonly imaPlayer: Player | null
readonly id: string | null
readonly $container: HTMLDivElement | null
readonly container: HTMLDivElement | null
}
export type Callback = (context: Context) => unknown
export interface Result {
name: 'artplayerPluginVast'
destroy: () => void
}
export type { CompatibilityOptions, WorkspaceContext as Context, RequestConfig, RuntimeResult as Result } from '../types/runtime-api.js'
export type Callback = (context: WorkspaceContext) => unknown
@@ -1,36 +1,20 @@
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
import type Artplayer from 'artplayer';
declare global {
interface Window {
artplayerPluginVast?: typeof artplayerPluginVast
}
}
export = artplayerPluginVast;
export as namespace artplayerPluginVast;
type PlayUrlFn = (url: string, config?: any) => void
type PlayResFn = (res: string, config?: any) => void
type Option = (params: {
art: Artplayer;
id: string;
ima: any;
imaPlayer: any;
$container: HTMLDivElement;
playUrl: (url: string) => void;
playRes: (res: string) => void;
}) => void;
interface VastPluginContext {
art: Artplayer
ima: any
imaPlayer: Player | null
playUrl: PlayUrlFn
playRes: PlayResFn
init: () => Player
adsRenderingSettings: any
playerOptions: PlayerOptions
container: HTMLDivElement | null
}
type Result = {
name: 'artplayerPluginVast';
};
export type ArtplayerPluginVastOption = (params: VastPluginContext) => void | Promise<void>
export interface ArtplayerPluginVastInstance {
name: 'artplayerPluginVast'
destroy?: () => void
}
declare function artplayerPluginVast(
option: ArtplayerPluginVastOption,
): (art: Artplayer) => ArtplayerPluginVastInstance
export default artplayerPluginVast
declare const artplayerPluginVast: (option: Option) => (art: Artplayer) => Result;
+54
View File
@@ -0,0 +1,54 @@
import type { google, ImaSdk } from '@alugha/ima'
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
export type RequestConfig = Record<string, unknown>
export interface CompatibilityOptions {
compatibility?: 'workspace-1.2'
}
export interface Context {
art: Artplayer
ima: ImaSdk
adsRenderingSettings: google.ima.AdsRenderingSettings
playerOptions: PlayerOptions
playUrl: (url: string, config?: RequestConfig) => void
playRes: (response: string, config?: RequestConfig) => void
init: () => Player | null
readonly container: HTMLDivElement | null
}
/** Published data fields retain their last allocation after release. */
export interface PublishedContext extends Context {
imaPlayer: Player
id: string
$container: HTMLDivElement
}
/** Resource getters are null before initialization and after release. */
export interface WorkspaceContext extends Context {
readonly imaPlayer: Player | null
readonly id: string | null
readonly $container: HTMLDivElement | null
}
export type RuntimeContext = PublishedContext | WorkspaceContext
export type RuntimeCallback<T extends RuntimeContext = RuntimeContext> = (context: T) => unknown
export interface RuntimeResult {
name: 'artplayerPluginVast'
destroy: () => void
}
export type Registration = (art: Artplayer) => Promise<RuntimeResult>
export interface RuntimeFactory {
(callback?: RuntimeCallback<PublishedContext>, options?: { compatibility?: undefined }): Registration
(callback: RuntimeCallback<WorkspaceContext> | undefined, options: { compatibility: 'workspace-1.2' }): Registration
(callback: RuntimeCallback | undefined, options: CompatibilityOptions): Registration
default: RuntimeFactory
}
export type ArtplayerPluginVastOption = RuntimeCallback<WorkspaceContext>
export type ArtplayerPluginVastInstance = RuntimeResult
@@ -0,0 +1,19 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginVast: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginVast {
type ArtplayerPluginVastInstance = import('./runtime-api.js').ArtplayerPluginVastInstance
type ArtplayerPluginVastOption = import('./runtime-api.js').ArtplayerPluginVastOption
type CompatibilityOptions = import('./runtime-api.js').CompatibilityOptions
type Context = import('./runtime-api.js').Context
type PublishedContext = import('./runtime-api.js').PublishedContext
type Registration = import('./runtime-api.js').Registration
type RequestConfig = import('./runtime-api.js').RequestConfig
type RuntimeCallback<T extends RuntimeContext = RuntimeContext> = import('./runtime-api.js').RuntimeCallback<T>
type RuntimeContext = import('./runtime-api.js').RuntimeContext
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
type RuntimeResult = import('./runtime-api.js').RuntimeResult
type WorkspaceContext = import('./runtime-api.js').WorkspaceContext
}
export = artplayerPluginVast
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginVast: RuntimeFactory
export default artplayerPluginVast
export type { ArtplayerPluginVastInstance, ArtplayerPluginVastOption, CompatibilityOptions, Context, PublishedContext, Registration, RequestConfig, RuntimeCallback, RuntimeContext, RuntimeFactory, RuntimeResult, WorkspaceContext } from './runtime-api.js'
+19
View File
@@ -0,0 +1,19 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginVast: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginVast {
type ArtplayerPluginVastInstance = import('./runtime-api.js').ArtplayerPluginVastInstance
type ArtplayerPluginVastOption = import('./runtime-api.js').ArtplayerPluginVastOption
type CompatibilityOptions = import('./runtime-api.js').CompatibilityOptions
type Context = import('./runtime-api.js').Context
type PublishedContext = import('./runtime-api.js').PublishedContext
type Registration = import('./runtime-api.js').Registration
type RequestConfig = import('./runtime-api.js').RequestConfig
type RuntimeCallback<T extends RuntimeContext = RuntimeContext> = import('./runtime-api.js').RuntimeCallback<T>
type RuntimeContext = import('./runtime-api.js').RuntimeContext
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
type RuntimeResult = import('./runtime-api.js').RuntimeResult
type WorkspaceContext = import('./runtime-api.js').WorkspaceContext
}
export = artplayerPluginVast
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,66 @@
# PKG-VAST-04:恢复发布类型并提供准确的双模式声明
基线c162d2b3b3f4fe64c3d178bdf0dc3b8eea32cac5。用户本次独立确认根入口保留
npm类型、未发布工作区类型迁移到/runtime,见[vast-type-decision.md](../vast-type-decision.md)。
这不是沿用Ads或其他四包的专属授权,也没有再次修改上一步初始化行为。
## 类型和模块
根/legacy恢复实际npm1.0.0声明原文:export=、必需callback、旧字段、SDK any与
同步结果误声明。完整typeof、Parameters、ReturnType、普通函数双向赋值与两个
编译器基线均保持;默认根类型没有新增必填default属性或不同重载。
/runtime复用已有三格式JS产物。runtime-api.d.ts统一描述ImaSdk/Player/Settings/
Options、配置、Context和Promise<RuntimeResult>;CJS的d.ts/d.cts与ESM的d.mts分别
提供正确模块形状。实现types.ts引用同一份字段/配置/Result,避免手工维护重复声明。
默认回调Player/id/容器非空且可写;workspace getter可空且只读;动态选项保留联合
上下文,不能推断默认非空。init在终止后可返回null,destroy在await结果中必需。
callback可省略,default自别名在准确入口声明,SDK字段不降级为any。
用户需将未发布工作区类型导入移到/runtime,并选择workspace-1.2;原根类型仍有
已明确披露的同步误声明,实际注册仍须await。README和模块地图包含迁移说明。
本步无新依赖,继续使用已固定的Glomex1.21.2和@alugha/ima2.1.0。新增两个Yarn
类型测试脚本,集成主typecheck和baseline发现;不改变Yarn版本、lockfile或包版本。
## 编辑器与打包修复
CommonJS编辑器生成器此前只允许Factory命名空间类型,不接受旧VAST的内联函数
类型和模块私有别名,首轮构建/测试精确失败。现支持该声明形状;未知导入/导出
仍拒绝。生成文件恢复npm旧global语义;原工作区SDK/Window/命名类型测试改用
校验过的冻结源码,保留旧路径验证,新增候选声明正反例及实际Monaco检查。
根文件的五项样式/声明顺序规则仅对这一个冻结文件局部关闭,以保留npm字节;
新runtime、实现和测试继续正常lint。生成声明只保留必要的历史顺序/别名规则说明,
不以any或skipLibCheck掩盖准确入口。生成器与旧SDK依赖工具文档同步。
首次真实pack检测到内部tsconfig.json泄漏,补充该包.npmignore后通过。源文件、
tsconfig与依赖不进入包。14组消费者从仓库外离线安装实际旧包/最终候选与核心,
再强制frozen重装并验证lock无变化、每个安装成员哈希、非workspace链接及所有
类型解析路径。准确入口实际解析到安装的两层SDK声明依赖。
## 验证及边界
日志、最终文件、安装矩阵与浏览器输入见[验证记录](../baselines/vast-types-validation.json)。
- 旧根原文与候选一致;完整历史工作区消费者冲突可重跑,3项类型提取、1项反向
赋值、2项缺失字段、1项可空访问失败分别记录。首次正式断言漏计反向赋值诊断,
补齐实际错误列表;没有删去消费者或改成宽泛失败断言。
- 类型/编辑器/工程20项通过;准确runtime14条反例逐行失败,根3类非法调用保持
原错误。完整VAST受控SDK运行时82项通过。
- 真正安装14组全部通过,包含TS5.9.3/4.3.5、Node10 CJS、NodeNext CJS/ESM、
Bundler ESM、不启用interop的import=require。root/legacy/runtime CJS、root/runtime
原生ESM身份和default别名验证通过;注册用已销毁宿主,不加载真实IMA。
- 三桌面引擎的本地Monaco编译旧VAST回调/工厂、拒绝缺失callback与错误ID类型,
并实际执行主播放器样例、获得非零解码尺寸。广告工厂只做类型验证。
- 正常VAST构建三格式及docs副本与上一步运行时字节一致;生成编辑器资产通过
两编译器检查。源与测试lint、严格源TS通过;全项目typecheck通过,检查415个
生产TS文件,已有核心runtime兼容编译器5.1.6矩阵也通过。
- 初始生成器失败、旧声明样式冲突、打包tsconfig泄漏、历史反例漏计与一次无文件
修改的PowerShell转义错误分别记录,不混作产品运行时回归。
## 交接和回退
本步只完成04类型/源码声明协调,不替代05真实IMA/设备验收或06完整历史深路径/
浏览器分发。VAST-TYPE-01按用户批准限定接受,VAST-DIST/LIFE和SDK验收继续开放。
本任务独立本地提交并执行提交审计,无push/publish。回退该提交恢复此前工作区根
声明并移除/runtime类型导出及本步打包/生成器修复;上一步两种运行时模式不受影响。
@@ -0,0 +1,32 @@
import type Artplayer from 'artplayer'
import vast from 'artplayer-plugin-vast'
type OriginalCallback = (params: {
art: Artplayer
id: string
ima: any
imaPlayer: any
$container: HTMLDivElement
playUrl: (url: string) => void
playRes: (response: string) => void
}) => void
type OriginalResult = { name: 'artplayerPluginVast' }
type OriginalFactory = (option: OriginalCallback) => (art: Artplayer) => OriginalResult
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 FactoryCheck = Assert<Equal<typeof vast, OriginalFactory>>
type ParameterCheck = Assert<Equal<Parameters<typeof vast>, [option: OriginalCallback]>>
type ResultCheck = Assert<Equal<ReturnType<ReturnType<typeof vast>>, OriginalResult>>
const replacement: typeof vast = (_option) => (_art) => ({ name: 'artplayerPluginVast' })
const reverse: OriginalFactory = vast
vast(({ art, id, imaPlayer, $container, playUrl, playRes }) => {
const identifier: string = id
const element: HTMLDivElement = $container
imaPlayer.addEventListener('AdStarted', () => {})
const url: void = playUrl('/ad.xml')
const response: void = playRes('<VAST/>')
void [art, identifier, element, url, response]
})
export type { FactoryCheck, ParameterCheck, ResultCheck }
void [replacement, reverse]
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 262 项,范围 22 个包及工作区/示例。
状态:todo 48 / doing 20 / blocked 0 / done 194 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 47 / doing 20 / blocked 0 / done 195 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -297,7 +297,7 @@
| PKG-VAST-01 | artplayer-plugin-vast<br>核对包契约与历史用法 | BASE-05 | 异步工厂、callback context、playUrl/playRes/init 与 SDK 版本;实际 npm 1.0.0 与未发布 1.2.0、SDK 1.21.0/1.21.2 的60成员归档和发布关联核心5.1.7 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-VAST-02 | artplayer-plugin-vast<br>建立特有行为与错误测试 | PKG-VAST-01, ENG-03, ENG-05 | SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07, PKG-VAST-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07, PKG-VAST-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-VAST-05 | artplayer-plugin-vast<br>验证新旧核心和组合 | PKG-VAST-04, CORE-22 | 官方测试广告或受控 SDK 环境,错误后主视频状态恢复;核验真实5.1.7/5.4.1/候选核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-VAST-06 | artplayer-plugin-vast<br>验证分发并同步文档 | PKG-VAST-05, ENG-07 | vast.js、SDK 资源、声明消费和独立版本记录;核验实际发布 namespace default、历史深路径、ESM和准确声明,版本按政策2.0.0由REL-09统一处理 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-VAST-07 | artplayer-plugin-vast<br>恢复旧发布VAST工厂default调用入口 | PKG-VAST-01, PKG-VAST-02 | 保留当前直接工厂并增加default自身别名,验证四类分发调用及异步注册,初始化决策独立保留 | 旧产物default用例失败,新源码/实际main/legacy/ESM通过;真实核心三浏览器控制SDK注册验证、模块文档/构建/测试入口同步;不冒充namespace反射、声明和IMA验收 | M | done |
@@ -601,6 +601,7 @@
- PKG-VAST-01: [记录](baselines/vast-contract.md) [记录](baselines/vast-release.json) [记录](changes/2026-09-12-PKG-VAST-01-contract.md) [记录](baselines/vast-contract-validation.json)
- PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md)
- PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md) [记录](changes/2026-09-14-PKG-VAST-03-compatibility.md) [记录](baselines/vast-compatibility-validation.json)
- PKG-VAST-04: [记录](changes/2026-09-14-PKG-VAST-04-types.md) [记录](baselines/vast-types-validation.json) [记录](vast-type-decision.md) [记录](vast-validation.md)
- PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md)
- PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json)
- PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json)
+20
View File
@@ -1,5 +1,25 @@
# 进度与证据
## PKG-VAST-04 公开类型协调及安装消费者完成
用户单独确认保留npm1.0.0根类型、工作区TS用法迁移/runtime。根/legacy声明原文
保留;准确入口覆盖Promise、SDK字段、两种模式的可空性/可写性、可选callback和
default别名。内部Context/Result/配置引用同一准确定义。README/架构/迁移决策同步。
实际旧包与最终候选在仓库外离线安装并强制frozen重装,14组编译模式全部通过,
检查所有安装成员/解析路径;最终核心+VAST共79个成员与当前文件一致。发现并修复
VAST打包带入内部tsconfig。编辑器生成器支持旧版内联工厂,保留冻结工作区SDK
生成测试;三浏览器Monaco正反例和主片解码均通过,未执行广告SDK。
20项类型/编辑器/工程、82项VAST运行回归通过,全项目typecheck通过415生产TS
文件;正常构建与上一步运行时产物一致。初始生成器/样式/打包错误、历史反例漏计
均有失败记录。见[变更](changes/2026-09-14-PKG-VAST-04-types.md)、
[证据](baselines/vast-types-validation.json)和[类型决定](vast-type-decision.md)。
04完成、类型风险按用户明确取舍限定接受;262项为195 done、20 doing、47 todo。
VAST剩05真实IMA/组合与06完整分发,LIFE/DIST和SDK风险继续开放。独立本地提交并
先核验提交审计,未push/publish;其他包、设备、远端CI及发布复盘不据此标完成。
## PKG-VAST-03 已确认初始化策略与运行时资源整理完成
用户于2026-09-14明确接受npm提前初始化默认、显式workspace-1.2惰性模式。
+1 -1
View File
@@ -148,7 +148,7 @@
| ADS-MEDIA-01 | open / 已复现 | Ads metadata 与 play 拒绝、隐藏页媒体状态和内容恢复仍缺真实验证 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-UI-01 | resolved / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| VAST-CONTEXT-01 | accepted-with-scope / 已复现 | Published VAST callback aliases and eager Player differ from the unpublished lazy workspace | PKG-VAST-02, PKG-VAST-03, PKG-VAST-04 |
| VAST-TYPE-01 | open / 已复现 | VAST declares an async factory as synchronous and imports an unavailable SDK type dependency | PKG-VAST-04, PKG-VAST-06 |
| VAST-TYPE-01 | accepted-with-scope / 已复现 | VAST declares an async factory as synchronous and imports an unavailable SDK type dependency | PKG-VAST-04, PKG-VAST-06 |
| VAST-LIFE-01 | open / 已复现 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 |
| VAST-DIST-01 | open / 已复现 | Published VAST CommonJS default namespace differs from the current callable distribution | PKG-VAST-04, PKG-VAST-06, PKG-VAST-07 |
| AMBILIGHT-LIFE-01 | open / 已复现 | Ambilight sampling and RAF/DOM ownership lack terminal/error handling | PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-05 |
+16 -5
View File
@@ -3283,7 +3283,7 @@
"id": "VAST-TYPE-01",
"title": "VAST declares an async factory as synchronous and imports an unavailable SDK type dependency",
"confirmation": "reproduced",
"status": "open",
"status": "accepted-with-scope",
"owners": [
"PKG-VAST-04",
"PKG-VAST-06"
@@ -3294,11 +3294,20 @@
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"refactor/baselines/vast-contract-validation.json",
"refactor/baselines/vast-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md"
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md",
"refactor/changes/2026-09-14-PKG-VAST-04-types.md",
"refactor/baselines/vast-types-validation.json",
"refactor/vast-type-decision.md"
],
"compatibleResolution": "Provide accurate Promise/context/IMA types and a self-contained installed declaration chain while checking legacy export= and consumers.",
"closureCriteria": "Strict isolated package consumers pass without skipLibCheck; synchronous Result inference differences are reproduced and resolved or separately approved. Ads approval does not apply.",
"workspaceState": "Direct @alugha/ima2.1.0 removes the two TS2307 diagnostics in TS5.9.3 and4.3.5; public synchronous Result mismatch and isolated packaged declaration validation remain04/06."
"workspaceState": "Task04 complete: root npm bytes and exact factory relationships preserved; CJS/ESM runtime types, editor consumers, old/current compiler positives and per-line negatives pass. Runtime public types share Context/Result/config with implementation. Full media and distribution acceptance remain05/06.",
"resolutionEvidence": [
"refactor/changes/2026-09-14-PKG-VAST-04-types.md",
"refactor/baselines/vast-types-validation.json",
"refactor/vast-type-decision.md"
],
"resolutionRationale": "User separately accepted npm1.0.0 root compatibility types and /runtime accurate Promise/SDK/context types, with unpublished workspace migration. Fourteen actual isolated installation/compiler cases pass without skipLibCheck and enforce SDK dependency resolution; root historical inaccuracy is disclosed, not presented as runtime truth."
},
{
"id": "VAST-LIFE-01",
@@ -3345,11 +3354,13 @@
"refactor/baselines/vast-contract-validation.json",
"refactor/changes/2026-09-14-PKG-VAST-07-default-alias.md",
"refactor/baselines/vast-alias-validation.json",
"test/vast-exports.test.js"
"test/vast-exports.test.js",
"refactor/changes/2026-09-14-PKG-VAST-04-types.md",
"refactor/baselines/vast-types-validation.json"
],
"compatibleResolution": "Preserve require(pkg).default alongside callable factory, script global and current ESM/legacy paths.",
"closureCriteria": "Real packed main/legacy/ESM, historical deep imports, export= consumers and local demo/editor all pass with immutable published comparisons.",
"workspaceState": "Task07 restores historical JavaScript .default calls using a callable self-alias. Final four-format tests fail on pre-change main/ESM and pass produced main+ESM and legacy+ESM.65 Node and117 controlled-SDK browser checks pass. Old namespace reflection shape, public declarations, deep paths and installed distribution remain04/06; this risk stays open."
"workspaceState": "Task07 preserves old JavaScript default calls. Task04 adds accurate typed default on /runtime, actual isolated root/legacy/runtime imports and native ESM identity checks; preserves npm root types. Historical deep paths, complete browser distribution and old namespace reflection distinction remain task06; risk stays open."
},
{
"id": "AMBILIGHT-LIFE-01",
+26
View File
@@ -0,0 +1,26 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
export function vastConsumer(kind, { legacy = false, requireImport = false } = {}) {
let source = fs.readFileSync(kind === 'runtime' ? 'test/types/vast-runtime.ts' : 'refactor/fixtures/consumers/vast-published.ts', 'utf8')
const entry = kind === 'runtime' ? 'artplayer-plugin-vast/runtime' : legacy ? 'artplayer-plugin-vast/legacy' : 'artplayer-plugin-vast'
if (legacy)
source = source.replaceAll('\'artplayer-plugin-vast\'', `'${entry}'`)
if (requireImport)
source = source.replace(`import vast from '${entry}'`, `import vast = require('${entry}')`)
return source
}
export function runtimeNegatives(source) {
const lines = source.split('\n')
const expected = []
const invalid = lines.map((line, index) => {
if (!/^[ \t]*\/\/ @ts-expect-error/.test(line))
return line
assert(lines[index + 1]?.trim() && !lines[index + 1].trim().startsWith('//'))
expected.push(index + 2)
return ''
}).join('\n')
assert.equal(expected.length, 14, 'Keep every runtime misuse independently observable')
return { invalid, expected }
}
+116
View File
@@ -0,0 +1,116 @@
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 { hash, readMember } from './releases.mjs'
import { runtimeNegatives, vastConsumer } from './vast-consumer.mjs'
import { verifyVastContract } from './vast-contract.mjs'
let output
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:vast-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/vast-package-types-'))
console.log(`VAST isolated package evidence: ${output}`)
const contract = await verifyVastContract()
const fixtures = Object.fromEntries(['runtime', 'published'].map(kind => [kind, hash(vastConsumer(kind))]))
const packages = []
for (const name of ['artplayer', 'artplayer-plugin-vast']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, name === 'artplayer-plugin-vast' ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const matrix = []
const core = packages[0]
const plugins = [{ ...contract.baseline.release, archive: contract.archives.get('artplayer-plugin-vast@1.0.0'), label: 'published' }, { ...packages[1], label: 'candidate' }]
for (const plugin of plugins) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'vast-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive', '--force'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
for (const pkg of [core, plugin]) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const runtimeFile = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtimeFile, plugin.label === 'published'
? `const assert = require('node:assert/strict'); const value = require('artplayer-plugin-vast'); assert.equal(typeof value.default, 'function'); assert.equal(typeof value.default(() => {}), 'function');`
: `const assert = require('node:assert/strict');
(async () => {
for (const name of ['artplayer-plugin-vast','artplayer-plugin-vast/runtime','artplayer-plugin-vast/legacy']) {const factory = require(name); assert.equal(factory.default, factory); assert.equal((await factory()({isDestroy:true})).name,'artplayerPluginVast');}
assert.equal(require('artplayer-plugin-vast/runtime'),require('artplayer-plugin-vast'));
const [root,runtime] = await Promise.all(['artplayer-plugin-vast','artplayer-plugin-vast/runtime'].map(name=>import(name)));
assert.equal(root.default,runtime.default); assert.equal(runtime.default.default,runtime.default); assert.equal((await runtime.default()({isDestroy:true})).name,'artplayerPluginVast');
})().catch(error=>{console.error(error);process.exitCode=1;});`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs'], [ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop']]
for (const [compiler, mode] of modes) {
const next = mode.startsWith('nodenext')
const requireImport = mode.endsWith('-no-interop')
const filename = path.join(consumer, next ? `consumer.${mode.endsWith('-cjs') ? 'cts' : 'mts'}` : 'consumer.ts')
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !requireImport, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
function compile(source) {
fs.writeFileSync(filename, source)
const program = compiler.createProgram([filename], options)
const declarations = []
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped installed consumer: ${actual}`)
if (!program.isSourceFileDefaultLibrary(file))
declarations.push({ file: path.relative(consumer, file.fileName).replaceAll('\\', '/'), sha256: hash(fs.readFileSync(file.fileName)) })
}
const diagnostics = compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, file: item.file && path.relative(consumer, item.file.fileName).replaceAll('\\', '/'), line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
return { sourceSha256: hash(source), diagnostics, declarations }
}
const entry = { plugin: plugin.label, compiler: compiler.version, mode }
matrix.push(entry)
const source = vastConsumer('published', { requireImport })
entry.root = compile(source)
writeJson(path.join(output, 'matrix-progress.json'), matrix)
assert.deepEqual(entry.root.diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`)
entry.rootNegative = compile(`${source}\nvast();\nvast(undefined);\nvast.default(() => {});\n`)
assert.deepEqual(entry.rootNegative.diagnostics.map(item => item.code), [2554, 2345, 2339])
if (plugin.label === 'candidate') {
entry.legacy = compile(vastConsumer('published', { requireImport, legacy: true }))
assert.deepEqual(entry.legacy.diagnostics, [])
const runtime = vastConsumer('runtime', { requireImport })
entry.runtime = compile(runtime)
writeJson(path.join(output, 'matrix-progress.json'), matrix)
assert.deepEqual(entry.runtime.diagnostics, [])
assert(entry.runtime.declarations.some(item => item.file.includes('@alugha/ima/')), 'SDK types must resolve through actual installed dependencies')
assert(entry.runtime.declarations.some(item => item.file.includes('@glomex/vast-ima-player/')))
const { invalid, expected } = runtimeNegatives(runtime)
entry.runtimeNegative = compile(invalid)
assert.deepEqual(entry.runtimeNegative.diagnostics.map(item => item.line), expected)
}
writeJson(path.join(output, 'matrix-progress.json'), matrix)
}
}
finally { removeConsumer(consumer) }
}
assert.deepEqual(Object.fromEntries(['runtime', 'published'].map(kind => [kind, hash(vastConsumer(kind))])), fixtures, 'Fixtures changed during installed validation')
writeJson(path.join(output, 'report.json'), { task: 'PKG-VAST-04', node: process.version, fixtures, packages, published: { archive: plugins[0].archive, sha256: hash(fs.readFileSync(plugins[0].archive)) }, matrix, scope: 'Actual published and Yarn-packed candidate installed outside workspace; offline forced frozen reinstall, member hashes and declaration path isolation. No real IMA, live ad SDK registration, device or complete task06 distribution claim.' })
console.log(`VAST isolated installed consumers passed ${matrix.length} compiler modes: ${output}`)
}
main().catch((error) => {
if (output)
writeJson(path.join(output, 'failure.json'), { message: error.message, stack: error.stack })
console.error(error)
process.exitCode = 1
})
+87
View File
@@ -0,0 +1,87 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Repository baseline 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 { readMember } from './releases.mjs'
import { runtimeNegatives } from './vast-consumer.mjs'
import { verifyVastContract } from './vast-contract.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
test('VAST frozen workspace declaration reproduces the approved npm consumer conflict', async () => {
const contract = await verifyVastContract()
const historical = contract.sources.get('packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts')
const source = fs.readFileSync('refactor/fixtures/consumers/vast-published.ts', 'utf8').replace('from \'artplayer-plugin-vast\'', 'from \'./vast-workspace-history\'')
for (const compiler of [ts, compat]) {
const filename = path.resolve('test/types/vast-history-consumer.ts')
const declaration = path.resolve('test/types/vast-workspace-history.d.ts')
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] }
const host = compiler.createCompilerHost(options)
const original = host.getSourceFile.bind(host)
host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
? compiler.createSourceFile(file, source, language, true)
: path.resolve(file) === declaration ? compiler.createSourceFile(file, historical, language, true) : original(file, language, ...rest)
const exists = host.fileExists.bind(host)
host.fileExists = file => path.resolve(file) === declaration || exists(file)
const program = compiler.createProgram([filename, declaration], options, host)
const codes = compiler.getPreEmitDiagnostics(program).map(item => item.code)
assert.deepEqual(codes, [2344, 2344, 2344, 2322, 2339, 2339, compiler === ts ? 18047 : 2531])
}
})
test('VAST root retains actual npm declaration bytes and complete factory extraction/replacement', async () => {
const contract = await verifyVastContract()
const published = readMember(contract.archives.get('artplayer-plugin-vast@1.0.0'), 'package/types/artplayer-plugin-vast.d.ts').toString().replaceAll('\r\n', '\n')
assert.equal(fs.readFileSync('packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts', 'utf8').replaceAll('\r\n', '\n'), published)
const source = fs.readFileSync('refactor/fixtures/consumers/vast-published.ts', 'utf8')
for (const [compiler, mode] of modes) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
const invalid = `${source}\nvast();\nvast(undefined);\nvast.default(() => {});\n`
assert.deepEqual(checkConsumer(compiler, mode, invalid).map(item => item.code), [2554, 2345, 2339])
assert.deepEqual(checkConsumer(compiler, mode, source.replaceAll('\'artplayer-plugin-vast\'', '\'artplayer-plugin-vast/legacy\'')), [])
}
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import vast = require('artplayer-plugin-vast'); vast(({id}) => id);`), [])
})
test('VAST runtime checks both initialization modes, SDK fields and async results in current and old compilers', () => {
assert.equal(fs.readFileSync('packages/artplayer-plugin-vast/types/runtime.d.ts', 'utf8'), fs.readFileSync('packages/artplayer-plugin-vast/types/runtime.d.cts', 'utf8'))
const source = fs.readFileSync('test/types/vast-runtime.ts', 'utf8')
for (const [compiler, mode] of modes) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
const { invalid, expected } = runtimeNegatives(source)
const errors = checkConsumer(compiler, mode, invalid)
assert.deepEqual(errors.map(item => item.line), expected, `${compiler.version} ${mode}: every invalid statement must fail on its own line`)
}
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import vast = require('artplayer-plugin-vast/runtime');
const mode: vast.CompatibilityOptions = {compatibility:'workspace-1.2'};
vast(({imaPlayer}) => imaPlayer.addEventListener('AdStarted', () => {})); vast.default(undefined, mode);`), [])
})
test('VAST editor preserves the published global callback and pure synchronous historical factory', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginVast')
const file = 'docs/assets/ts/artplayer-plugin-vast.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;
const factory: typeof artplayerPluginVast = (_option) => (_art) => ({name: 'artplayerPluginVast'});
const result: {name: 'artplayerPluginVast'} = artplayerPluginVast(({id, $container, imaPlayer}) => {
const idValue: string = id; const node: HTMLDivElement = $container; imaPlayer.addEventListener('AdStarted', () => {}); void [idValue, node]; })(art);
// @ts-expect-error Historical callback remains required.
artplayerPluginVast();
// @ts-expect-error New options and accurate Promise belong to runtime entry.
artplayerPluginVast(() => {}, {compatibility:'workspace-1.2'});
void [factory, result];`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 2)
}
})
+7 -2
View File
@@ -3112,11 +3112,16 @@
"CORE-07",
"PKG-VAST-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-14-PKG-VAST-04-types.md",
"baselines/vast-types-validation.json",
"vast-type-decision.md",
"vast-validation.md"
]
},
{
"id": "PKG-VAST-05",
+6
View File
@@ -40,3 +40,9 @@ JASSUB 的 PKG-JASSUB-04 保留实际 npm 1.0.0/1.1.0 相同的根声明(忽
不改变必填资源 URL、旧 resize 参数顺序、Promise 返回声明和扩展索引。新增
/runtime 描述真实可选参数、同步方法及 Worker 数据,并复用同一运行产物。
这项实施没有新增旧类型破坏,不声称用户另行批准过 JASSUB 专属类型冲突。
VAST的PKG-VAST-04在2026-09-14取得独立确认:用户接受“保留 npm 根类型,工作区
类型迁移到 /runtime”。根/legacy保留实际npm1.0.0的export=、必需callback、any
SDK字段及同步历史结果;/runtime提供准确Promise、两种初始化模式及SDK类型。
未发布工作区的回调类型、命名类型和根导入形状按[vast-type-decision.md](vast-type-decision.md)
迁移。本次批准独立于VAST初始化时机批准,不是将此前四包授权自动扩大。
+3 -3
View File
@@ -60,6 +60,6 @@ art-vast-;两者增加单模块递增后缀修复同毫秒ID碰撞,不恢复
PKG-VAST-03已落实默认模式、显式工作区模式与发布字段,82项Node、162项三浏览器
受控SDK检查通过;初始化风险按本次用户决定限定接受。详见
[完成记录](changes/2026-09-14-PKG-VAST-03-compatibility.md)。实际IMA播放和公开声明
尚未完成。此前Ads的类型推导批准与本次VAST运行时决定分别记录;未来类型协调
继续以实际发布声明为基线。
[完成记录](changes/2026-09-14-PKG-VAST-03-compatibility.md)。公开声明随后按
[独立类型确认](vast-type-decision.md)恢复npm根类型并提供准确/runtime入口;实际IMA
播放仍未验收。此前Ads的类型批准、本次运行时决定和随后VAST类型决定分别记录。
+52
View File
@@ -0,0 +1,52 @@
# VAST 公开声明协调:已确认并实施
2026-09-14用户明确接受:“保留 npm 根类型,工作区类型迁移到 /runtime”。
本决定独立于初始化运行时批准;后续不再等待这项类型取舍授权。
PKG-VAST-04。运行时初始化策略已由用户单独确认,见
[运行时决策](vast-compatibility-decision.md);这里不重新询问该决定。
## 已复现的声明冲突
实际npm1.0.0根声明采用export=,回调有id/$container/imaPlayer:any,工厂参数必需,
第二层错误地声明同步{name}。未发布工作区1.2.0改成default export,回调增加
init/选项/getter但丢失id/$container,imaPlayer可空,并导出Option/Instance类型。
使用实际npm归档声明和当前工作区声明,在TS5.9.3及4.3.5、strict、无skipLibCheck
下交叉编译:各自消费者在原声明通过;npm消费者在工作区声明出现4项错误,包括
完整工厂类型不等、两个字段缺失、imaPlayer可空。工作区消费者在原npm声明缺少
命名类型。临时证据为refactor/.cache/vast04-type-conflict.json;正式测试继续纳入04。
补充命名类型可以解决部分导入问题,但无法同时保持两套不同回调Parameters、完整
工厂赋值关系、同步Result和准确Promise。增加交叉类型/重载也会改变类型提取结果。
## 已接受的方案
1. 根入口/legacy保留实际npm1.0.0的完整工厂类型、必需参数、export=及同步历史声明。
旧类型的any和同步误声明只作为历史兼容面保留,不声称它们准确描述运行时。
2. 新增/runtime,复用相同JS产物,提供准确Promise、必需destroy、可省略callback、
两种初始化模式、SDK类型、配置字段及.default自身别名。
3. 依赖未发布工作区回调/命名类型的TS代码转到/runtime,并显式选择workspace-1.2。
准确init返回Player|null;核心销毁后可能无法初始化,调用方需要判空。
4. 所有已验证的旧JavaScript调用保持上一任务已实现的行为。旧类型与真实行为的
差异、TS迁移示例、CJS/ESM导入方式写入包文档,并以隔离安装消费者验收。
```ts
import vast from 'artplayer-plugin-vast/runtime'
vast((context) => {
context.playerOptions.autoResize = false
context.init()?.addEventListener('AdStarted', onAdStarted)
}, { compatibility: 'workspace-1.2' })
```
这会改变未发布工作区的根类型用法,已由本次用户明确确认。之前Canvas等包的类型
授权及VAST初始化授权均不作为此决定的替代依据;仍须实际消费验证才完成04。
没有推送或发布授权。
## 实施结果
根声明恢复实际npm原文(仅忽略换行格式比较);新增/runtime的CJS/ESM类型门面和
共用runtime-api.d.ts,内部Context/Result/配置也引用这份准确定义。两套模式的资源
字段可空性/可写性、必需destroy和Promise已建立正反例。实际安装14组编译检查通过,
包括不启用interop的require导入;完整结果见
[04记录](changes/2026-09-14-PKG-VAST-04-types.md)。真实Google IMA验收仍由05负责。
+14 -2
View File
@@ -1,6 +1,7 @@
# VAST 测试维护
PKG-VAST-03现已落实用户确认的两种初始化模式;公开声明继续由04协调。来源见
PKG-VAST-03已落实用户确认的两种初始化模式;04按独立类型确认提供准确/runtime。
来源见
[实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json)
和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。
@@ -14,6 +15,17 @@ PKG-VAST-03现已落实用户确认的两种初始化模式;公开声明继续
## 文件与执行
PKG-VAST-04新增`yarn test:vast-types`和`yarn test:vast-types-package`。根声明与
实际npm1.0.0原文一致,/runtime与同一JS产物绑定;TS5.9.3/4.3.5、CommonJS、
NodeNext、Bundler、关闭interop共14组实际安装消费通过。安装后强制frozen重装、
成员哈希与解析路径隔离均检查;14条runtime反例逐行断言错误,不能只统计数量。
主项目`yarn typecheck`也接入根/准确消费样例,baseline测试自动发现vast-types。
编辑器CommonJS生成器补齐旧VAST的内联工厂与局部type;当前global回归npm旧类型,
冻结工作区的SDK生成路径仍独立测试。三真实浏览器的Monaco消费生成声明、拒绝
错误输入,并运行主片解码样例均通过;不执行广告SDK。正式VAST运行回归82通过,
类型/编辑器/工程20通过。见[04记录](changes/2026-09-14-PKG-VAST-04-types.md)。
PKG-VAST-07补回工厂`.default`自身别名,不改变初始化决策。当时`yarn test:vast`
65项通过;实际main/legacy加ESM各4项导出通过。117项三浏览器检查通过,其中候选
异步注册场景使用`.default`,其他场景保留直接调用。浏览器仍替换SDK边界,实际
@@ -81,4 +93,4 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
使用精确注入;Node固定Date.now用于同毫秒ID复现,未改变生产时钟。
只有实际VAST外部脚本持续因VPN无法加载时,可按用户授权记录并跳过该网络验证;
它不豁免类型、生命周期或其他包。本次未用该例外。04~06和SDK-07继续开放。
它不豁免类型、生命周期或其他包。本次未用该例外。05/06和SDK-07继续开放。
+7 -2
View File
@@ -44,13 +44,18 @@ package consumer tests still use them. Core retains its existing bundler and
private definitions namespace. Plugins preserve callable/default aliases and
exported type namespaces. Unsupported imports/re-exports and invalid syntax fail.
VAST's `Player` and `PlayerOptions` come from the installed, lockfile-pinned
Historical workspace VAST's `Player` and `PlayerOptions` come from the installed, lockfile-pinned
`@glomex/vast-ima-player` 1.21.2 and `@alugha/ima` 2.1.0 declarations. A dedicated
type-only SDK entry avoids bundling a second copy of the linked workspace core.
SDK types stay in the plugin's module-private definitions namespace, not global
runtime values. The optional Window hook and required callback remain unchanged.
Adjacent generated `artplayer-plugin-vast.LICENSE.txt` preserves upstream notices.
This does not settle VAST's runtime default-behavior compatibility decision.
PKG-VAST-04 restores the published npm1.0.0 root declaration and editor global under
the user's explicit type decision. The CommonJS generator supports its inline factory
and module-private aliases, preserving the old required callback and field types.
Current root generation does not expose the workspace SDK types or Window augmentation;
the historical fixture still verifies that generator path. Accurate module consumers use
VAST's `/runtime` declarations. Real IMA verification remains separate.
The editor loads 22 declarations: core, 20 ecosystem libraries and i18n. The
unreferenced legacy `artplayer-plugin-websr.d.ts` asset is not in the canonical
+11 -5
View File
@@ -121,6 +121,7 @@ function generateCommonJSPluginEditor(source: ts.SourceFile, name: string) {
let exported = false
let global = false
let classExport = false
let inlineFactory = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(ts.isStringLiteral(node.moduleSpecifier) && node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported CommonJS plugin editor import')
@@ -142,10 +143,14 @@ function generateCommonJSPluginEditor(source: ts.SourceFile, name: string) {
const definition = definitions[0]
assert(definition && ts.isIdentifier(definition.name) && definition.type)
assert.equal(definition.name.text, name)
assert(ts.isTypeReferenceNode(definition.type) && ts.isQualifiedName(definition.type.typeName)
&& ts.isIdentifier(definition.type.typeName.left) && definition.type.typeName.left.text === name && definition.type.typeName.right.text === 'Factory', 'Editor export must use its public Factory interface')
inlineFactory = ts.isFunctionTypeNode(definition.type)
assert(inlineFactory || (ts.isTypeReferenceNode(definition.type) && ts.isQualifiedName(definition.type.typeName)
&& ts.isIdentifier(definition.type.typeName.left) && definition.type.typeName.left.text === name && definition.type.typeName.right.text === 'Factory'), 'Editor export must use its public Factory interface or an inline function type')
callable = true
}
else if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) {
assert(!node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword), 'CommonJS local types must remain private')
}
else if (ts.isExportAssignment(node)) {
assert(node.isExportEquals && ts.isIdentifier(node.expression) && node.expression.text === name, 'Unexpected CommonJS plugin export')
exported = true
@@ -159,10 +164,11 @@ function generateCommonJSPluginEditor(source: ts.SourceFile, name: string) {
}
statements.push(node)
}
assert(namespace && callable && exported && global, 'Incomplete CommonJS plugin editor declaration')
assert((namespace || inlineFactory) && callable && exported && global, 'Incomplete CommonJS plugin editor declaration')
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
const bridge = classExport ? '' : '/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n'
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n${bridge}${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
const bridge = classExport || !namespace ? '' : '/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n'
const historical = inlineFactory ? '/* eslint-disable ts/no-use-before-define, ts/consistent-type-definitions -- Preserve historical export ordering and private aliases. */\n' : ''
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n${bridge}${historical}${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
export function checkPluginEditorDeclaration(code: string, core: string, consumer = '', compiler: typeof ts = ts) {
+2
View File
@@ -74,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 fixture of ['test/types/vast-runtime.ts', 'refactor/fixtures/consumers/vast-published.ts'])
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `VAST consumer failed: TS ${compiler.version} ${mode}`)
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']) {
+22 -4
View File
@@ -1,6 +1,6 @@
import { expect, test } from './fixtures.js'
test('docs Monaco worker consumes generated core types and runs a typed player example', async ({ page }) => {
test('docs Monaco worker consumes generated core and published VAST types and runs a typed player example', async ({ page }, testInfo) => {
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
const result = await page.evaluate(async () => {
@@ -12,23 +12,36 @@ test('docs Monaco worker consumes generated core types and runs a typed player e
const source = await (await fetch('/assets/ts/artplayer.d.ts')).text()
const libraryUri = 'file:///artplayer.d.ts'
const library = types.typescriptDefaults.addExtraLib(source, libraryUri)
const vastSource = await (await fetch('/assets/ts/artplayer-plugin-vast.d.ts')).text()
const vastLibraryUri = 'file:///artplayer-plugin-vast.d.ts'
const vastLibrary = types.typescriptDefaults.addExtraLib(vastSource, vastLibraryUri)
const model = api.editor.createModel(`
const editorOption: Artplayer.OptionInput = { container: '.player', url: '/test/pattern.mp4', muted: true }
const editorArt: Artplayer = new Artplayer(editorOption)
const editorEvents: Artplayer.Emitter<{ value: [number] }> = new Artplayer.Emitter()
editorEvents.emit('value', 1)
const editorVastCallback: Parameters<typeof artplayerPluginVast>[0] = ({id, $container, imaPlayer}) => {
const identifier: string = id
const container: HTMLDivElement = $container
imaPlayer.addEventListener('AdStarted', () => {})
void [identifier, container]
}
const editorVastFactory: typeof artplayerPluginVast = (_callback) => (_art) => ({name: 'artplayerPluginVast'})
`, 'typescript', api.Uri.parse('file:///consumer.ts'))
const invalid = api.editor.createModel('new Artplayer({ container: 123, url: "" })', 'typescript', api.Uri.parse('file:///invalid.ts'))
const vastInvalid = api.editor.createModel('artplayerPluginVast();\nartplayerPluginVast(({id}) => { const wrong: number = id })', 'typescript', api.Uri.parse('file:///vast-invalid.ts'))
try {
const getWorker = await types.getTypeScriptWorker()
const worker = await getWorker(model.uri, invalid.uri)
const worker = await getWorker(model.uri, invalid.uri, vastInvalid.uri)
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
const declaration = await worker.getSemanticDiagnostics(libraryUri)
const vastDeclaration = await worker.getSemanticDiagnostics(vastLibraryUri)
const vastBad = (await worker.getSemanticDiagnostics(vastInvalid.uri.toString())).map(item => item.code)
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
const output = await worker.getEmitOutput(model.uri.toString())
if (syntax.length || semantic.length || declaration.length)
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), ready: false }
return { syntax, semantic, declaration, vastDeclaration, vastBad, vastSource, invalid: bad.map(item => item.code), ready: false }
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
if (!script)
throw new Error('Monaco did not emit the typed example')
@@ -37,7 +50,7 @@ editorEvents.emit('value', 1)
try {
if (!art.isReady)
await new Promise(resolve => art.on('ready', resolve))
return { syntax, semantic, declaration, invalid: bad.map(item => item.code), ready: art.isReady && art.video.videoWidth > 0 }
return { syntax, semantic, declaration, vastDeclaration, vastBad, vastSource, invalid: bad.map(item => item.code), ready: art.isReady && art.video.videoWidth > 0 }
}
finally {
art.destroy()
@@ -46,12 +59,17 @@ editorEvents.emit('value', 1)
finally {
model.dispose()
invalid.dispose()
vastInvalid.dispose()
library.dispose()
vastLibrary.dispose()
}
})
expect(result.syntax).toEqual([])
expect(result.semantic).toEqual([])
expect(result.declaration).toEqual([])
expect(result.vastDeclaration).toEqual([])
expect(result.vastBad).toEqual([2554, 2322])
await testInfo.attach('vast-editor-declaration', { contentType: 'text/plain', body: result.vastSource })
expect(result.invalid.length).toBeGreaterThan(0)
expect(result.ready).toBe(true)
})
+6 -2
View File
@@ -4,6 +4,7 @@ import fs from 'node:fs'
import { test } from 'node:test'
import { ESLint } from 'eslint'
import compat from 'typescript-compat'
import { verifyVastContract } from '../refactor/scripts/vast-contract.mjs'
import { vastSdkDeclarations } from '../scripts/editor-declarations/dependencies.ts'
import { editorLibUris, generateEditorDeclarations } from '../scripts/editor-declarations/generate.ts'
import { checkStandaloneDeclarations } from '../scripts/editor-declarations/validation.ts'
@@ -24,11 +25,14 @@ test('editor declarations are reproducible, standalone and preserve constructor/
assert.throws(() => asGlobalDeclaration('export { default } from "./missing"', 'Artplayer'), /unresolved exports/)
})
test('Chapter and VAST editor consumers preserve SDK types, optional Window hook and named results', () => {
test('Chapter and historical workspace VAST editor consumers preserve SDK types, optional Window hook and named results', async () => {
const contract = await verifyVastContract()
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const generated = new Map([['artplayer.d.ts', core]])
for (const [suffix, global] of [['chapter', 'artplayerPluginChapter'], ['vast', 'artplayerPluginVast']]) {
const source = fs.readFileSync(`packages/artplayer-plugin-${suffix}/types/artplayer-plugin-${suffix}.d.ts`, 'utf8')
const source = suffix === 'vast'
? contract.sources.get('packages/artplayer-plugin-vast/types/artplayer-plugin-vast.d.ts')
: fs.readFileSync(`packages/artplayer-plugin-${suffix}/types/artplayer-plugin-${suffix}.d.ts`, 'utf8')
const old = `${source.replace(/^import.*$/gim, '')}\nexport = ${global};\nexport as namespace ${global};\n`
const diagnostics = checkPluginEditorDeclaration(old, core)
assert(diagnostics.some(item => item.code === 2309))
+83
View File
@@ -0,0 +1,83 @@
import type { google, ImaSdk } from '@alugha/ima'
import type { Player, PlayerOptions } from '@glomex/vast-ima-player'
import type Artplayer from 'artplayer'
import type { ArtplayerPluginVastInstance, ArtplayerPluginVastOption, CompatibilityOptions, PublishedContext, RuntimeFactory, RuntimeResult, WorkspaceContext } from 'artplayer-plugin-vast/runtime'
import vast from 'artplayer-plugin-vast/runtime'
declare const art: Artplayer
declare const options: CompatibilityOptions
const registration = vast((context) => {
const player: Player = context.imaPlayer
const container: HTMLDivElement = context.$container
const id: string = context.id
const sdk: ImaSdk = context.ima
const settings: google.ima.AdsRenderingSettings = context.adsRenderingSettings
const playerOptions: PlayerOptions = context.playerOptions
const self: PublishedContext = context
context.id = id
context.$container = container
context.imaPlayer = player
context.init()?.addEventListener('AdStarted', () => {})
const urlResult: void = context.playUrl('/ad.xml', { adTagUrl: '/override.xml', custom: true })
const responseResult: void = context.playRes('<VAST/>')
// @ts-expect-error SDK methods retain actual vendor signatures.
context.imaPlayer.playAds(42)
// @ts-expect-error SDK settings are typed.
context.adsRenderingSettings.enablePreloading = 'yes'
// @ts-expect-error The original media element must remain an element.
context.$container = 'container'
// @ts-expect-error Live container is read-only in both modes.
context.container = container
// @ts-expect-error Terminal core destruction makes init return null.
const initialized: Player = context.init()
void [player, container, id, sdk, settings, playerOptions, self, urlResult, responseResult, initialized]
})
const pending: Promise<RuntimeResult> = registration(art)
// @ts-expect-error Registration is asynchronous.
const synchronous: RuntimeResult = registration(art)
pending.then((result) => {
const name: 'artplayerPluginVast' = result.name
const disposed: void = result.destroy()
const migrated: ArtplayerPluginVastInstance = result
void [name, disposed, migrated]
})
vast()
vast.default()
vast(undefined, { compatibility: 'workspace-1.2' })
vast(async (context) => {
const self: WorkspaceContext = context
const player: Player | null = context.imaPlayer
const container: HTMLDivElement | null = context.$container
const id: string | null = context.id
context.playerOptions.autoResize = false
context.init()?.addEventListener('AdStarted', () => {})
// @ts-expect-error Workspace resource getters may be null.
const nonNull: Player = context.imaPlayer
// @ts-expect-error Workspace fields are getters without setters.
context.imaPlayer = player
// @ts-expect-error Workspace identifiers are getters without setters.
context.id = 'new-id'
// @ts-expect-error Request input remains a string.
context.playUrl(123)
void [self, player, container, id, nonNull]
}, { compatibility: 'workspace-1.2' })
vast((context) => {
const player: Player | null = context.imaPlayer
// @ts-expect-error Dynamic mode cannot promise eager allocation.
const eager: Player = context.imaPlayer
void [player, eager]
}, options)
const callback: ArtplayerPluginVastOption = context => context.init()
vast(callback, { compatibility: 'workspace-1.2' })
const factory: RuntimeFactory = vast.default
// @ts-expect-error Unknown compatibility mode is rejected at runtime.
vast(undefined, { compatibility: 'wrong' })
// @ts-expect-error Callback must be callable when supplied.
vast(1)
// @ts-expect-error A known workspace option cannot provide eager-only context.
vast((context: PublishedContext) => context.id, { compatibility: 'workspace-1.2' })
void [factory, synchronous]