From aca4bc1cd26f3327236cc9801b331c40ac93e385 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 00:05:14 +0800 Subject: [PATCH] refactor(mask): [PKG-MASK-04] migrate strict types and preserve public consumers --- .../ts/artplayer-plugin-danmuku-mask.d.ts | 45 +- package.json | 3 +- .../artplayer-plugin-danmuku-mask/.npmignore | 3 +- .../ARCHITECTURE.md | 58 +- .../package.json | 7 + .../src/{config.js => config.ts} | 4 +- .../src/{controller.js => controller.ts} | 41 +- .../src/{index.js => index.ts} | 5 +- .../src/{output.js => output.ts} | 13 +- .../src/sdk-ambient.d.ts | 3 + .../src/{sdk.js => sdk.ts} | 16 +- .../src/types.ts | 50 + .../tsconfig.json | 5 + .../types/README.md | 39 + .../danmuku-mask-types-validation.json | 1308 +++++++++++++++++ .../changes/2026-09-14-PKG-MASK-04-types.md | 53 + .../fixtures/implementation/danmuku-mask.ts | 6 + refactor/impact-policy.json | 2 +- refactor/plan.md | 5 +- refactor/progress.md | 9 + refactor/risks.json | 2 +- refactor/scripts/danmuku-mask-consumer.mjs | 22 + .../scripts/danmuku-mask-package-types.mjs | 108 ++ refactor/scripts/danmuku-mask-types.test.mjs | 57 + refactor/tasks.json | 7 +- refactor/third-party.json | 6 +- scripts/build-ts.js | 2 +- test/README.md | 4 + test/helpers/danmuku-mask-candidate.js | 3 +- test/types/danmuku-mask-public.ts | 34 + 30 files changed, 1848 insertions(+), 72 deletions(-) rename packages/artplayer-plugin-danmuku-mask/src/{config.js => config.ts} (83%) rename packages/artplayer-plugin-danmuku-mask/src/{controller.js => controller.ts} (78%) rename packages/artplayer-plugin-danmuku-mask/src/{index.js => index.ts} (75%) rename packages/artplayer-plugin-danmuku-mask/src/{output.js => output.ts} (70%) create mode 100644 packages/artplayer-plugin-danmuku-mask/src/sdk-ambient.d.ts rename packages/artplayer-plugin-danmuku-mask/src/{sdk.js => sdk.ts} (57%) create mode 100644 packages/artplayer-plugin-danmuku-mask/src/types.ts create mode 100644 packages/artplayer-plugin-danmuku-mask/tsconfig.json create mode 100644 packages/artplayer-plugin-danmuku-mask/types/README.md create mode 100644 refactor/baselines/danmuku-mask-types-validation.json create mode 100644 refactor/changes/2026-09-14-PKG-MASK-04-types.md create mode 100644 refactor/fixtures/implementation/danmuku-mask.ts create mode 100644 refactor/scripts/danmuku-mask-consumer.mjs create mode 100644 refactor/scripts/danmuku-mask-package-types.mjs create mode 100644 refactor/scripts/danmuku-mask-types.test.mjs create mode 100644 test/types/danmuku-mask-public.ts diff --git a/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts b/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts index 68515f8f8..60e301109 100644 --- a/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts +++ b/docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts @@ -1,25 +1,26 @@ -interface Option { - solutionPath?: string - modelSelection?: number - smoothSegmentation?: boolean - minDetectionConfidence?: number - minTrackingConfidence?: number - selfieMode?: boolean - drawContour?: boolean - foregroundThreshold?: number - opacity?: number - maskBlurAmount?: number +// Generated from the package public declaration by yarn build:ts. Do not edit. +/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */ +declare namespace artplayerPluginDanmukuMaskDefinitions { + export interface Option { + solutionPath?: string + modelSelection?: number + smoothSegmentation?: boolean + minDetectionConfidence?: number + minTrackingConfidence?: number + selfieMode?: boolean + drawContour?: boolean + foregroundThreshold?: number + opacity?: number + maskBlurAmount?: number + } + export interface Result { + name: 'artplayerPluginDanmukuMask' + start: () => Promise + stop: () => void + } + export const artplayerPluginDanmukuMask: (option?: Option) => (art: Artplayer) => Result } - -interface Result { - name: 'artplayerPluginDanmukuMask' - start: () => Promise - stop: () => void -} - -declare const artplayerPluginDanmukuMask: (option?: Option) => (art: Artplayer) => Result - -export default artplayerPluginDanmukuMask - +declare const artplayerPluginDanmukuMask: typeof artplayerPluginDanmukuMaskDefinitions.artplayerPluginDanmukuMask +declare namespace artplayerPluginDanmukuMask { } export = artplayerPluginDanmukuMask export as namespace artplayerPluginDanmukuMask; diff --git a/package.json b/package.json index 659e54949..b975ecd08 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ "test:danmuku-mask": "node --test test/danmuku-mask-lifecycle.test.js test/danmuku-mask-failures.test.js refactor/scripts/danmuku-mask-contract.test.mjs", "test:danmuku": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js refactor/scripts/danmuku-contract.test.mjs", "test:danmuku-types": "node --test refactor/scripts/danmuku-types.test.mjs", - "test:danmuku-types-package": "node refactor/scripts/danmuku-package-types.mjs" + "test:danmuku-types-package": "node refactor/scripts/danmuku-package-types.mjs", + "test:danmuku-mask-types-package": "node refactor/scripts/danmuku-mask-package-types.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-plugin-danmuku-mask/.npmignore b/packages/artplayer-plugin-danmuku-mask/.npmignore index 4439c6874..2b272d565 100644 --- a/packages/artplayer-plugin-danmuku-mask/.npmignore +++ b/packages/artplayer-plugin-danmuku-mask/.npmignore @@ -2,4 +2,5 @@ node_modules .DS_Store yarn.lock package-lock.json -src \ No newline at end of file +src +tsconfig.json diff --git a/packages/artplayer-plugin-danmuku-mask/ARCHITECTURE.md b/packages/artplayer-plugin-danmuku-mask/ARCHITECTURE.md index aec041aee..2fc9d0b38 100644 --- a/packages/artplayer-plugin-danmuku-mask/ARCHITECTURE.md +++ b/packages/artplayer-plugin-danmuku-mask/ARCHITECTURE.md @@ -9,15 +9,47 @@ The compatibility baseline is the actual npm 1.1.0 package and the earlier | Module | Responsibility | | --- | --- | -| `src/index.js` | Preserve synchronous registration, capture core template nodes before option getters, expose named start/stop closures | -| `src/config.js` | Snapshot the original option defaults without changing OR/undefined semantics | -| `src/sdk.js` | Existing TF backend selection, MediaPipe adapter configuration, mask calls and model disposal outlet | -| `src/controller.js` | One active run, initialization/inference serialization, cancellation, RAF ownership, ready/destroy subscriptions | -| `src/output.js` | Private canvas/context, unchanged binary mask colors and threshold conversion, guarded maskImage commit, canvas release | +| `src/index.ts` | Preserve synchronous registration, capture core template nodes before option getters, expose named start/stop closures | +| `src/config.ts` | Snapshot the original option defaults without changing OR/undefined semantics | +| `src/sdk.ts` | Existing TF backend selection, MediaPipe adapter configuration, mask calls and model disposal outlet | +| `src/controller.ts` | One active run, initialization/inference serialization, cancellation, RAF ownership, ready/destroy subscriptions | +| `src/output.ts` | Private canvas/context, unchanged binary mask colors and threshold conversion, guarded maskImage commit, canvas release | +| `src/types.ts` | Internal host, normalized options, exact legacy SDK arguments, canvas resources and nullable run state | +| `src/sdk-ambient.d.ts` | Declaration-only import of the SDK's official Long namespace dependency | -These modules remain JavaScript for PKG-MASK-03. Full owned-source TypeScript and -public declaration work belongs to PKG-MASK-04; this task does not claim it is -finished. Public declarations are unchanged. +The owned modules use strict TypeScript. The entry depends on config/controller; +controller depends on SDK/output; output delegates mask operations to SDK. The +shared contracts contain only types and do not add runtime dependencies. The +internal host accepts the actual core declaration in +`refactor/fixtures/implementation/danmuku-mask.ts`; it needs only core template +nodes, destroy state and ready/destroy subscription methods. Public declarations +remain a separate compatibility surface and never import these source types. + +Controller fields use `declare` to retain the existing own-property initialization +order. Its limited non-null assertions describe lifecycle invariants: tick runs +only after model/output initialization, disposal waits for busy frame work, and +the public start promise is assigned before the first initialization continuation. +The cancellation resolver is assigned by the synchronous Promise executor. +Output byte indexing relies on native ImageData's complete RGBA pixels. The +backend error's message assertion preserves the old property read, including +its behavior for non-Error rejections; it does not normalize thrown values. + +The SDK config uses the official MediaPipe config intersected with the exact +legacy extra keys. `satisfies` checks those fields, and the subsequent assertion +prevents the SDK union's excess-property check from deleting compatibility data. +No SDK option interpretation changes. TF 4.22.0 `dist/hash_util.d.ts:2-3` references +`Long` without an import. The local declaration-only bridge loads its existing +official `@types/long` 4.0.2 UMD namespace. This package imports the TF declarations +directly; it therefore needs the bridge under the root's explicit `types: []`. +Keep `skipLibCheck: false`, do not rewrite Long or add a runtime import for it. +The package tsconfig and all source modules stay outside the packed artifact. + +Public factory declarations retain the actual npm 1.0.0/1.1.0 shape. The exact +`typesVersions` legacy mapping fixes old Node-resolution consumers without +changing the root factory. See `types/README.md` for private types, namespace +limits and the semantic online-editor generator. Strict positive/negative +factory replacement and return-type cases run in the baseline suite; installed +consumers separately check packed contents and historical diagnostics. Mask captures the core's video and `.art-danmuku` layer. The core template provides that layer before plugins register; Danmuku does not create it. @@ -104,15 +136,19 @@ make a disposal test appear stronger than its real API. From the repository root with pinned Node and Yarn: ```sh +node node_modules/typescript/bin/tsc -p packages/artplayer-plugin-danmuku-mask/tsconfig.json node --test test/danmuku-mask-lifecycle.test.js yarn test:danmuku-mask +yarn test:danmuku-mask-types-package +yarn build:ts artplayer-plugin-danmuku-mask node --test test/danmuku-mask-failures.test.js refactor/scripts/danmuku-mask-contract.test.mjs node node_modules/eslint/bin/eslint.js packages/artplayer-plugin-danmuku-mask/src test/helpers/danmuku-mask-candidate.js test/danmuku-mask-lifecycle.test.js yarn build artplayer-plugin-danmuku-mask yarn dev artplayer-plugin-danmuku-mask ``` -The candidate helper bundles current owned modules and substitutes the SDK +The candidate helper requires exactly one JS/TS source entry, bundles current +owned modules and substitutes the SDK imports with controlled implementations. Its fake RAF/video/canvas test cancellation and output ownership; they do not prove real model quality, canvas pixels, CSS alignment, CORS, browser scheduling, GPU memory or WASM cleanup. @@ -129,5 +165,5 @@ by its original player. Native shared TensorFlow backend state remains unverifie Native acceptance still needs actual local model loading, new/old cores, real Danmuku composition, pause/seek/source/layout changes, multiple players, failure recovery and post-destroy resources. The default unversioned solutionPath can -load assets independently of Yarn's SDK resolution. PKG-MASK-04/05/06 remain -separate source/type, real combination and distribution/release stages. +load assets independently of Yarn's SDK resolution. PKG-MASK-05/06 retain the +real combination and distribution/release gates after source/type migration. diff --git a/packages/artplayer-plugin-danmuku-mask/package.json b/packages/artplayer-plugin-danmuku-mask/package.json index 5991c46fc..44735d2b4 100644 --- a/packages/artplayer-plugin-danmuku-mask/package.json +++ b/packages/artplayer-plugin-danmuku-mask/package.json @@ -41,5 +41,12 @@ "@tensorflow/tfjs-backend-webgl": "^4.21.0", "@tensorflow/tfjs-converter": "^4.21.0", "@tensorflow/tfjs-core": "^4.21.0" + }, + "typesVersions": { + "*": { + "legacy": [ + "types/artplayer-plugin-danmuku-mask.d.ts" + ] + } } } diff --git a/packages/artplayer-plugin-danmuku-mask/src/config.js b/packages/artplayer-plugin-danmuku-mask/src/config.ts similarity index 83% rename from packages/artplayer-plugin-danmuku-mask/src/config.js rename to packages/artplayer-plugin-danmuku-mask/src/config.ts index 39350e6a0..60601e449 100644 --- a/packages/artplayer-plugin-danmuku-mask/src/config.js +++ b/packages/artplayer-plugin-danmuku-mask/src/config.ts @@ -1,4 +1,6 @@ -export default function maskConfig(option) { +import type { MaskConfig, MaskOptions } from './types' + +export default function maskConfig(option: MaskOptions): MaskConfig { return { solutionPath: option.solutionPath || 'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation', modelSelection: option.modelSelection || 1, diff --git a/packages/artplayer-plugin-danmuku-mask/src/controller.js b/packages/artplayer-plugin-danmuku-mask/src/controller.ts similarity index 78% rename from packages/artplayer-plugin-danmuku-mask/src/controller.js rename to packages/artplayer-plugin-danmuku-mask/src/controller.ts index 23548ab2d..9d94bcd14 100644 --- a/packages/artplayer-plugin-danmuku-mask/src/controller.js +++ b/packages/artplayer-plugin-danmuku-mask/src/controller.ts @@ -1,8 +1,18 @@ +import type { MaskConfig, MaskHost, MaskNodes, MaskRun } from './types' import { createOutput, releaseOutput, renderMask } from './output' import { loadSegmenter, releaseSegmenter } from './sdk' export default class MaskController { - constructor(art, config, { $video, $danmuku }) { + declare art: MaskHost + declare config: MaskConfig + declare video: HTMLVideoElement + declare layer: HTMLElement + declare closed: boolean + declare run: MaskRun | null + declare tail: Promise + declare ready: () => void + + constructor(art: MaskHost, config: MaskConfig, { $video, $danmuku }: MaskNodes) { this.art = art this.config = config this.video = $video @@ -38,11 +48,11 @@ export default class MaskController { } } - active(run) { + active(run: MaskRun): boolean { return run === this.run && run.running && !this.closed && !this.art.isDestroy } - release(run) { + release(run: MaskRun): Promise { if (run.releasing) return run.releasing const segmenter = run.segmenter @@ -63,7 +73,7 @@ export default class MaskController { return run.releasing } - schedule(run) { + schedule(run: MaskRun): void { if (!this.active(run) || run.frame !== null || run.busy) return run.frame = requestAnimationFrame(() => { @@ -72,7 +82,7 @@ export default class MaskController { }) } - tick(run) { + tick(run: MaskRun): void { if (!this.active(run) || run.busy) return const video = this.video @@ -86,7 +96,8 @@ export default class MaskController { // Reserve the work slot before SDK code can synchronously reenter start/stop. await Promise.resolve() if (this.active(run)) - await renderMask(run.output, video, this.layer, run.segmenter, this.config, () => this.active(run)) + // Only initialized runs reach tick; release waits for this busy work to settle. + await renderMask(run.output!, video, this.layer, run.segmenter!, this.config, () => this.active(run)) } catch (error) { if (this.active(run)) @@ -103,14 +114,16 @@ export default class MaskController { this.tail = work.catch(error => console.warn('Failed to release danmuku mask resources:', error)) } - async start() { + async start(): Promise { if (this.closed || this.art.isDestroy) return + // The start promise is assigned before initialization resumes after its first await. if (this.run?.running) - return this.run.started - let cancel - const cancelled = new Promise(resolve => cancel = resolve) - const run = { running: true, initializing: true, busy: false, frame: null, segmenter: null, output: null, cancel, started: null } + return this.run.started! + // Promise executors install their resolver synchronously. + let cancel!: () => void + const cancelled = new Promise(resolve => cancel = resolve) + const run: MaskRun = { running: true, initializing: true, busy: false, frame: null, segmenter: null, output: null, cancel, started: null } const previous = this.tail this.run = run const initialize = (async () => { @@ -149,7 +162,7 @@ export default class MaskController { return run.started } - halt() { + halt(): void { const run = this.run if (run) { run.running = false @@ -163,13 +176,13 @@ export default class MaskController { } } - stop() { + stop(): void { this.halt() if (this.layer?.style) this.layer.style.maskImage = 'none' } - destroy() { + destroy(): void { if (this.closed) return this.closed = true diff --git a/packages/artplayer-plugin-danmuku-mask/src/index.js b/packages/artplayer-plugin-danmuku-mask/src/index.ts similarity index 75% rename from packages/artplayer-plugin-danmuku-mask/src/index.js rename to packages/artplayer-plugin-danmuku-mask/src/index.ts index 7d9e9485f..68c1a7e85 100644 --- a/packages/artplayer-plugin-danmuku-mask/src/index.js +++ b/packages/artplayer-plugin-danmuku-mask/src/index.ts @@ -1,8 +1,9 @@ +import type { MaskHost, MaskOptions } from './types' import maskConfig from './config' import MaskController from './controller' -export default function artplayerPluginDanmukuMask(option = {}) { - return (art) => { +export default function artplayerPluginDanmukuMask(option: MaskOptions = {}) { + return (art: MaskHost) => { const { template: { $video, $danmuku } } = art const controller = new MaskController(art, maskConfig(option), { $video, $danmuku }) async function startSegmentation() { diff --git a/packages/artplayer-plugin-danmuku-mask/src/output.js b/packages/artplayer-plugin-danmuku-mask/src/output.ts similarity index 70% rename from packages/artplayer-plugin-danmuku-mask/src/output.js rename to packages/artplayer-plugin-danmuku-mask/src/output.ts index 88d1d4596..452af3a23 100644 --- a/packages/artplayer-plugin-danmuku-mask/src/output.js +++ b/packages/artplayer-plugin-danmuku-mask/src/output.ts @@ -1,15 +1,18 @@ +import type { BodySegmenter } from '@tensorflow-models/body-segmentation' +import type { Active, MaskConfig, MaskOutput } from './types' import { drawMask, toBinaryMask } from './sdk' -export function makeWhiteTransparent(imageData) { +export function makeWhiteTransparent(imageData: ImageData): ImageData { const data = imageData.data + // Native ImageData stores complete four-byte RGBA pixels. for (let i = 0; i < data.length; i += 4) { - if (data[i] > 250 && data[i + 1] > 250 && data[i + 2] > 250) + if (data[i]! > 250 && data[i + 1]! > 250 && data[i + 2]! > 250) data[i + 3] = 0 } return imageData } -export function createOutput(layer) { +export function createOutput(layer: HTMLElement): MaskOutput { const canvas = document.createElement('canvas') try { const ctx = canvas.getContext('2d') @@ -31,7 +34,7 @@ export function createOutput(layer) { } } -export async function renderMask(output, video, layer, segmenter, config, active) { +export async function renderMask(output: MaskOutput, video: HTMLVideoElement, layer: HTMLElement, segmenter: BodySegmenter, config: MaskConfig, active: Active): Promise { const { canvas, ctx } = output canvas.width = video.videoWidth canvas.height = video.videoHeight @@ -57,7 +60,7 @@ export async function renderMask(output, video, layer, segmenter, config, active layer.style.maskImage = `url(${url})` } -export function releaseOutput(output) { +export function releaseOutput(output: MaskOutput | null): void { if (output) { output.canvas.width = 0 output.canvas.height = 0 diff --git a/packages/artplayer-plugin-danmuku-mask/src/sdk-ambient.d.ts b/packages/artplayer-plugin-danmuku-mask/src/sdk-ambient.d.ts new file mode 100644 index 000000000..28ff86475 --- /dev/null +++ b/packages/artplayer-plugin-danmuku-mask/src/sdk-ambient.d.ts @@ -0,0 +1,3 @@ +// TF 4.22.0 hash_util.d.ts uses global Long without importing its own dependency. +// Load the official UMD namespace for this internal check; emit no runtime import. +import 'long' diff --git a/packages/artplayer-plugin-danmuku-mask/src/sdk.js b/packages/artplayer-plugin-danmuku-mask/src/sdk.ts similarity index 57% rename from packages/artplayer-plugin-danmuku-mask/src/sdk.js rename to packages/artplayer-plugin-danmuku-mask/src/sdk.ts index 8ace4bca7..5e30bb7cf 100644 --- a/packages/artplayer-plugin-danmuku-mask/src/sdk.js +++ b/packages/artplayer-plugin-danmuku-mask/src/sdk.ts @@ -1,21 +1,25 @@ +import type { BodySegmenter } from '@tensorflow-models/body-segmentation' +import type { Active, MaskConfig, SegmenterConfig } from './types' import * as bodySegmentation from '@tensorflow-models/body-segmentation' import * as tf from '@tensorflow/tfjs-core' import '@tensorflow/tfjs-backend-webgl' import '@tensorflow/tfjs-backend-cpu' -export async function loadSegmenter(config, active) { +export async function loadSegmenter(config: MaskConfig, active: Active): Promise { try { await tf.setBackend('webgl') } catch (error) { if (!active()) return null - console.warn('WebGL backend not available, falling back to CPU', error.message) + // Keep the historical message property read, including non-Error rejection behavior. + console.warn('WebGL backend not available, falling back to CPU', (error as { message: unknown }).message) await tf.setBackend('cpu') } if (!active()) return null try { + // Validate the full legacy object without dropping keys absent from the SDK config union. return await bodySegmentation.createSegmenter(bodySegmentation.SupportedModels.MediaPipeSelfieSegmentation, { runtime: 'mediapipe', modelType: 'general', @@ -25,7 +29,7 @@ export async function loadSegmenter(config, active) { minDetectionConfidence: config.minDetectionConfidence, minTrackingConfidence: config.minTrackingConfidence, selfieMode: config.selfieMode, - }) + } satisfies SegmenterConfig as SegmenterConfig) } catch (error) { if (active()) @@ -34,7 +38,7 @@ export async function loadSegmenter(config, active) { } } -export async function releaseSegmenter(segmenter) { +export async function releaseSegmenter(segmenter: BodySegmenter): Promise { try { await segmenter.dispose() } @@ -43,5 +47,5 @@ export async function releaseSegmenter(segmenter) { } } -export const toBinaryMask = (...args) => bodySegmentation.toBinaryMask(...args) -export const drawMask = (...args) => bodySegmentation.drawMask(...args) +export const toBinaryMask = (...args: Parameters) => bodySegmentation.toBinaryMask(...args) +export const drawMask = (...args: Parameters) => bodySegmentation.drawMask(...args) diff --git a/packages/artplayer-plugin-danmuku-mask/src/types.ts b/packages/artplayer-plugin-danmuku-mask/src/types.ts new file mode 100644 index 000000000..48d208975 --- /dev/null +++ b/packages/artplayer-plugin-danmuku-mask/src/types.ts @@ -0,0 +1,50 @@ +import type { BodySegmenter, MediaPipeSelfieSegmentationMediaPipeModelConfig } from '@tensorflow-models/body-segmentation' + +// Owned implementation contracts; the historical public entrypoint is maintained separately. +export interface MaskOptions { + solutionPath?: string + modelSelection?: number + smoothSegmentation?: boolean + minDetectionConfidence?: number + minTrackingConfidence?: number + selfieMode?: boolean + drawContour?: boolean + foregroundThreshold?: number + opacity?: number + maskBlurAmount?: number +} + +export type MaskConfig = Required +export type Active = () => boolean + +// These legacy extra keys are passed through even where the current adapter ignores them. +export type SegmenterConfig = MediaPipeSelfieSegmentationMediaPipeModelConfig & Pick + +export interface MaskNodes { + $video: HTMLVideoElement + $danmuku: HTMLElement +} + +export interface MaskHost { + isDestroy: boolean + template: MaskNodes + on: (event: 'ready' | 'destroy', callback: () => void) => unknown + off: (event: 'ready' | 'destroy', callback: () => void) => unknown +} + +export interface MaskOutput { + canvas: HTMLCanvasElement + ctx: CanvasRenderingContext2D +} + +export interface MaskRun { + running: boolean + initializing: boolean + busy: boolean + frame: number | null + segmenter: BodySegmenter | null + output: MaskOutput | null + cancel: () => void + started: Promise | null + releasing?: Promise +} diff --git a/packages/artplayer-plugin-danmuku-mask/tsconfig.json b/packages/artplayer-plugin-danmuku-mask/tsconfig.json new file mode 100644 index 000000000..79f44ac59 --- /dev/null +++ b/packages/artplayer-plugin-danmuku-mask/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "allowJs": false }, + "include": ["src/**/*.ts", "../../refactor/fixtures/implementation/danmuku-mask.ts"] +} diff --git a/packages/artplayer-plugin-danmuku-mask/types/README.md b/packages/artplayer-plugin-danmuku-mask/types/README.md new file mode 100644 index 000000000..c0f5f6113 --- /dev/null +++ b/packages/artplayer-plugin-danmuku-mask/types/README.md @@ -0,0 +1,39 @@ +# Public type maintenance + +`artplayer-plugin-danmuku-mask.d.ts` is the authored public declaration for both +the root and `/legacy` entrypoints. Both actual npm 1.0.0 and 1.1.0 shipped this +same factory shape. Keep its optional argument, synchronous registration, +literal result name, `start(): Promise` and `stop(): void`; ordinary +replacement functions and `Parameters`/`ReturnType` extraction are supported. + +The declaration already describes these runtime return values correctly. This +migration does not add a second `/runtime` entrypoint or expose SDK model types. +Internal run, canvas and SDK types live under `src/`; they are not the consumer +contract and are excluded from the package archive. + +The manifest's exact `typesVersions` mapping makes `/legacy` resolve with older +Node module resolution, including the supported TypeScript 4.3.5 compiler. The +published packages lacked this mapping; their root declaration remains unchanged. +Modern resolution uses the existing `exports` declaration targets. + +The package retains its historical NodeNext ESM declaration namespace behavior. +Do not infer a runtime `factory.default` property from that type-only namespace: +the latest CommonJS runtime exports a function without that property. The npm +1.0.0 CommonJS wrapper had a default export object, while 1.1.0 already exports +the callable directly. These are recorded historical differences. + +Numeric option types do not redefine runtime defaults. Zero-valued options still +follow the original OR defaults; the installed SDK uses `modelType: general` +and ignores several additional forwarded options. See `../ARCHITECTURE.md` before +changing their effects or claiming a specific inference backend. + +Run `node --test refactor/scripts/danmuku-mask-types.test.mjs` for strict factory, +argument/result and negative consumer cases. After a normal package build, +`yarn test:danmuku-mask-types-package` checks actual tarballs installed outside the +workspace, old/current compilers, NodeNext/Bundler resolution and the historical +namespace diagnostics. Those checks do not run a model or prove GPU cleanup. + +`yarn build:ts artplayer-plugin-danmuku-mask` generates the standalone online +editor declaration with the semantic generator. This avoids the former mixture +of default and export-assignment syntax. The private Option/Result interfaces +remain private; extract them through `Parameters` and `ReturnType` when needed. diff --git a/refactor/baselines/danmuku-mask-types-validation.json b/refactor/baselines/danmuku-mask-types-validation.json new file mode 100644 index 000000000..4d7b1f33d --- /dev/null +++ b/refactor/baselines/danmuku-mask-types-validation.json @@ -0,0 +1,1308 @@ +{ + "task": "PKG-MASK-04", + "node": "v24.21.0", + "platform": "win32", + "modules": [ + { + "module": "index", + "emittedSha256": "ef242ca5595a638631e5b2bfb4b249fec6e45d7d32c76e08a43398bbbcae9758", + "sourceSha256": "cbd966b983ecdc13bc985c59e723c4b4cd7f8a98e3fa4c7cc12cb88d287c3dca" + }, + { + "module": "config", + "emittedSha256": "ff88ef3d18a6587d6e24751b830d5f18c96a1269265934c7d51f1fe04c8d0b2d", + "sourceSha256": "ef81ea4699dc350df7868e8fb15eed12d8ac3d916910a62de7fb65d88695feac" + }, + { + "module": "controller", + "emittedSha256": "9482971d02b8943301dceeb9d3cc49a5e9a96fcd83ecbc12e0d2e49606947364", + "sourceSha256": "e73fc6693a20640c4e389267b122306141662b227b2f5c7f85427a1bafd30ece" + }, + { + "module": "output", + "emittedSha256": "53f18676aa4e29bd57b2d8a4e67693e89bdc8630a960478a1c4d650efc6548e2", + "sourceSha256": "f3ed5d9858f531485f927797990a4f8c2273143b765a9df4918da5cc12613141" + }, + { + "module": "sdk", + "emittedSha256": "d94af8696d3f3ace347c51f920427b2726868951b6e55ca88ef3dcbcd198c58a", + "sourceSha256": "ebb1df555357c5674352ac3f17774a1d2823d1c7aa524a9a4d044ea27a1287c3" + } + ], + "artifacts": [ + { + "file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.js", + "sha256": "406a8c28207d386cffe1e61dc79afd8a6a9d5cbd1dc8e1661b4febbf48182f55", + "unchangedFrom": "aba125262abbbdcb8225a3dabff05066d403a1a6" + }, + { + "file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.legacy.js", + "sha256": "62d61dcedd3e90fa480589c2f1e3168c97635232c98ffe8c25c7918a2f3c66db", + "unchangedFrom": "aba125262abbbdcb8225a3dabff05066d403a1a6" + }, + { + "file": "packages/artplayer-plugin-danmuku-mask/dist/artplayer-plugin-danmuku-mask.mjs", + "sha256": "51936576383129170125b33d2983858e0841ea507368970f6d154bc9d78583b1", + "unchangedFrom": "aba125262abbbdcb8225a3dabff05066d403a1a6" + } + ], + "tests": { + "file": "refactor/.cache/mask04-final-tests.log", + "sha256": "cf76d7c590d1eb93495f9b328db7c5383d7e6bda22483f52e3e56484b9b89acb", + "passed": 99, + "candidateLifecycle": 36, + "historicalDefects": 54, + "historicalContract": 6, + "publicAndEditorTypeTests": 3, + "failed": 0, + "skipped": 0 + }, + "strict": { + "productionFiles": 404, + "log": "refactor/.cache/mask04-all-types.log", + "sha256": "8364e0c6f7953b50898d769c7147372e2828e7c2987ebca7b1092331c1576ad4", + "packageConfig": "packages/artplayer-plugin-danmuku-mask/tsconfig.json", + "bridge": { + "file": "packages/artplayer-plugin-danmuku-mask/src/sdk-ambient.d.ts", + "dependency": "@types/long", + "version": "4.0.2", + "reason": "TF4.22.0 hash_util declarations use Long without importing it; load the official namespace in a declaration-only input, preserving types:[] and skipLibCheck:false." + } + }, + "editor": { + "file": "docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts", + "sha256": "06c6e938876edcc0ac2c8dcd1e1be4596b0f056685a3523d4c9836f2f2f90362", + "oldSourceSha256": "6fa4a4a4a2e9e0170b79755620f7cdbcef21f05756b702a3b4bdf120c74b5c8d", + "oldEditorDiagnostics": [ + { + "compiler": "5.9.3", + "diagnostics": [ + { + "code": 2309, + "message": "An export assignment cannot be used in a module with other exported elements." + } + ] + }, + { + "compiler": "4.3.5", + "diagnostics": [ + { + "code": 2309, + "message": "An export assignment cannot be used in a module with other exported elements." + } + ] + } + ] + }, + "checks": [ + { + "file": "refactor/.cache/mask04-build.log", + "sha256": "9ac55452785dc3b250768e14d061fef45a002ef8b73efe632a278c055b4fa550" + }, + { + "file": "refactor/.cache/mask04-editor-build.log", + "sha256": "ae37677ef4ae43644693686d3e532acd7c0b537146e9c1f799274928aeab6503" + }, + { + "file": "refactor/.cache/mask04-final-lint.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "installed": { + "task": "PKG-MASK-04", + "node": "v24.21.0", + "packages": [ + { + "name": "artplayer", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\danmuku-mask-package-types-kf2wJS\\artplayer.tgz", + "sha256": "0c0ea52d41575261b836aefac68280733b119c75834ecad6d4bc78edb073f4c1", + "files": { + "package/ARCHITECTURE.md": "bee98fc9d93a3911d58e98ef15f2dbe07e12f2274c444d989094248a22079e46", + "package/README.md": "1e185e65f67fc2081b4182bbc6c4558ea05b6e3fa3acc5dd0c3ac7df1d542b09", + "package/THIRD_PARTY_NOTICES": "b77b5b46b79fa0cda850b0781f78c3b1112c4ed9672fa291956fe8eff53098e7", + "package/dist/artplayer.js": "6dc8ca6d6eb34aa8a056cbc31328efd634530b3c5c60f0014cb7ce719fabc335", + "package/dist/artplayer.legacy.js": "80f74a8a39df4baf64cc00c65d21cd57c011bfcdead0da38eea077fbc67c5c04", + "package/dist/artplayer.mjs": "432e2b0ad92009a09743e020bf7c928c8b8f18a52496622b16259ec9ab20059f", + "package/dist/i18n/ar.js": "bd0bef65f4f7dc3f8ed562904cc6fa9071ff4537fb64c1e0fc3d554141bcd3cb", + "package/dist/i18n/ar.mjs": "59310b7bbeea83bbb7d73aadc5ca82a6ec269c3530d19aa741c94d5c64fc5da8", + "package/dist/i18n/cs.js": "6e2c93daba24c2e48dfa206bcf4f2f2a8a02713b856e404d2e4e4ecd8309ca2f", + "package/dist/i18n/cs.mjs": "c73fba18797d62d7b2581e67fe7340c35fe84510b2250ff1ecaad253d9cabbac", + "package/dist/i18n/es.js": "f4133ac6ea06b69a474b33c4a1809a4066e2376bb9464a3f3aa8330f01f1944a", + "package/dist/i18n/es.mjs": "1390bca3def2be115e811de337df0a0c3f63998e51359f283023246245c552a7", + "package/dist/i18n/fa.js": "8d3ef2e0f0cc0ea480c2167b6fcb87cdf444e90759c3519e8daa139fec576da4", + "package/dist/i18n/fa.mjs": "ab441e8a2d79043b29ceca87c783bf06874bb9d3a149c1604bc167eb481be2a9", + "package/dist/i18n/fr.js": "89dc4194a6c0a962b9ce756b52c90401c1acf3b159f265f3bc0d7d6d7d987f03", + "package/dist/i18n/fr.mjs": "99491878430fb7b6f2b7bf93da0588a831704b412e01116f23ba1d9d45863816", + "package/dist/i18n/id.js": "4c6dc86db741bcc2857df55a9d037e82e91d375fa6fd22e0562809ff621b92db", + "package/dist/i18n/id.mjs": "383e5803a6b6686c6de4e867770241de75b6f517dcca2bdedc6f2004bf6e1fb3", + "package/dist/i18n/pl.js": "3a38828b1c3d719c0aca9906cf6a46c23555a8411f8d57645f0e0ce8147dc664", + "package/dist/i18n/pl.mjs": "079ed96732979035297af387c64a7b99519b5a43107fb86735a654ea4fe8e9b5", + "package/dist/i18n/ru.js": "62fac9dddb302eb16382e6e9301fc4b2c66f5f69d4a358a26b67026a5fa4e787", + "package/dist/i18n/ru.mjs": "a893aeaee1d79409db72e324fbff200e3864f33ed168f04d91d25f7cf2efa765", + "package/dist/i18n/tr.js": "1492c36b069a7d73db4f3913bd18085faff7afd8b3c158138fe264e31600e38c", + "package/dist/i18n/tr.mjs": "a90478cac078836b76c2a98261907610fee07f3e4b28671b8e7dbf533778f918", + "package/dist/i18n/vi.js": "c7216ef7e608a79b7e2c479a07bf310d4ab79577947ac4a62d676faaced0e9cf", + "package/dist/i18n/vi.mjs": "66270fdfa0b5da396322d1769169b7df37a030ba15ac18eb30bd87b3af8074ec", + "package/dist/i18n/zh-tw.js": "11c49857d6d40a5ffd224ffa59c9026e71621b345f63c5a086326db80a22371a", + "package/dist/i18n/zh-tw.mjs": "a38e630cb96de96c15d824686aec750e25cf6c8ada01285a292442a81566314f", + "package/package.json": "82e92699e7c29847f32ac6767ece0401290e7d7a12854722bdbcfd09b90b7954", + "package/types/COMPATIBILITY.md": "a7da9e102a116104b513df4f7451247b7759bc502d3375f30bd5b14a541826e0", + "package/types/README.md": "1bd9868e773610e6312fd48a1c5aa20772d29df0af40c3db66e75b7ab4a1aee0", + "package/types/artplayer.d.cts": "3685065e4263abcfd9d953176e461d5b3eb990b0363b54689a7f55deba770fef", + "package/types/artplayer.d.mts": "b664fec51ee5c5381f2ceab25d99d52aee56feab04a2ee7b4ea5b2145ea5fa0e", + "package/types/artplayer.d.ts": "5d0162adf7ac6d0f2bb9b5cfe361f28657c4d9c540cadbf36eba3cc954ce50f8", + "package/types/component.d.ts": "ae5bc1ddeaa1ee1ebbc62573e32423993727079205c64751a5a314170e588a32", + "package/types/config.d.ts": "36f9a84cd436f17701a0d11d3417fc1fe0414e0c52c80083b94a85dfc329e37e", + "package/types/cssVar.d.ts": "93217860fff2ebf750f606d95ddd22220a58837119e63bdf2f5f78fd7488458b", + "package/types/emitter.d.ts": "7e1f9a2a8ce65feb95734f1ed52d1334e57aa388bbef2a43d8bc53a9b6f6623c", + "package/types/events.d.ts": "f83624c9268a284477e8bd793fe3562a2363b72f2a44d5c64fe8c6a6cede8830", + "package/types/i18n.d.ts": "f4f2f05bdfda73c74fef34767cb18ee8e11b743120b1c4598e9714d7846018e5", + "package/types/icons.d.ts": "326a67ba7bdcf52862c5b2a269e540140aff98d05c7b96196485a77f84fe5960", + "package/types/language.d.cts": "f7928e108cf7b7814c2d75cb0a525333e95873fca6cd6d89a740450656b9c1cd", + "package/types/language.d.mts": "6619a26d604b7cccbe2f596243d87ae37cfaae33b8b52874ec071d9787446f9e", + "package/types/legacy/language.d.ts": "c7abda4d506fd933c350e9030290bf9e64347a921d683a66115da1995ebe0a81", + "package/types/option.d.ts": "ccf2c8ddc537dc65c9682d39f044726e23bff18f476b622512d7e6a4b949b5f6", + "package/types/playback.d.ts": "58705c273df6e76a77a6575e769a9ec6c013785c25080f2551aa22415788d870", + "package/types/player.d.ts": "92b00211b70ac71336e495899df972a0519dd138111846044fe4e0f967966177", + "package/types/plugin.d.ts": "fc3594a41c51d8248713e33c486fb1f6a518ac5c7ce31be6988a6cd8d052c5f3", + "package/types/quality.d.ts": "1ac1a48b4f74ff9ba1ce60443e1463d50d1c6a2e5d87d63dc7d2a8eb7f4c2c34", + "package/types/runtime.d.cts": "7ee988dfea9a0ac71a34a656f96f7b74e78aa92547cfdfab7ff7e1139a77d4a3", + "package/types/runtime.d.mts": "dcb85f59a8b7714918ced0c1d18c2bb24e8b80b56d8c594e019dcbdb5ad88b08", + "package/types/runtime.d.ts": "9c6ccd39a1be293f17ae46dae8ea077053e12635ee79d73a5e3b3a559230fcff", + "package/types/runtime/component.d.ts": "1b4c0cee44568649e80140fe778febfcae88af0ac85515f1cbeff78ab0c6d14f", + "package/types/runtime/construction.d.ts": "7a87bedf6f7ffff0c46078aa5f1551e0077d35574e286516121f5f935e2d9ce4", + "package/types/runtime/events.d.ts": "a1e2f9d387475453eaf95c2a5ee79e228636c0bb859bda3fcc46da46574a0c31", + "package/types/runtime/media.d.ts": "8fc89401be7fe25542c169d07dd5675f10916ed20be8493e19695966cf3864a8", + "package/types/runtime/option.d.ts": "6e334e1a76fdcc40b4665c02523bd31c3994d96078fadeb40e23422aa54c1a1d", + "package/types/runtime/player.d.ts": "8ac6383502e4872e83659838844856903031a81a0adc8e8535feb78453aa042c", + "package/types/runtime/plugin.d.ts": "8528f2b17843149f42f9cc16d6fd70baa238c9dc2c329b67d2c38096799c69bd", + "package/types/runtime/services.d.ts": "604c9f8e4b6c0da818f96576bf9f4b0e96085a9003927b2f7ba30027b1ba237e", + "package/types/runtime/setting.d.ts": "349c1fcb205ad312916bd3ee55e6bf241ea8a569fb2ab6c80822e69fc518f7d7", + "package/types/runtime/subtitle.d.ts": "269ff84678d327b3a9dc2f5bc6cc0d731d1e081b85422e474621d5d792efb153", + "package/types/runtime/template.d.ts": "7a62f968a38b4bf9109a4439557c02074eb1d62ba1f6e21a8eb194e91f0badfe", + "package/types/runtime/utils.d.ts": "6bf731705d86853e066dbaf3b8d4003b6548423fe615b7412b722bd0d3a6e48b", + "package/types/setting.d.ts": "08305a75dff96f06490430e099338b463df471b550c1d0dac3abc8995b39eb09", + "package/types/subtitle.d.ts": "b18314e2d64efdb6045830b6e12f615ae2ed585416130e562329ad3ea337f6ce", + "package/types/template.d.ts": "4792bc0c2cbcb7966e4375118bd34b326166e0831862e94ace4cf6b1bbe6a8c8", + "package/types/utils.d.ts": "fca0d3d814df94a25f9eb067c86fa0cf272e2791bac793b91229d57680797712" + } + }, + { + "name": "artplayer-plugin-danmuku-mask", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\danmuku-mask-package-types-kf2wJS\\artplayer-plugin-danmuku-mask.tgz", + "sha256": "7cf50ed53d9efd9cc10e7a2f855c8b9dda00c9da49a1adb4e7282b6201c22f94", + "files": { + "package/ARCHITECTURE.md": "fa081aa9a8c6065dd54c6605ab7f30e6346fe6c8157139cd7785c2e71179c2ab", + "package/README.md": "3f81e23ecb798620b3e401d7c9155a8a9c5b4b36fb16f97bc256f51f587e6a6a", + "package/dist/artplayer-plugin-danmuku-mask.js": "406a8c28207d386cffe1e61dc79afd8a6a9d5cbd1dc8e1661b4febbf48182f55", + "package/dist/artplayer-plugin-danmuku-mask.legacy.js": "62d61dcedd3e90fa480589c2f1e3168c97635232c98ffe8c25c7918a2f3c66db", + "package/dist/artplayer-plugin-danmuku-mask.mjs": "51936576383129170125b33d2983858e0841ea507368970f6d154bc9d78583b1", + "package/package.json": "03b68f518f32d6619d7da1f163180224942ab195b5d1c0d18ab852d98fffdc3f", + "package/types/README.md": "f253dfcfa34931c644ca46eb826a3d84b16e8929e2615098ce33adfca9261d5c", + "package/types/artplayer-plugin-danmuku-mask.d.ts": "f2b67f82a6471b79d921a7376b24620d0ed8284a3e1882b0f75a9b38dfac3638" + } + } + ], + "published": [ + { + "label": "published-1.0.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-danmuku-mask-1.0.0.tgz", + "sha256": "8a88f52ae843bed1be01a2e60b82cf886d33582f990a5016d49b1409c3493c4b" + }, + { + "label": "published-1.1.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-danmuku-mask-1.1.0.tgz", + "sha256": "96682e46cb140a84b164818dd118480d7be42b24afaab390027a25e52e8ae31c" + } + ], + "matrix": [ + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 3, + "message": "Cannot find module 'artplayer-plugin-danmuku-mask/legacy' or its corresponding type declarations.\n There are types at '/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.cts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 24, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' is not assignable to type 'Factory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(option?: Option | undefined): (art: Artplayer) => Result'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 25, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 26, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 29, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 39, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 40, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 41, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 42, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 43, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 44, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.mts", + "line": 45, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.0.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.0.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 3, + "message": "Cannot find module 'artplayer-plugin-danmuku-mask/legacy' or its corresponding type declarations." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 3, + "message": "Cannot find module 'artplayer-plugin-danmuku-mask/legacy' or its corresponding type declarations.\n There are types at '/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.cts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 24, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' is not assignable to type 'Factory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(option?: Option | undefined): (art: Artplayer) => Result'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 25, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 26, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 29, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 39, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 40, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 41, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 42, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 43, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 44, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.mts", + "line": 45, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.1.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "published-1.1.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [ + { + "code": 2307, + "file": "consumer.ts", + "line": 3, + "message": "Cannot find module 'artplayer-plugin-danmuku-mask/legacy' or its corresponding type declarations." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.cts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.cts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.cts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "raw": [ + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 21, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'false' does not satisfy the constraint 'true'." + }, + { + "code": 2344, + "file": "consumer.mts", + "line": 22, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(...args: any): any'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 24, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' is not assignable to type 'Factory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' provides no match for the signature '(option?: Option | undefined): (art: Artplayer) => Result'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 25, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 26, + "message": "Type 'Factory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")'." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 28, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + }, + { + "code": 2349, + "file": "consumer.mts", + "line": 29, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\")' has no call signatures." + } + ], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.mts", + "line": 38, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 39, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 40, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 41, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 42, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 43, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.mts", + "line": 44, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.mts", + "line": 45, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "bundler-esm", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + }, + { + "plugin": "candidate", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "raw": [], + "diagnostics": [], + "invalid": [ + { + "code": 2322, + "file": "consumer.ts", + "line": 36, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 37, + "message": "Type 'number' is not assignable to type 'boolean | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 38, + "message": "Type 'number' is not assignable to type 'string | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 39, + "message": "Type 'string' is not assignable to type 'number | undefined'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 40, + "message": "Type '\"artplayerPluginDanmukuMask\"' is not assignable to type '\"other\"'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 41, + "message": "Type 'Promise' is not assignable to type 'void'." + }, + { + "code": 2322, + "file": "consumer.ts", + "line": 42, + "message": "Type 'void' is not assignable to type 'Promise'." + }, + { + "code": 2339, + "file": "consumer.ts", + "line": 43, + "message": "Property 'then' does not exist on type '(art: Artplayer) => Result'." + } + ] + } + ], + "scope": "Actual published and candidate packages installed outside workspace, offline/frozen lock stability, byte checks, strict types and CJS/legacy registration. No model startup, native inference, ESM browser or GPU acceptance.", + "evidenceDirectory": "D:/github/ArtPlayer/refactor/.cache/danmuku-mask-package-types-kf2wJS" + }, + "limits": "Runtime emission preserved and package consumers verified. No real model, native browser/video/CSS or GPU/WASM validation is claimed here; PKG-MASK-05/06 remain." +} diff --git a/refactor/changes/2026-09-14-PKG-MASK-04-types.md b/refactor/changes/2026-09-14-PKG-MASK-04-types.md new file mode 100644 index 000000000..7ccccdb83 --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-MASK-04-types.md @@ -0,0 +1,53 @@ +# PKG-MASK-04 严格TS与公开类型兼容 + +## 实现与公开接口 + +五个自有运行模块迁为TS,types.ts明确最小核心宿主、原始可选参数/完整配置、SDK +额外历史传参、输出画布及每轮可空资源。类字段declare不产生新字段;局部断言说明 +对应初始化、Promise resolver或RGBA长度约束。实际Artplayer宿主fixture通过严格检查。 +相对MASK03提交aba125262,五模块归一化JS和三种正常构建产物保持一致。 + +公开声明与npm1.0.0/1.1.0保持字节一致(忽略换行)。原参数可选、同步注册、 +Promise start、void stop、固定name、完整工厂双向赋值及类型提取继续保留。 +这些返回值本就准确,不为形式统一额外增加/runtime入口或暴露内部SDK类型。 + +## 两项类型工具修复 + +实际严格消费者先复现旧Node模块解析下/legacy报2307,现以精确typesVersions路径 +映射修复;默认工厂声明与其他入口未变,历史直接声明路径仍验证。npm两版在相同 +模式均记录旧失败,候选只允许消除此特定行的2307;NodeNext ESM根命名空间诊断 +保持历史结果。根CommonJS最新是函数无default属性,1.0.0对象包装单独验证。 + +旧在线编辑器生成器把export default和export=混在一起,实际旧文件在两个编译器 +均报2309。接入现有语义生成器,生成文件正例及三项非法调用反例通过。私有Option/ +Result不改为新增公开类型;消费者用Parameters/ReturnType提取,生成内容校验防漂移。 + +SDK严格声明另复现TF4.22.0的hash_util.d.ts缺少Long import。声明专用sdk-ambient.d.ts +引用已安装官方@types/long4.0.2,不产生运行时导入、没有重写第三方类型,也不改 +types:[]或skipLibCheck:false。包src和tsconfig均不进入tarball。 + +## 验证 + +Node24.21.0下联合99项通过:36候选生命周期、54历史缺陷、6历史契约、3公共/编辑器 +类型测试。定向lint、全仓404生产TS及当前/兼容编译器流程通过。三种产物正常重建, +docs副本一致,生成编辑器声明通过正反例和文件一致性验证。 + +最终Yarn实际pack将旧npm1.0.0/1.1.0和候选分别安装到仓库外,复制根锁作依赖种子, +离线安装后再次frozen安装并逐字节核对包内容/锁文件。每包TS5.9.3的Node10、NodeNext +CJS/ESM、Bundler及TS4.3.5的Node10,共15组;每组8项非法输入/返回值独立报错。 +实际安装main和legacy执行同步注册/stop/destroy,但不启动模型;不称为播放或SDK +验收。旧Node10/legacy失败用既有直接声明路径核实工厂,其原错误保留在报告。 + +复跑:yarn test:danmuku-mask、node --test refactor/scripts/danmuku-mask-types.test.mjs、 +yarn test:danmuku-mask-types-package、yarn typecheck、yarn build:ts artplayer-plugin-danmuku-mask。 +新增安装检查脚本复用现有Yarn/TS版本,无新增依赖或锁文件改动。包内架构及types/README +解释维护路径和历史边界。详见[类型验证](../baselines/danmuku-mask-types-validation.json)。 +同步更新风险、SDK来源与影响映射中的当前源码路径,冻结历史JS夹具不改;风险和影响 +检查通过,没有因迁移路径失效而忽略SDK或插件组合验证。 + +## 剩余工作与回退 + +05仍负责真实模型、原支持范围核心、浏览器/设备/切源/布局/后端与GPU资源;06负责 +完整分发、许可、示例和发布验收。WebKit/Chrome设备能力和SDK的close完成仍未证明。 +本任务不关闭这些风险、不改变各包独立major发布政策,不推送或发布。 +回退此任务源码/类型解析及生成器选择后正常重建即可;03生命周期修复是独立提交。 diff --git a/refactor/fixtures/implementation/danmuku-mask.ts b/refactor/fixtures/implementation/danmuku-mask.ts new file mode 100644 index 000000000..dd77a5818 --- /dev/null +++ b/refactor/fixtures/implementation/danmuku-mask.ts @@ -0,0 +1,6 @@ +import type Artplayer from 'artplayer' +import factory from '../../../packages/artplayer-plugin-danmuku-mask/src' + +declare const art: Artplayer +const registration: { name: string, start: () => Promise, stop: () => void } = factory({ opacity: 0, smoothSegmentation: false })(art) +export { registration } diff --git a/refactor/impact-policy.json b/refactor/impact-policy.json index 114f40621..70a3623ea 100644 --- a/refactor/impact-policy.json +++ b/refactor/impact-policy.json @@ -123,7 +123,7 @@ "consumer": "artplayer-plugin-danmuku-mask", "reason": "Mask and Danmuku share the core-owned $danmuku layer; retain combination validation for root identity, styles and lifecycle", "evidence": [ - "packages/artplayer-plugin-danmuku-mask/src/index.js", + "packages/artplayer-plugin-danmuku-mask/src/index.ts", "docs/assets/example/danmuku.mask.js" ] } diff --git a/refactor/plan.md b/refactor/plan.md index c98a9202c..4f9006eaa 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 231 项,范围 22 个包及工作区/示例。 -状态:todo 67 / doing 14 / blocked 0 / done 150 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 66 / doing 14 / blocked 0 / done 151 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -243,7 +243,7 @@ | PKG-MASK-01 | artplayer-plugin-danmuku-mask
核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | done | | PKG-MASK-02 | artplayer-plugin-danmuku-mask
建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-MASK-03 | artplayer-plugin-danmuku-mask
整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | -| PKG-MASK-04 | artplayer-plugin-danmuku-mask
迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | +| PKG-MASK-04 | artplayer-plugin-danmuku-mask
迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done | | PKG-MASK-05 | artplayer-plugin-danmuku-mask
验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-MASK-06 | artplayer-plugin-danmuku-mask
验证分发并同步文档 | PKG-MASK-05, ENG-07 | danmuku.mask.js、资源版本/许可、CPU fallback 和包体积证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | @@ -535,6 +535,7 @@ - PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md) - PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json) - PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json) +- PKG-MASK-04: [记录](changes/2026-09-14-PKG-MASK-04-types.md) [记录](baselines/danmuku-mask-types-validation.json) - PKG-ASR-01: [记录](changes/2026-09-13-PKG-ASR-01-contracts.md) [记录](baselines/asr-contract.md) [记录](baselines/asr-release.json) - PKG-ASR-02: [记录](changes/2026-09-13-PKG-ASR-02-audio-baseline.md) [记录](baselines/asr-audio-validation.json) - PKG-ASR-03: [记录](changes/2026-09-13-PKG-ASR-03-audio-ownership.md) [记录](baselines/asr-ownership-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 62d3711aa..ad19ed4b9 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,14 @@ # 进度与证据 +## PKG-MASK-04 严格TS及公开类型完成 + +五个自有模块迁TS且运行产物保持MASK03字节;真实核心宿主、SDK声明桥接和私有资源 +类型通过严格检查。修复旧Node解析/legacy类型路径与编辑器混合导出,保留根工厂。 +联合99项、安装包15编译器模式和全仓404生产TS通过;[详细记录](changes/2026-09-14-PKG-MASK-04-types.md)。 +16插件中源码与类型迁移未完成的剩AutoThumbnail/VAST/JASSUB三包;所有后续组合/发布 +门槛继续按tasks.json执行,不能把迁移完成当作已可发布。Mask05/06与实际SDK资源风险待验收。 + + ## PKG-MASK-03 生命周期拆分完成 五个职责模块和单运行归属控制器落地;停止/销毁、迟到回调、并发启动、释放等待和 diff --git a/refactor/risks.json b/refactor/risks.json index acab6bc98..79a351fb7 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -751,7 +751,7 @@ "PKG-MASK-06" ], "evidence": [ - "packages/artplayer-plugin-danmuku-mask/src/index.js", + "packages/artplayer-plugin-danmuku-mask/src/index.ts", "refactor/baselines/danmuku-mask-contract.md", "refactor/baselines/danmuku-mask-contract-validation.json", "refactor/baselines/danmuku-mask-release.json" diff --git a/refactor/scripts/danmuku-mask-consumer.mjs b/refactor/scripts/danmuku-mask-consumer.mjs new file mode 100644 index 000000000..cef8e1325 --- /dev/null +++ b/refactor/scripts/danmuku-mask-consumer.mjs @@ -0,0 +1,22 @@ +import fs from 'node:fs' + +export const maskInvalidStatements = [ + 'mask({ modelSelection: "general" })', + 'mask({ smoothSegmentation: 1 })', + 'mask({ solutionPath: 42 })', + 'mask({ opacity: "1" })', + 'const invalidName: "other" = result.name', + 'const invalidStart: void = result.start()', + 'const invalidStop: Promise = result.stop()', + 'mask().then(() => {})', +] + +export function maskConsumerSource(mode, raw = false) { + let source = fs.readFileSync(new URL('../../test/types/danmuku-mask-public.ts', import.meta.url), 'utf8') + if (mode === 'nodenext-esm' && !raw) { + source = source + .replace('import mask from \'artplayer-plugin-danmuku-mask\'', 'import maskModule from \'artplayer-plugin-danmuku-mask\'\nconst mask = maskModule.default') + .replace('import legacy from \'artplayer-plugin-danmuku-mask/legacy\'', 'import legacyModule from \'artplayer-plugin-danmuku-mask/legacy\'\nconst legacy = legacyModule.default') + } + return source +} diff --git a/refactor/scripts/danmuku-mask-package-types.mjs b/refactor/scripts/danmuku-mask-package-types.mjs new file mode 100644 index 000000000..136dd7274 --- /dev/null +++ b/refactor/scripts/danmuku-mask-package-types.mjs @@ -0,0 +1,108 @@ +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 { maskConsumerSource, maskInvalidStatements } from './danmuku-mask-consumer.mjs' +import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs' +import { hash, readMember } from './releases.mjs' + +async function main() { + assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:danmuku-mask-types-package') + const yarn = process.env.npm_execpath + assert(yarn && fs.existsSync(yarn)) + const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/danmuku-mask-package-types-')) + console.log(`Mask installed consumer evidence: ${output}`) + const contract = await verifyDanmukuMaskContract() + const packages = [] + for (const name of ['artplayer', 'artplayer-plugin-danmuku-mask']) { + const archive = path.join(output, `${name}.tgz`) + fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name))) + const manifest = JSON.parse(readMember(archive, 'package/package.json')) + const files = packedFiles(archive) + checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : []) + packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) }) + } + const core = packages[0] + const candidates = [ + ...[...contract.baseline.previous, contract.baseline.release].map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })), + { ...packages[1], label: 'candidate' }, + ] + const matrix = [] + const priorDiagnostics = new Map() + for (const plugin of candidates) { + const consumer = consumerDirectory() + try { + writeJson(path.join(consumer, 'package.json'), { name: 'mask-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) }) + fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock')) + fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer)) + const lock = fs.readFileSync(path.join(consumer, 'yarn.lock')) + fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer)) + assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock) + fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock) + for (const pkg of [core, plugin]) { + const root = path.join(consumer, 'node_modules', pkg.name) + assert.equal(fs.realpathSync(root), root, 'Installed package is 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 runtime = path.join(consumer, 'exports.cjs') + fs.writeFileSync(runtime, `const assert = require('node:assert/strict'); global.self = global; +for (const name of ['artplayer-plugin-danmuku-mask', 'artplayer-plugin-danmuku-mask/legacy']) { + const value = require(name); const old = ${plugin.version === '1.0.0'}; + assert.equal(typeof value, old ? 'object' : 'function'); + assert.equal(typeof value.default, old ? 'function' : 'undefined'); + const factory = old ? value.default : value; + const style = {}; const handlers = new Map(); + const result = factory()({ template: { $video: {}, $danmuku: {style} }, on(name, fn) { handlers.set(name, fn); }, off(name, fn) { if (handlers.get(name) === fn) handlers.delete(name); } }); + assert.deepEqual(Object.keys(result), ['name', 'start', 'stop']); + assert.equal(result.name, 'artplayerPluginDanmukuMask'); assert.equal(typeof result.start, 'function'); + assert.equal(result.stop(), undefined); assert.equal(style.maskImage, 'none'); + handlers.get('destroy')(); +} +console.log('Installed CJS/legacy registration and stop/destroy passed; no model startup.');`) + fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtime], consumer)) + for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) { + const next = mode.startsWith('nodenext') + const filename = path.join(consumer, `consumer.${next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'}`) + const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs } + const compile = (source) => { + fs.writeFileSync(filename, source) + const program = compiler.createProgram([filename], options) + for (const file of program.getSourceFiles()) { + const actual = fs.realpathSync(file.fileName) + assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Type escaped consumer: ${actual}`) + } + return compiler.getPreEmitDiagnostics(program).map(d => ({ code: d.code, file: d.file ? path.relative(consumer, d.file.fileName).replaceAll('\\', '/') : null, line: d.file && d.start !== undefined ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(d.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '') })) + } + const raw = compile(maskConsumerSource(mode, true)) + const key = `${compiler.version}:${mode}` + if (plugin.label === 'published-1.1.0') + priorDiagnostics.set(key, raw) + if (plugin.label === 'candidate') + assert.deepEqual(raw, priorDiagnostics.get(key).filter(item => !(mode === 'node10-commonjs' && item.code === 2307 && item.line === 3)), 'Only the recorded old legacy-path resolution failure may be corrected') + let source = maskConsumerSource(mode) + if (plugin.label !== 'candidate' && mode === 'node10-commonjs') { + assert.deepEqual(raw.map(item => [item.code, item.line]), [[2307, 3]], 'Historical legacy path failure must stay precise') + source = source.replace('from \'artplayer-plugin-danmuku-mask/legacy\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'') + } + const diagnostics = compile(source) + assert.deepEqual(diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`) + const invalid = compile(`${source}\n${maskInvalidStatements.join('\n')}\n`) + assert.equal(invalid.length, maskInvalidStatements.length) + assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index)) + matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, raw, diagnostics, invalid }) + } + } + finally { removeConsumer(consumer) } + } + writeJson(path.join(output, 'report.json'), { task: 'PKG-MASK-04', node: process.version, packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix, scope: 'Actual published and candidate packages installed outside workspace, offline/frozen lock stability, byte checks, strict types and CJS/legacy registration. No model startup, native inference, ESM browser or GPU acceptance.' }) + console.log(`Mask installed consumers passed ${matrix.length} compiler modes: ${output}`) +} +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/refactor/scripts/danmuku-mask-types.test.mjs b/refactor/scripts/danmuku-mask-types.test.mjs new file mode 100644 index 000000000..1ad09875e --- /dev/null +++ b/refactor/scripts/danmuku-mask-types.test.mjs @@ -0,0 +1,57 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +// eslint-disable-next-line test/no-import-node-test -- Public compatibility uses the repository Node 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 { maskInvalidStatements } from './danmuku-mask-consumer.mjs' +import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs' +import { readMember } from './releases.mjs' + +test('Mask public declaration keeps both actual published factory and result shapes', async () => { + const contract = await verifyDanmukuMaskContract() + const current = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8').replaceAll('\r\n', '\n') + for (const archive of contract.archives.values()) + assert.equal(current, readMember(archive, 'package/types/artplayer-plugin-danmuku-mask.d.ts').toString().replaceAll('\r\n', '\n')) +}) + +test('Mask strict public consumers preserve optional arguments, function replacement and reject invalid results/options', () => { + const source = fs.readFileSync('test/types/danmuku-mask-public.ts', 'utf8') + for (const compiler of [ts, compat]) { + assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), []) + assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source.replace('from \'artplayer-plugin-danmuku-mask\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'')), [], 'Legacy mapping must preserve historical direct declaration paths') + const invalid = checkConsumer(compiler, 'node10-commonjs', `${source}\n${maskInvalidStatements.join('\n')}\n`) + assert.equal(invalid.length, maskInvalidStatements.length) + assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index)) + } +}) + +test('Mask editor declaration is generated from the unchanged public contract and accepts optional registration', async () => { + const source = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8') + const generated = generatePluginEditorDeclaration(source, 'artplayerPluginDanmukuMask') + const file = 'docs/assets/ts/artplayer-plugin-danmuku-mask.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 = `const option: NonNullable[0]> = {opacity: 0}; +const art = new Artplayer({container: '#player', url: '/video.mp4'}); +const result = artplayerPluginDanmukuMask(option)(art); +const started: Promise = result.start(); +const stopped: void = result.stop(); +artplayerPluginDanmukuMask(); +const replacement: typeof artplayerPluginDanmukuMask = () => () => ({name: 'artplayerPluginDanmukuMask', async start() {}, stop() {}}); +// @ts-expect-error Synchronous registration has no then method. +artplayerPluginDanmukuMask().then(() => {}); +// @ts-expect-error The runtime factory has no default property. +artplayerPluginDanmukuMask.default(); +// @ts-expect-error Opacity remains numeric. +artplayerPluginDanmukuMask({opacity: '1'});` + 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, 3) + } +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 2ab69ac79..99f850f73 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -2585,11 +2585,14 @@ "ENG-06", "CORE-07" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "模型 adapter、canvas 和选项的精确类型", "acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容", - "evidence": [] + "evidence": [ + "changes/2026-09-14-PKG-MASK-04-types.md", + "baselines/danmuku-mask-types-validation.json" + ] }, { "id": "PKG-MASK-05", diff --git a/refactor/third-party.json b/refactor/third-party.json index 381221eed..d29e1d7d5 100644 --- a/refactor/third-party.json +++ b/refactor/third-party.json @@ -1228,7 +1228,11 @@ "PKG-MASK-06" ], "sourceFiles": [ - "packages/artplayer-plugin-danmuku-mask/src/index.js" + "packages/artplayer-plugin-danmuku-mask/src/index.ts", + "packages/artplayer-plugin-danmuku-mask/src/config.ts", + "packages/artplayer-plugin-danmuku-mask/src/sdk.ts", + "packages/artplayer-plugin-danmuku-mask/src/controller.ts", + "packages/artplayer-plugin-danmuku-mask/src/output.ts" ], "contract": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.", "validationStatus": "MASK01 verified two actual releases, six frozen Yarn/installed SDK versions, adapter source hashes and 12 existing demo resource byte matches. The default solutionPath remains an unversioned CDN. Controlled export/default/pixel probes do not establish real models, fallback, browser/GPU cleanup or notice completeness; MASK05/06 remain open.", diff --git a/scripts/build-ts.js b/scripts/build-ts.js index d442e8823..20a3fea1a 100644 --- a/scripts/build-ts.js +++ b/scripts/build-ts.js @@ -53,7 +53,7 @@ console.log(`✨ Built ${artplayerTSoutput}`); const type = pluginsTS[index] const { name, file } = parsePluginInfo(type) const source = String(fs.readFileSync(type)) - const semanticPlugin = ['artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name) + const semanticPlugin = ['artplayerPluginDanmukuMask', 'artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name) const localTypes = name === 'artplayerProxyMediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(type), 'media.d.ts'), 'utf8') } : {} const code = semanticPlugin ? generatePluginEditorDeclaration(source, name, localTypes) diff --git a/test/README.md b/test/README.md index 0f2d4c6d3..86116f04f 100644 --- a/test/README.md +++ b/test/README.md @@ -6,6 +6,10 @@ Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`. frozen historical defects and public contracts. Candidate tests also run in `test:unit`; their controlled SDK, RAF and canvas hosts do not prove native model inference, GPU disposal or browser mask geometry. See the Mask architecture map. +`yarn test:danmuku-mask-types-package` installs both published versions and the +candidate tarball outside the workspace for old/current compiler compatibility, +negative cases and CJS/legacy registration without model startup. The ordinary +baseline suite also checks public declaration identity and editor generation. `yarn test:mediabunny` runs historical lifecycle observations, candidate load cancellation, and real SDK input parsing/track contracts; all are in `test:unit`. Candidate failures diff --git a/test/helpers/danmuku-mask-candidate.js b/test/helpers/danmuku-mask-candidate.js index 0214e0251..fcfd55678 100644 --- a/test/helpers/danmuku-mask-candidate.js +++ b/test/helpers/danmuku-mask-candidate.js @@ -1,10 +1,11 @@ import assert from 'node:assert/strict' import vm from 'node:vm' import { build } from 'esbuild' +import { resolveSource } from './load.js' export const flush = () => new Promise(resolve => setImmediate(resolve)) export async function maskCandidate() { - const result = await build({ entryPoints: ['packages/artplayer-plugin-danmuku-mask/src/index.js'], bundle: true, write: false, format: 'cjs', platform: 'browser', external: ['@tensorflow-models/body-segmentation', '@tensorflow/tfjs-core', '@tensorflow/tfjs-backend-webgl', '@tensorflow/tfjs-backend-cpu'] }) + const result = await build({ entryPoints: [resolveSource('packages/artplayer-plugin-danmuku-mask/src/index')], bundle: true, write: false, format: 'cjs', platform: 'browser', external: ['@tensorflow-models/body-segmentation', '@tensorflow/tfjs-core', '@tensorflow/tfjs-backend-webgl', '@tensorflow/tfjs-backend-cpu'] }) return result.outputFiles[0].text } diff --git a/test/types/danmuku-mask-public.ts b/test/types/danmuku-mask-public.ts new file mode 100644 index 000000000..f5ba1adf7 --- /dev/null +++ b/test/types/danmuku-mask-public.ts @@ -0,0 +1,34 @@ +import Artplayer from 'artplayer' +import mask from 'artplayer-plugin-danmuku-mask' +import legacy from 'artplayer-plugin-danmuku-mask/legacy' + +interface Option { + solutionPath?: string + modelSelection?: number + smoothSegmentation?: boolean + minDetectionConfidence?: number + minTrackingConfidence?: number + selfieMode?: boolean + drawContour?: boolean + foregroundThreshold?: number + opacity?: number + maskBlurAmount?: number +} +interface Result { name: 'artplayerPluginDanmukuMask', start: () => Promise, stop: () => void } +type Factory = (option?: Option) => (art: Artplayer) => Result +type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false +type Assert = T +type Arguments = Assert, [option?: Option]>> +type Registration = Assert>, Result>> +const replacement: Factory = () => () => ({ name: 'artplayerPluginDanmukuMask', async start() {}, stop() {} }) +const toOld: Factory = mask +const fromOld: typeof mask = replacement +const legacyReplacement: typeof legacy = replacement +const option: Option = { solutionPath: '/models', modelSelection: 0, smoothSegmentation: false, minDetectionConfidence: 0, minTrackingConfidence: 0, selfieMode: false, drawContour: false, foregroundThreshold: 0, opacity: 0, maskBlurAmount: 0 } +const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [mask(), mask(option), legacy(undefined)] }) +const result = mask(option)(art) +const name: 'artplayerPluginDanmukuMask' = result.name +const starting: Promise = result.start() +const stopped: void = result.stop() +void [toOld, fromOld, legacyReplacement, name, starting, stopped] +export type { Arguments, Registration }