diff --git a/package.json b/package.json index c9bb0a383..1679ae1d7 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "check:ci": "node refactor/scripts/ci-workflow.mjs", "test:ci": "node --test test/ci-summary.test.js refactor/scripts/ci-workflow.test.mjs refactor/scripts/impact.test.mjs test/package-runtime.test.js", "test:package:runtime": "node scripts/package-runtime.mjs --canonical", - "test:asr": "node --test test/asr.test.js test/asr-audio.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-distribution.test.js" + "test:asr": "node --test test/asr.test.js test/asr-audio.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-distribution.test.js", + "test:asr-types-package": "node refactor/scripts/asr-package-types.mjs" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/packages/artplayer-plugin-asr/.npmignore b/packages/artplayer-plugin-asr/.npmignore index 4439c6874..2b272d565 100644 --- a/packages/artplayer-plugin-asr/.npmignore +++ b/packages/artplayer-plugin-asr/.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-asr/ARCHITECTURE.md b/packages/artplayer-plugin-asr/ARCHITECTURE.md index 411f19eec..35753e613 100644 --- a/packages/artplayer-plugin-asr/ARCHITECTURE.md +++ b/packages/artplayer-plugin-asr/ARCHITECTURE.md @@ -63,10 +63,20 @@ HTML, punctuation splitting, replacement, ignored non-string values and the historical `length=0` behavior. The CJS function has a non-enumerable self `default` alias for consumers of the 2.0 namespace, while preserving the callable 2.1 form. -Root public declarations have not yet migrated: they describe stop as void and -the callback as void/Promise. PKG-ASR-04 owns exact asynchronous public types, -old assignment consumers, module forms and installed tarball acceptance. Internal -strict TypeScript does not prove that public type migration complete. +Root and `/legacy` declarations intentionally retain the historical factory, +void stop and void/Promise callback. Their NodeNext ESM default import remains +the old namespace type: changing it into a callable default would break legal +namespace substitution. Historical direct-call errors are frozen separately from +legal namespace consumers. Do not silently rewrite these declarations to match +runtime behavior or require a self-default property on old replacement factories. + +The additive `/runtime` entry references the same main/ESM implementation, with +`RuntimeOption`, `RuntimeResult` and `RuntimeFactory`: callback text/null/no result +and asynchronous stop are accurate. Its `.d.mts` default export and `.d.cts`/ +`.d.ts` export assignment support ESM, raw CommonJS and old Node10 resolution. +Pure type namespaces make all four named runtime types available in every mode. +The root also exports those types for convenient annotations. No runtime code or +version changes are needed for this entry. From the repository root: @@ -76,6 +86,11 @@ From the repository root: published/current core, pause/resume, source switch, stop/restart and destroy. Set `ARTPLAYER_ASR_ARTIFACT` to an actual main/legacy file to test that artifact. - `yarn typecheck`: includes the package strict/noUncheckedIndexedAccess project. +- `yarn test:asr-types-package`: packs and installs core/ASR outside the workspace, + checks frozen 2.0/2.1 consumers and candidate declarations in strict compiler + modes, verifies offline frozen reinstall and file hashes, exercises actual + import/require exports, and rejects invalid uses. Historical declaration errors + are explicit expectations and never counted as candidate success. - `yarn dev artplayer-plugin-asr` and `yarn build artplayer-plugin-asr`: normal repository development and three-format production builds. diff --git a/packages/artplayer-plugin-asr/README.md b/packages/artplayer-plugin-asr/README.md index e1c4ae10b..c45e815b3 100644 --- a/packages/artplayer-plugin-asr/README.md +++ b/packages/artplayer-plugin-asr/README.md @@ -8,6 +8,35 @@ restart capture; the player's direct Web Audio connection remains alive until the player is destroyed so that stopping recognition does not silence playback. Call `art.destroy()` when disposing the player. +## TypeScript + +Existing imports keep their historical types, including `stop(): void` and a +`void | Promise` recognition callback. For accurate asynchronous types, use +the additive `/runtime` entry. It loads the same implementation as the root entry: + +```ts +import Artplayer from 'artplayer' +import asr from 'artplayer-plugin-asr/runtime' +import type { RuntimeOption } from 'artplayer-plugin-asr/runtime' + +const option: RuntimeOption = { + onAudioChunk: async ({ pcm, wav }) => { + // Send one of these buffers to your own recognition service. + void [pcm, wav] + return 'Recognized text.' + }, +} +const art = new Artplayer({ container: '#player', url: '/video.mp4' }) +const recognition = asr(option)(art) +await recognition.stop() +``` + +The callback may return text, `null`, or no value, synchronously or asynchronously. +CommonJS TypeScript can use `import asr = require('artplayer-plugin-asr/runtime')`. +The root declaration retains its historical NodeNext ESM namespace shape; use +`/runtime` for a directly callable default import in that mode. This is an opt-in +type entry, and does not require changes to existing JavaScript consumers. + ## Maintenance See [ARCHITECTURE.md](ARCHITECTURE.md) for the current audio flow, public behavior, diff --git a/packages/artplayer-plugin-asr/package.json b/packages/artplayer-plugin-asr/package.json index f3a364521..87b00c8f5 100644 --- a/packages/artplayer-plugin-asr/package.json +++ b/packages/artplayer-plugin-asr/package.json @@ -27,11 +27,31 @@ "types": "./types/artplayer-plugin-asr.d.ts", "import": "./dist/artplayer-plugin-asr.legacy.js", "require": "./dist/artplayer-plugin-asr.legacy.js" + }, + "./runtime": { + "import": { + "types": "./types/runtime.d.mts", + "default": "./dist/artplayer-plugin-asr.mjs" + }, + "require": { + "types": "./types/runtime.d.cts", + "default": "./dist/artplayer-plugin-asr.js" + } } }, "main": "./dist/artplayer-plugin-asr.js", "module": "./dist/artplayer-plugin-asr.mjs", "types": "./types/artplayer-plugin-asr.d.ts", "legacy": "./dist/artplayer-plugin-asr.legacy.js", - "browserslist": "last 1 Chrome version" + "browserslist": "last 1 Chrome version", + "typesVersions": { + "*": { + "legacy": [ + "types/artplayer-plugin-asr.d.ts" + ], + "runtime": [ + "types/runtime.d.ts" + ] + } + } } diff --git a/packages/artplayer-plugin-asr/tsconfig.json b/packages/artplayer-plugin-asr/tsconfig.json index c9218405d..bba301755 100644 --- a/packages/artplayer-plugin-asr/tsconfig.json +++ b/packages/artplayer-plugin-asr/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { "allowJs": false }, - "include": ["src/**/*.ts", "types/**/*.d.ts", "../../types/assets.d.ts"] + "include": ["src/**/*.ts", "types/**/*.d.ts", "types/**/*.d.mts", "types/**/*.d.cts", "../../types/assets.d.ts"] } diff --git a/packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts b/packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts index 13aed0ccd..0cf2329ab 100644 --- a/packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts +++ b/packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts @@ -1,11 +1,11 @@ import type Artplayer from 'artplayer' -interface AudioChunk { +export interface AudioChunk { pcm: ArrayBuffer wav: ArrayBuffer } -interface AsrPluginOption { +export interface AsrPluginOption { length?: number interval?: number sampleRate?: number @@ -13,13 +13,30 @@ interface AsrPluginOption { onAudioChunk?: (chunk: AudioChunk) => void | Promise } -interface AsrPluginInstance { +export interface AsrPluginInstance { name: 'artplayerPluginAsr' stop: () => void hide: () => void append: (subtitle: string) => void } +/** Historical factory shape, including void stop and callback results. */ +export type Factory = (option?: AsrPluginOption) => (art: Artplayer) => AsrPluginInstance + +/** Accurate asynchronous view available through the /runtime entry. */ +export interface RuntimeOption extends Omit { + onAudioChunk?: (chunk: AudioChunk) => string | void | null | Promise +} + +export interface RuntimeResult extends Omit { + stop: () => Promise +} + +export interface RuntimeFactory { + (option?: RuntimeOption): (art: Artplayer) => RuntimeResult + readonly default: RuntimeFactory +} + declare function artplayerPluginAsr(option?: AsrPluginOption): (art: Artplayer) => AsrPluginInstance export default artplayerPluginAsr diff --git a/packages/artplayer-plugin-asr/types/runtime.d.cts b/packages/artplayer-plugin-asr/types/runtime.d.cts new file mode 100644 index 000000000..cd00f3c7e --- /dev/null +++ b/packages/artplayer-plugin-asr/types/runtime.d.cts @@ -0,0 +1,11 @@ +import type { RuntimeFactory } from './artplayer-plugin-asr.js' + +declare const artplayerPluginAsr: RuntimeFactory +// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory. +declare namespace artplayerPluginAsr { + type AudioChunk = import('./artplayer-plugin-asr.js').AudioChunk + type RuntimeFactory = import('./artplayer-plugin-asr.js').RuntimeFactory + type RuntimeOption = import('./artplayer-plugin-asr.js').RuntimeOption + type RuntimeResult = import('./artplayer-plugin-asr.js').RuntimeResult +} +export = artplayerPluginAsr diff --git a/packages/artplayer-plugin-asr/types/runtime.d.mts b/packages/artplayer-plugin-asr/types/runtime.d.mts new file mode 100644 index 000000000..73dae478e --- /dev/null +++ b/packages/artplayer-plugin-asr/types/runtime.d.mts @@ -0,0 +1,5 @@ +import type { RuntimeFactory } from './artplayer-plugin-asr.js' + +declare const artplayerPluginAsr: RuntimeFactory +export default artplayerPluginAsr +export type { AudioChunk, RuntimeFactory, RuntimeOption, RuntimeResult } from './artplayer-plugin-asr.js' diff --git a/packages/artplayer-plugin-asr/types/runtime.d.ts b/packages/artplayer-plugin-asr/types/runtime.d.ts new file mode 100644 index 000000000..cd00f3c7e --- /dev/null +++ b/packages/artplayer-plugin-asr/types/runtime.d.ts @@ -0,0 +1,11 @@ +import type { RuntimeFactory } from './artplayer-plugin-asr.js' + +declare const artplayerPluginAsr: RuntimeFactory +// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory. +declare namespace artplayerPluginAsr { + type AudioChunk = import('./artplayer-plugin-asr.js').AudioChunk + type RuntimeFactory = import('./artplayer-plugin-asr.js').RuntimeFactory + type RuntimeOption = import('./artplayer-plugin-asr.js').RuntimeOption + type RuntimeResult = import('./artplayer-plugin-asr.js').RuntimeResult +} +export = artplayerPluginAsr diff --git a/refactor/baselines/asr-type-diagnostics.json b/refactor/baselines/asr-type-diagnostics.json new file mode 100644 index 000000000..517a7d49e --- /dev/null +++ b/refactor/baselines/asr-type-diagnostics.json @@ -0,0 +1,254 @@ +{ + "schemaVersion": 1, + "task": "PKG-ASR-04", + "sourceReport": "refactor/.cache/asr-package-types-3KzGBO/report.json", + "scope": "Observed strict installed consumers from frozen 2.0.0 and 2.1.0 archives with packed candidate core. Both old NodeNext ESM default imports are namespaces; direct-call diagnostics are retained, not counted as passing compatibility. Legal namespace consumers are verified separately.", + "cases": [ + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "diagnostics": [] + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "diagnostics": [] + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "diagnostics": [ + { + "file": "consumer.mts", + "line": 26, + "code": 2322, + "message": "Type '{ length: number; interval: number; sampleRate: number; autoHideTimeout: number; onAudioChunk(chunk: any): void; }' is not assignable to type 'never'." + }, + { + "file": "consumer.mts", + "line": 26, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 31, + "code": 7006, + "message": "Parameter 'chunk' implicitly has an 'any' type." + }, + { + "file": "consumer.mts", + "line": 37, + "code": 2322, + "message": "Type 'undefined' is not assignable to type 'never'." + }, + { + "file": "consumer.mts", + "line": 37, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 39, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 46, + "code": 2322, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' is not assignable to type 'PublishedFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(option?: PublishedOption | undefined): (art: Artplayer) => PublishedResult'." + }, + { + "file": "consumer.mts", + "line": 47, + "code": 2322, + "message": "Type 'PublishedFactory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")'." + }, + { + "file": "consumer.mts", + "line": 48, + "code": 2322, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' is not assignable to type 'PublishedFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(option?: PublishedOption | undefined): (art: Artplayer) => PublishedResult'." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 50, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 54, + "code": 2339, + "message": "Property 'onAudioChunk' does not exist on type 'never'." + } + ] + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "diagnostics": [] + }, + { + "plugin": "published-2.0.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "diagnostics": [] + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "diagnostics": [] + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "diagnostics": [] + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "diagnostics": [ + { + "file": "consumer.mts", + "line": 26, + "code": 2322, + "message": "Type '{ length: number; interval: number; sampleRate: number; autoHideTimeout: number; onAudioChunk(chunk: any): void; }' is not assignable to type 'never'." + }, + { + "file": "consumer.mts", + "line": 26, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 31, + "code": 7006, + "message": "Parameter 'chunk' implicitly has an 'any' type." + }, + { + "file": "consumer.mts", + "line": 37, + "code": 2322, + "message": "Type 'undefined' is not assignable to type 'never'." + }, + { + "file": "consumer.mts", + "line": 37, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 39, + "code": 2344, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' does not satisfy the constraint '(...args: any) => any'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(...args: any): any'." + }, + { + "file": "consumer.mts", + "line": 46, + "code": 2322, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' is not assignable to type 'PublishedFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(option?: PublishedOption | undefined): (art: Artplayer) => PublishedResult'." + }, + { + "file": "consumer.mts", + "line": 47, + "code": 2322, + "message": "Type 'PublishedFactory' is not assignable to type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")'." + }, + { + "file": "consumer.mts", + "line": 48, + "code": 2322, + "message": "Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' is not assignable to type 'PublishedFactory'.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' provides no match for the signature '(option?: PublishedOption | undefined): (art: Artplayer) => PublishedResult'." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 49, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 50, + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"/node_modules/artplayer-plugin-asr/types/artplayer-plugin-asr\")' has no call signatures." + }, + { + "file": "consumer.mts", + "line": 54, + "code": 2339, + "message": "Property 'onAudioChunk' does not exist on type 'never'." + } + ] + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "diagnostics": [] + }, + { + "plugin": "published-2.1.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "diagnostics": [] + } + ], + "fixtures": { + "refactor/fixtures/consumers/asr-published.ts": "7611ab9ef9765fe6a6ca5112589228d49012400bf0b9d9cede96ead8e63cffc2", + "refactor/fixtures/consumers/asr-esm-namespace.ts": "1e3b7475452f43f71e4e038c17368860fe2bc00a61451397fe609e9a3e078d98" + } +} diff --git a/refactor/baselines/asr-types-validation.json b/refactor/baselines/asr-types-validation.json new file mode 100644 index 000000000..6f0d97ccc --- /dev/null +++ b/refactor/baselines/asr-types-validation.json @@ -0,0 +1,363 @@ +{ + "schemaVersion": 1, + "task": "PKG-ASR-04", + "capturedAt": "2026-09-13T07:06:14.836Z", + "baseCommit": "0cf2af48400908fbe4217259e7d9fd37c1e15d1f", + "runtimeChanged": false, + "dependenciesAdded": [], + "versionChanged": false, + "installation": { + "node": "v24.21.0", + "yarn": "1.22.22", + "outsideWorkspace": true, + "offline": true, + "frozenReinstall": true, + "installedHashesVerified": true, + "workspaceLinks": false, + "runtimeExportsExecuted": true, + "packages": [ + { + "name": "artplayer", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\asr-package-types-nlZPaL\\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-asr", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\asr-package-types-nlZPaL\\artplayer-plugin-asr.tgz", + "sha256": "d784829c8fbc06f78d8a7647ef3530cec1cf3f5157a053bfbc58111d772eb543", + "files": { + "package/ARCHITECTURE.md": "a46a1abf28a4c783a4cec2983c4ba276b7e9e33e8446d811cc9c49c1f5231ef8", + "package/README.md": "d8eec6fafd308a3f52c16548a27ea07c2cb247d950b3e155d94f98e010e6b994", + "package/dist/artplayer-plugin-asr.js": "533b60d6348764fa460c2436cc1197b95544051fb4d1ec4d950fec9f88e1c5cb", + "package/dist/artplayer-plugin-asr.legacy.js": "c36ebbea3a4e5f30b2736ffb6a964d45e330b0911f51e32d9cab44397ec16912", + "package/dist/artplayer-plugin-asr.mjs": "751201241a848cdc26235334ce7603b174a90bf4ac793ff3f85182b1783f6996", + "package/package.json": "781b1eebefdff5fdc234b793ff9d6d8263275115ccff4c4d90c55eca942f2444", + "package/types/artplayer-plugin-asr.d.ts": "8fce55a45c368dc95fc6169750d2157ad57f683f62b586b8162ac720e1efdf42", + "package/types/runtime.d.cts": "2965dccc49a3a75c3b16c0c13501077b8ac199e78bac2d19d57be3d811583606", + "package/types/runtime.d.mts": "ff0769d3e408d66c7d38574171ff2621456afccc50ee8191ca4ee394dc57666e", + "package/types/runtime.d.ts": "2965dccc49a3a75c3b16c0c13501077b8ac199e78bac2d19d57be3d811583606" + } + } + ], + "published": [ + { + "label": "published-2.0.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-asr-2.0.0.tgz", + "sha256": "faa726b9536306664d935c95367230e0c622bd046faf636fc2dcef4bbce4cd47" + }, + { + "label": "published-2.1.0", + "archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-asr-2.1.0.tgz", + "sha256": "8bc3cd93f78ce99e5c09cc546c770a63e8aea5729d3d584134ce04d826b7708b" + } + ] + }, + "strict": { + "skipLibCheck": false, + "ambientTypes": [], + "sourceProjects": [ + "tsconfig.json", + "packages/artplayer-plugin-asr/tsconfig.json" + ], + "installedResolution": "Only isolated consumer and executing compiler standard libraries", + "compilerVersions": [ + "5.9.3", + "4.3.5" + ] + }, + "matrix": [ + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "validConsumerDiagnostics": 15, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 15, + "namespaceChecked": true, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.0.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.0.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "validConsumerDiagnostics": 15, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 15, + "namespaceChecked": true, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.1.0", + "compiler": "5.9.3", + "mode": "bundler-esm", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "published-2.1.0", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 0, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-cjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "nodenext-esm", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 15, + "namespaceChecked": true, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "bundler-esm", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "4.3.5", + "mode": "node10-commonjs", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "5.9.3", + "mode": "node10-commonjs-no-interop", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + }, + { + "plugin": "candidate", + "compiler": "4.3.5", + "mode": "node10-commonjs-no-interop", + "validConsumerDiagnostics": 0, + "invalidStatementsRejected": 12, + "originalDirectDiagnostics": 0, + "namespaceChecked": false, + "namespaceDiagnostics": 0 + } + ], + "historicalLimits": "Both old releases have 15 NodeNext ESM direct-call diagnostics. These are frozen in asr-type-diagnostics.json and remain on candidate root; legal namespace consumers pass all three packages. Runtime ESM default import is accurate through the additive /runtime entry.", + "correctedDuringReview": [ + "Added missing runtime named type exports to CommonJS/Node10 declarations", + "Retained original root namespace declarations after detecting replacement-object incompatibility", + "Excluded implementation tsconfig from actual package", + "Used a compiler-portable invalid buffer example; old TS typed-array structural acceptance is not claimed as rejection" + ], + "inputs": { + "packages/artplayer-plugin-asr/.npmignore": "145c5fa0341195e21cf13df3423a057282f8ea374e9a1b3065e2280e1ce1c5c1", + "packages/artplayer-plugin-asr/package.json": "781b1eebefdff5fdc234b793ff9d6d8263275115ccff4c4d90c55eca942f2444", + "packages/artplayer-plugin-asr/tsconfig.json": "583e4ac1afa99e3a29c7f899df9fdeefcbd14d3847d69143e3fa6a8facac6d99", + "packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts": "dca6dfb5a6b6d58a891412c29d857214c95834e60bdabf0aa9d2fec48f31ec12", + "packages/artplayer-plugin-asr/types/runtime.d.ts": "2965dccc49a3a75c3b16c0c13501077b8ac199e78bac2d19d57be3d811583606", + "packages/artplayer-plugin-asr/types/runtime.d.cts": "2965dccc49a3a75c3b16c0c13501077b8ac199e78bac2d19d57be3d811583606", + "packages/artplayer-plugin-asr/types/runtime.d.mts": "ff0769d3e408d66c7d38574171ff2621456afccc50ee8191ca4ee394dc57666e", + "refactor/scripts/asr-package-types.mjs": "00de7c03cba39bed912933d58d686fb0b4aea374944c460ee70a7e404119d259", + "refactor/fixtures/consumers/asr-published.ts": "7611ab9ef9765fe6a6ca5112589228d49012400bf0b9d9cede96ead8e63cffc2", + "refactor/fixtures/consumers/asr-esm-namespace.ts": "1e3b7475452f43f71e4e038c17368860fe2bc00a61451397fe609e9a3e078d98", + "test/types/asr-public.ts": "19eac65cf88ccf908f175dc03243f6933b62dfa3d254525501c2396a571c8843", + "test/types/asr-source.ts": "f2541df4f4e7976a97fa9f31c730298e98fea4749eef2a2e098da539fbc240ca", + "scripts/typecheck.mjs": "96b166178bada2ab2130af1ea422e01965cc6467746ecf605e87ec3b29068309", + "package.json": "a95566e7e6825cda949428a71d11cbcc39c1ba3651fc631ed8511ca5e078d340" + }, + "evidence": [ + { + "file": "refactor/.cache/asr-package-types-nlZPaL/report.json", + "sha256": "5b32bda6481f74a9db7fe68f54f36a9752deb2cbb8e56b2a42cdc061297b8df7" + }, + { + "file": "refactor/.cache/asr04-installed-final.log", + "sha256": "cebbdebc4b5419f16e419a33e0ba5ae33102091c3dcfd2fb95d541f0e2640634" + }, + { + "file": "refactor/.cache/asr04-strict-final.log", + "sha256": "0d6e90ea1b4dadf17e3dd5dee44c3894e17455e124d2a7ab31d5850249ec72b7" + }, + { + "file": "refactor/.cache/asr04-lint-final.log", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "file": "refactor/.cache/asr04-artifact-identity.log", + "sha256": "0ad9dc4536c26e81edf671c68cc218aef044e3655bc1ba91afbcf42187783215" + } + ], + "unchangedRuntimeEvidence": "refactor/baselines/asr-ownership-validation.json", + "limitation": [ + "No full CI or browser rerun for unchanged JS", + "Node 24 actual installs only; full Node/OS distribution matrix remains later acceptance", + "ASR-05 native fallback/volume/CORS/device evidence remains incomplete", + "ASR-06 release documentation and distribution remains incomplete" + ], + "releaseReady": false +} diff --git a/refactor/changes/2026-09-13-PKG-ASR-04-public-types.md b/refactor/changes/2026-09-13-PKG-ASR-04-public-types.md new file mode 100644 index 000000000..3f6411122 --- /dev/null +++ b/refactor/changes/2026-09-13-PKG-ASR-04-public-types.md @@ -0,0 +1,53 @@ +# PKG-ASR-04:ASR 公共类型与隔离安装 + +本任务延续 ASR-03 的 8 个严格 TS 模块,处理公共类型与安装边界。保留根入口及 +`/legacy` 的历史工厂形状,新增 `/runtime` 精确描述回调返回字幕、异步 stop。 +不改运行时 JS、不新增依赖、不修改版本;大版本与发布仍按后续任务统一执行。 + +## 兼容决策 + +- 根入口仍是原来的可选参数、同步注册、void stop、void/Promise callback; + 旧工厂和插件结果可双向赋值,Parameters/ReturnType 提取也保留。 +- `/runtime` 加载同一 main/ESM 产物,只提供准确的类型入口:回调可同步或异步返回 + string/null/void,stop 返回 Promise,工厂具有只读 self.default。 + 不将 self.default 加到旧工厂类型,避免要求用户的替换函数提供额外属性。 +- root 与 runtime 都导出 AudioChunk/RuntimeOption/RuntimeResult/RuntimeFactory。 + runtime 的 ESM 使用 `.d.mts`,CommonJS/Node10 使用 export assignment。 + 子代理发现后者漏了命名类型,已补纯类型 namespace 并加入四类型等价回归。 +- 旧根声明在 NodeNext ESM 下把 default import 描述成命名空间,合法的 + `asr.default()`、`namespace.default.default()` 和 `{default: factory}` 替换必须保留。 + 原拟直接工厂 `.d.mts` 会破坏这些旧类型用法,已撤回;准确 ESM 默认导入用 `/runtime`。 + 这是声明兼容测试,不能推断旧 JS 支持所有双层 default 调用。 + +包内 README 给出新入口用法,ARCHITECTURE 记录维护限制。实际打包发现 tsconfig +泄漏,已补 `.npmignore`,保留所有历史 dist/types 文件。源码 TS 与测试不进入包。 + +## 验证与证据 + +`yarn test:asr-types-package` 实际打包核心与 ASR,在工作区外逐个安装 frozen +2.0.0、2.1.0 及候选包。离线安装后用 frozen lockfile 再安装;检查锁文件不变、 +无工作区符号链接、全部包文件与 archive 哈希相符。类型程序只能读取该临时消费者 +及当前编译器标准库;strict、skipLibCheck:false、types:[],不靠 ambient 类型兜底。 + +- 2 个旧发布包各 5 种配置;每包 4 种直接用法通过,NodeNext ESM 原有 15 条诊断 + 按完整消息冻结。合法 ESM 命名空间 fixture 在两个旧包及候选包均通过。 +- 候选 7 种配置:TS 5.9.3 Node10/NodeNext CJS/NodeNext ESM/Bundler,TS 4.3.5 + Node10,以及两编译器 Node10 关闭 interop。每种公共用法零诊断,12 项负例在 + 各自语句行被拒绝;原 direct fixture 的 NodeNext ESM 诊断也与旧包一致。 +- 实际 Node 24.21.0 import/require 验证三候选入口、root/runtime 实现身份、 + self.default 不可枚举、legacy 默认导入身份;旧包验证各自 CJS 和 ESM 导出。 +- 根项目及 ASR 分包严格检查通过,source/public 类型边界加入持续检查。 + 定向 ESLint 和台账测试通过,ASR 构建 JS 与 ASR-03 HEAD 逐字节相同。 + +类型反例中的 typed-array 与 ArrayBuffer 在旧 TS 标准库下结构可赋值,不能声称 +TS 4.3.5 会拒绝。负例使用所有目标编译器都应拒绝的字符串 buffer,保持公开 +ArrayBuffer 字段不变;没有通过缩窄字段或升级最低编译器绕过兼容性。 + +具体 archive、输入、日志哈希及矩阵摘要见 +[验证记录](../baselines/asr-types-validation.json),旧 direct 诊断见 +[冻结声明诊断](../baselines/asr-type-diagnostics.json)。此前 ASR-03 的音频/浏览器 +证据继续对应未变的 JS;本任务没有重复整仓 CI 或浏览器回归,也不声称完成真实 +fallback、音量/静音、CORS、物理设备或全 Node 版本验收。这些仍归 ASR-05/06。 + +子代理独立提供公共/历史消费者与声明审查,主代理修复声明、运行实际隔离安装、 +整合证据和提交。回退本任务移除新增类型入口与测试,不改变 ASR-03 运行时修复。 diff --git a/refactor/fixtures/consumers/asr-esm-namespace.ts b/refactor/fixtures/consumers/asr-esm-namespace.ts new file mode 100644 index 000000000..513301203 --- /dev/null +++ b/refactor/fixtures/consumers/asr-esm-namespace.ts @@ -0,0 +1,46 @@ +import type Artplayer from 'artplayer' +import asr from 'artplayer-plugin-asr' +import * as namespace from 'artplayer-plugin-asr' + +// Compile this fixture as .mts with NodeNext: published declarations expose +// a CommonJS declaration namespace even though the runtime import is ESM. +interface HistoricalOption { + length?: number + interval?: number + sampleRate?: number + autoHideTimeout?: number + onAudioChunk?: (chunk: { pcm: ArrayBuffer, wav: ArrayBuffer }) => void | Promise +} + +interface HistoricalResult { + name: 'artplayerPluginAsr' + stop: () => void + hide: () => void + append: (subtitle: string) => void +} + +type HistoricalFactory = (option?: HistoricalOption) => (art: Artplayer) => HistoricalResult +interface HistoricalModule { default: HistoricalFactory } +const result: ReturnType> = { + name: 'artplayerPluginAsr', + stop() {}, + hide() {}, + append(_subtitle: string) {}, +} +const replacementFactory: HistoricalFactory = (_option?: HistoricalOption) => (_art: Artplayer) => result +const replacementModule: typeof asr = { default: replacementFactory } +const assignToOld: HistoricalModule = asr +const assignFromOld: typeof asr = assignToOld +const namespaceModule: typeof namespace.default = replacementModule +const factoryToOld: HistoricalFactory = asr.default +const factoryFromOld: typeof asr.default = replacementFactory +const option: Parameters[0] = { + onAudioChunk: async (_chunk): Promise => {}, +} +const absent: Parameters[0] = undefined +declare const art: Artplayer +const installed: HistoricalResult = asr.default(option)(art) +const namespaceResult: HistoricalResult = namespace.default.default(absent)(art) +const stop: void = installed.stop() +const append: void = namespaceResult.append('Old namespace import.') +void [assignFromOld, namespaceModule, factoryToOld, factoryFromOld, stop, append] diff --git a/refactor/fixtures/consumers/asr-published.ts b/refactor/fixtures/consumers/asr-published.ts new file mode 100644 index 000000000..bd46f57fe --- /dev/null +++ b/refactor/fixtures/consumers/asr-published.ts @@ -0,0 +1,55 @@ +import Artplayer from 'artplayer' +import asr from 'artplayer-plugin-asr' +import * as namespace from 'artplayer-plugin-asr' + +interface PublishedChunk { + pcm: ArrayBuffer + wav: ArrayBuffer +} + +interface PublishedOption { + length?: number + interval?: number + sampleRate?: number + autoHideTimeout?: number + onAudioChunk?: (chunk: PublishedChunk) => void | Promise +} + +interface PublishedResult { + name: 'artplayerPluginAsr' + stop: () => void + hide: () => void + append: (subtitle: string) => void +} + +type PublishedFactory = (option?: PublishedOption) => (art: Artplayer) => PublishedResult +const option: Parameters[0] = { + length: 3, + interval: 100, + sampleRate: 16000, + autoHideTimeout: 10000, + onAudioChunk(chunk) { + const pcm: ArrayBuffer = chunk.pcm + const wav: ArrayBuffer = chunk.wav + void [pcm, wav] + }, +} +const empty: Parameters[0] = undefined +const asyncOption: PublishedOption = { onAudioChunk: async (_chunk: PublishedChunk): Promise => {} } +const result: ReturnType> = { + name: 'artplayerPluginAsr', + stop() {}, + hide() {}, + append(_subtitle: string) {}, +} +const replacement: PublishedFactory = (_option?: PublishedOption) => (_art: Artplayer) => result +const assignToOld: PublishedFactory = asr +const assignFromOld: typeof asr = replacement +const namespaceFactory: PublishedFactory = namespace.default +const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [asr(option), asr(asyncOption), asr(), namespace.default(empty)] }) +const installed: PublishedResult = asr(option)(art) +const stop: void = installed.stop() +const hide: void = installed.hide() +const append: void = installed.append('Historical subtitle.') +const callback: ((chunk: PublishedChunk) => void | Promise) | undefined = option.onAudioChunk +void [assignToOld, assignFromOld, namespaceFactory, stop, hide, append, callback] diff --git a/refactor/plan.md b/refactor/plan.md index c7ee6c3b6..21dfbca44 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 225 项,范围 22 个包及工作区/示例。 -状态:todo 83 / doing 16 / blocked 0 / done 126 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 82 / doing 16 / blocked 0 / done 127 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -254,7 +254,7 @@ | PKG-ASR-01 | artplayer-plugin-asr
核对包契约与历史用法 | BASE-05 | length/interval/sampleRate/onAudioChunk、append/hide/stop 的真实能力 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done | | PKG-ASR-02 | artplayer-plugin-asr
建立特有行为与错误测试 | PKG-ASR-01, ENG-03, ENG-05 | PCM/WAV、chunk 时序、回调慢/拒绝、重复初始化与停止 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done | | PKG-ASR-03 | artplayer-plugin-asr
整理内部职责与资源 | PKG-ASR-02, CORE-10, CORE-18 | AudioContext/Worklet/Stream 生命周期和背压分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done | -| PKG-ASR-04 | artplayer-plugin-asr
迁移自有源码和公开类型 | PKG-ASR-03, ENG-04, ENG-06, CORE-07 | 主线程/Worklet/音频 buffer 与异步回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo | +| PKG-ASR-04 | artplayer-plugin-asr
迁移自有源码和公开类型 | PKG-ASR-03, ENG-04, ENG-06, CORE-07 | 主线程/Worklet/音频 buffer 与异步回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done | | PKG-ASR-05 | artplayer-plugin-asr
验证新旧核心和组合 | PKG-ASR-04, CORE-22 | 真实 WebAudio 分块、播放暂停/切源/销毁,不引入网络 ASR | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo | | PKG-ASR-06 | artplayer-plugin-asr
验证分发并同步文档 | PKG-ASR-05, ENG-07 | asr.js、Worklet 资源、输出格式与声明证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo | @@ -529,6 +529,7 @@ - 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) +- PKG-ASR-04: [记录](changes/2026-09-13-PKG-ASR-04-public-types.md) [记录](baselines/asr-types-validation.json) [记录](baselines/asr-type-diagnostics.json) - PKG-ADS-01: [记录](baselines/ads-release.json) [记录](baselines/ads-contract.md) [记录](baselines/ads-contract-validation.json) [记录](changes/2026-09-12-PKG-ADS-01-contract.md) [记录](scripts/ads-contract.test.mjs) - PKG-ADS-02: [记录](changes/2026-09-12-PKG-ADS-02-tests.md) [记录](ads-validation.md) [记录](baselines/ads-validation.json) - PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index aaa999a03..92811e1d6 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,19 @@ # 进度与证据 +## PKG-ASR-04 公共类型与安装兼容完成 + +保留根入口及 legacy 的旧工厂、void stop 和 NodeNext ESM 命名空间形状;新增 +runtime 精确类型入口,描述字幕回调与 Promise stop。子代理补消费者并发现 +CommonJS 命名类型遗漏、旧 ESM 替换对象兼容风险,均修复后由主代理隔离安装验收。 +实际 2.0/2.1/候选共 17 配置:候选 7 配置通过,每种 12 负例按语句拒绝;两旧包 +各有 1 组原生 NodeNext direct 声明错误单独冻结,三个包的合法 namespace 用法通过。 +根/分包 strict、定向 lint、包字节与 frozen reinstall 检查通过,修复 tsconfig 打包泄漏。 +见[变更](changes/2026-09-13-PKG-ASR-04-public-types.md)与[证据](baselines/asr-types-validation.json)。 +225 项:127 done、16 doing、82 todo;205 风险,ASR-TYPE-01 闭环。 +下一步 ASR-05 原生 fallback/音量/跨源/设备组合与 ASR-06 发行;运行时 JS 未改, +沿用 ASR-03 浏览器证据,本项不等于 ASR 整包发布验收,没有推送或发布。 + + ## PKG-ASR-03 TS 拆分与音频生命周期完成 生产源码拆为 8 个严格 TS 模块;队列保留半块/尾部,识别回调背压、拒绝恢复、 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index da7acd235..5bce4e423 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -206,6 +206,6 @@ | JASSUB-EXPORT-01 | open / 已复现 | JASSUB historical CommonJS object.default changes to a direct factory in 1.1.0 | PKG-JASSUB-04, PKG-JASSUB-06 | | CORE-DEFAULTS-SSR-01 | resolved / 已复现 | Reading static defaults throws when navigator is absent | CORE-25 | | ENG-DIST-PUBLIC-01 | resolved / 已复现 | Vite copied authored declarations into library distribution | ENG-12 | -| ASR-TYPE-01 | open / 源码/产物事实 | ASR declarations omit asynchronous stop and callback text results | PKG-ASR-04 | +| ASR-TYPE-01 | resolved / 已复现 | ASR declarations omit asynchronous stop and callback text results | PKG-ASR-04 | | ASR-CHUNK-01 | resolved / 已复现 | ASR tick discards underfilled chunks and overrun samples in source | PKG-ASR-02, PKG-ASR-03 | | ASR-LIFE-01 | resolved / 已复现 | ASR partial initialization, pause resume and stale callback cleanup need controlled evidence | PKG-ASR-02, PKG-ASR-03, PKG-ASR-05 | diff --git a/refactor/risks.json b/refactor/risks.json index e11ca9a0d..767559716 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -4545,8 +4545,8 @@ { "id": "ASR-TYPE-01", "title": "ASR declarations omit asynchronous stop and callback text results", - "confirmation": "source-observed", - "status": "open", + "confirmation": "reproduced", + "status": "resolved", "owners": [ "PKG-ASR-04" ], @@ -4555,7 +4555,15 @@ "refactor/baselines/asr-release.json" ], "compatibleResolution": "Keep historical assignment compatibility while supplying accurate asynchronous consumption.", - "closureCriteria": "Actual old/new installed TS consumers cover void callback, Promise, returned text and factory substitution." + "closureCriteria": "Actual old/new installed TS consumers cover void callback, Promise, returned text and factory substitution.", + "resolutionEvidence": [ + "refactor/changes/2026-09-13-PKG-ASR-04-public-types.md", + "refactor/baselines/asr-types-validation.json", + "refactor/scripts/asr-package-types.mjs", + "test/types/asr-public.ts", + "refactor/fixtures/consumers/asr-esm-namespace.ts" + ], + "resolutionRationale": "Root/legacy preserve historical void callback and stop, factory substitution and legal NodeNext namespace shape. Additive runtime supplies text/null/void callbacks and Promise stop with consistent named types. Actual isolated 2.0/2.1/candidate installs, seven candidate compiler configurations and 12 per-mode invalid statements verify declarations. Existing old NodeNext direct-call diagnostics remain explicit, not candidate success." }, { "id": "ASR-CHUNK-01", diff --git a/refactor/scripts/asr-package-types.mjs b/refactor/scripts/asr-package-types.mjs new file mode 100644 index 000000000..0744b621b --- /dev/null +++ b/refactor/scripts/asr-package-types.mjs @@ -0,0 +1,166 @@ +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 { verifyAsrContract } from './asr-contract.mjs' +import { hash, readMember } from './releases.mjs' + +const publicFixture = fs.readFileSync(path.join(workspace, 'test/types/asr-public.ts'), 'utf8') +const publishedFixture = fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/asr-published.ts'), 'utf8') +const standardModes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']] + +function typecheck(consumer, compiler, mode, candidate) { + const next = mode.startsWith('nodenext') + const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts' + const filename = path.join(consumer, `consumer.${extension}`) + const files = [filename] + if (candidate && mode !== 'bundler-esm' && mode !== 'nodenext-esm') { + const commonjs = path.join(consumer, `commonjs.${extension}`) + fs.writeFileSync(commonjs, `import asr = require('artplayer-plugin-asr'); +import legacy = require('artplayer-plugin-asr/legacy'); +import runtime = require('artplayer-plugin-asr/runtime'); +import type Artplayer from 'artplayer'; +const replacement: typeof asr.default = () => (_art: Artplayer) => ({name: 'artplayerPluginAsr', stop() {}, hide() {}, append(_text: string) {}}); +const legacyReplacement: typeof legacy.default = replacement; +asr.default(); legacy.default(); runtime({onAudioChunk: async () => 'Text.'}); runtime.default(); +declare const art: Artplayer; +const stopped: Promise = runtime()(art).stop(); +void [legacyReplacement, stopped];`) + files.push(commonjs) + } + const options = { + strict: true, + noEmit: true, + skipLibCheck: false, + types: [], + esModuleInterop: !mode.endsWith('-no-interop'), + target: compiler.ScriptTarget.ES2020, + lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], + module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, + moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs, + } + function compile(code) { + fs.writeFileSync(filename, code) + const program = compiler.createProgram(files, 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()))), `Types escaped installed consumer: ${actual}`) + } + return compiler.getPreEmitDiagnostics(program).map(item => ({ + file: item.file ? path.relative(consumer, item.file.fileName).replaceAll('\\', '/') : null, + line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, + code: item.code, + message: compiler.flattenDiagnosticMessageText(item.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), ''), + })) + } + let source = candidate + ? mode.endsWith('-no-interop') ? publicFixture.replace('import runtime from \'artplayer-plugin-asr/runtime\'', 'import runtime = require(\'artplayer-plugin-asr/runtime\')') : publicFixture + : publishedFixture + if (candidate && mode === 'nodenext-esm') { + source = source + .replace('import legacy from \'artplayer-plugin-asr\'', 'import legacyModule from \'artplayer-plugin-asr\'\nconst legacy = legacyModule.default') + .replace('import legacyEntry from \'artplayer-plugin-asr/legacy\'', 'import legacyEntryModule from \'artplayer-plugin-asr/legacy\'\nconst legacyEntry = legacyEntryModule.default') + .replace('namespace.default', 'namespace.default.default') + } + const diagnostics = compile(source) + const invalid = candidate ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : [] + if (candidate) { + const expectedLines = [] + let line = 1 + for (const text of source.split('\n')) { + if (text.startsWith('// @ts-expect-error')) + expectedLines.push(line) + else line++ + } + assert.deepEqual(invalid.map(item => item.line).sort((a, b) => a - b), expectedLines, 'Each invalid statement must fail at its own line') + } + const published = candidate ? compile(publishedFixture) : diagnostics + const namespace = mode === 'nodenext-esm' ? compile(fs.readFileSync(path.join(workspace, 'refactor/fixtures/consumers/asr-esm-namespace.ts'), 'utf8')) : [] + return { compiler: compiler.version, mode, diagnostics, invalid, published, namespace } +} + +async function main() { + assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:asr-types-package') + const yarn = process.env.npm_execpath + assert(yarn && fs.existsSync(yarn)) + const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/asr-package-types-')) + console.log(`ASR isolated package evidence: ${output}`) + const contract = await verifyAsrContract() + const expected = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/asr-type-diagnostics.json'), 'utf8')) + for (const [file, digest] of Object.entries(expected.fixtures)) + assert.equal(hash(fs.readFileSync(path.join(workspace, file), 'utf8').replaceAll('\r\n', '\n')), digest, `Frozen ASR fixture changed: ${file}`) + const packages = [] + for (const name of ['artplayer', 'artplayer-plugin-asr']) { + 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 matrix = [] + const core = packages[0] + const candidates = [ + ...[...contract.baseline.previous].reverse().concat(contract.baseline.release).map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })), + { ...packages[1], label: 'candidate' }, + ] + for (const plugin of candidates) { + const candidate = plugin.label === 'candidate' + const consumer = consumerDirectory() + try { + writeJson(path.join(consumer, 'package.json'), { name: 'asr-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 must not be a workspace link') + for (const [member, expected] of Object.entries(pkg.files)) + assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`) + } + const runtimeFile = path.join(consumer, 'exports.cjs') + fs.writeFileSync(runtimeFile, candidate + ? `const assert = require('node:assert/strict'); +for (const name of ['artplayer-plugin-asr', 'artplayer-plugin-asr/legacy', 'artplayer-plugin-asr/runtime']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(Object.getOwnPropertyDescriptor(factory, 'default').enumerable, false); assert.equal(typeof factory({}), 'function'); } +assert.equal(require('artplayer-plugin-asr/runtime'), require('artplayer-plugin-asr')); +Promise.all(['artplayer-plugin-asr', 'artplayer-plugin-asr/runtime', 'artplayer-plugin-asr/legacy'].map(name => import(name))).then(([root, runtime, legacy]) => { assert.deepEqual(Object.keys(root), ['default']); assert.deepEqual(Object.keys(runtime), ['default']); assert.equal(runtime.default, root.default); assert.equal(root.default.default, root.default); assert.equal(legacy.default, require('artplayer-plugin-asr/legacy')); }).catch(error => { console.error(error); process.exitCode = 1; });` + : `const assert = require('node:assert/strict'); const value = require('artplayer-plugin-asr'); assert.equal(typeof value, '${plugin.version === '2.1.0' ? 'function' : 'object'}'); assert.equal(typeof (value.default || value)({}), 'function'); assert.equal(typeof value.default, '${plugin.version === '2.1.0' ? 'undefined' : 'function'}'); import('artplayer-plugin-asr').then(module => { assert.equal(typeof module.default, 'function'); assert.equal(typeof module.default({}), 'function'); }).catch(error => { console.error(error); process.exitCode = 1; });`) + fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer)) + const modes = candidate ? [...standardModes, [ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop']] : standardModes + for (const [compiler, mode] of modes) { + const result = { plugin: plugin.label, ...typecheck(consumer, compiler, mode, candidate) } + matrix.push(result) + writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}.json`), result) + console.log(`${plugin.label} TS ${compiler.version} ${mode}: ${result.diagnostics.length} diagnostics; ${result.invalid.length} negative diagnostics`) + } + } + finally { + removeConsumer(consumer) + } + } + writeJson(path.join(output, 'report.json'), { suite: 'asr-isolated-package-types', introducedBy: 'PKG-ASR-04', packages, published: candidates.slice(0, -1).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix }) + for (const result of matrix) { + const baseline = result.plugin === 'candidate' ? [] : expected.cases.find(item => item.plugin === result.plugin && item.compiler === result.compiler && item.mode === result.mode)?.diagnostics + assert.deepEqual(result.diagnostics, baseline, `${result.plugin} TS ${result.compiler} ${result.mode}`) + assert.deepEqual(result.namespace, [], `Historical ESM namespace: ${result.plugin}`) + if (result.plugin === 'candidate') { + const publishedBaseline = result.mode === 'nodenext-esm' ? expected.cases.find(item => item.plugin === 'published-2.1.0' && item.compiler === result.compiler && item.mode === result.mode).diagnostics : [] + assert.deepEqual(result.published, publishedBaseline, `Historical direct consumer: ${result.compiler} ${result.mode}`) + assert.equal(result.invalid.length, 12, 'Installed declarations must reject all 12 invalid uses') + } + } + console.log(`ASR installed matrix verified: ${matrix.length} cases; ${output}`) +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index 0b70fd2f7..4440d29ba 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -2682,11 +2682,15 @@ "ENG-06", "CORE-07" ], - "status": "todo", + "status": "done", "risk": "H", "deliverable": "主线程/Worklet/音频 buffer 与异步回调类型", "acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容", - "evidence": [] + "evidence": [ + "changes/2026-09-13-PKG-ASR-04-public-types.md", + "baselines/asr-types-validation.json", + "baselines/asr-type-diagnostics.json" + ] }, { "id": "PKG-ASR-05", diff --git a/scripts/typecheck.mjs b/scripts/typecheck.mjs index 8879bdce6..9e09b89d0 100644 --- a/scripts/typecheck.mjs +++ b/scripts/typecheck.mjs @@ -73,6 +73,15 @@ export function runTypechecks() { console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`) } for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) { + for (const fixture of ['test/types/asr-public.ts', mode === 'nodenext-esm' ? 'refactor/fixtures/consumers/asr-esm-namespace.ts' : 'refactor/fixtures/consumers/asr-published.ts']) { + let source = fs.readFileSync(path.join(root, fixture), 'utf8') + if (fixture === 'test/types/asr-public.ts' && mode === 'nodenext-esm') { + source = source.replace('import legacy from \'artplayer-plugin-asr\'', 'import legacyModule from \'artplayer-plugin-asr\'\nconst legacy = legacyModule.default') + .replace('import legacyEntry from \'artplayer-plugin-asr/legacy\'', 'import legacyEntryModule from \'artplayer-plugin-asr/legacy\'\nconst legacyEntry = legacyEntryModule.default') + .replace('namespace.default', 'namespace.default.default') + } + assert.deepEqual(checkConsumer(compiler, mode, source), [], `ASR consumer failed: TS ${compiler.version} ${mode}`) + } for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/dash-control.ts', 'test/types/ads.ts', 'refactor/fixtures/consumers/ads-published.ts', 'refactor/fixtures/consumers/ads-workspace.ts', 'refactor/fixtures/consumers/dash-control-legacy.ts', 'test/types/audio-track.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) { assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`) } diff --git a/test/types/asr-public.ts b/test/types/asr-public.ts new file mode 100644 index 000000000..317b826bf --- /dev/null +++ b/test/types/asr-public.ts @@ -0,0 +1,101 @@ +import type { AsrPluginInstance, AsrPluginOption, AudioChunk, RuntimeFactory, RuntimeOption, RuntimeResult } from 'artplayer-plugin-asr' +import type { AudioChunk as EntryChunk, RuntimeFactory as EntryFactory, RuntimeOption as EntryOption, RuntimeResult as EntryResult } from 'artplayer-plugin-asr/runtime' +import Artplayer from 'artplayer' +import legacy from 'artplayer-plugin-asr' +import * as namespace from 'artplayer-plugin-asr' +import legacyEntry from 'artplayer-plugin-asr/legacy' +import runtime from 'artplayer-plugin-asr/runtime' + +type Equal = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false +type Assert = T +type RuntimeEntryTypes = Assert> + +interface HistoricalOption { + length?: number + interval?: number + sampleRate?: number + autoHideTimeout?: number + onAudioChunk?: (chunk: { pcm: ArrayBuffer, wav: ArrayBuffer }) => void | Promise +} + +interface HistoricalResult { + name: 'artplayerPluginAsr' + stop: () => void + hide: () => void + append: (subtitle: string) => void +} + +type HistoricalFactory = (option?: HistoricalOption) => (art: Artplayer) => HistoricalResult +type RootArguments = Assert, [option?: AsrPluginOption]>> +type RootOption = Assert> +type RootResult = Assert>, AsrPluginInstance>> +type RootShape = Assert> +type RootFactory = Assert> +type RootStop = Assert, void>> +type ChunkShape = Assert> +type PreciseArguments = Assert, [option?: RuntimeOption]>> +type PreciseResult = Assert>, RuntimeResult>> +type PreciseStop = Assert, Promise>> +type PreciseFactory = Assert> +type PreciseCallback = Assert, (chunk: AudioChunk) => string | void | null | Promise>> + +const replacement: HistoricalFactory = (_option?: HistoricalOption) => (_art: Artplayer) => ({ + name: 'artplayerPluginAsr', + stop() {}, + hide() {}, + append(_subtitle: string) {}, +}) +const assignToOld: HistoricalFactory = legacy +const assignFromOld: typeof legacy = replacement +const namespaceFactory: HistoricalFactory = namespace.default +const explicitLegacy: HistoricalFactory = legacyEntry +const option: AsrPluginOption = { onAudioChunk: async (_chunk: AudioChunk): Promise => {} } +const absent: Parameters[0] = undefined +const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [legacy(option), runtime(), legacyEntry(absent)] }) +const old = legacy(option)(art) +const oldReturns: void[] = [old.stop(), old.hide(), old.append('Subtitle.')] +const bareRoot: typeof legacy = replacement + +const runtimeOption: RuntimeOption = { length: 2, interval: 250, sampleRate: 48000, autoHideTimeout: 3000, onAudioChunk: async (chunk) => { + const pcm: ArrayBuffer = chunk.pcm + const wav: ArrayBuffer = chunk.wav + void [pcm, wav] + return 'Recognized subtitle.' +} } +const correct: RuntimeResult = runtime(runtimeOption)(art) +const stopped: Promise = correct.stop() +const alias: RuntimeFactory = runtime.default.default +const aliasResult: RuntimeResult = alias(undefined)(art) +runtime({ onAudioChunk: () => 'Synchronous subtitle.' }) +runtime({ onAudioChunk: () => null }) +runtime({ onAudioChunk: () => undefined }) +runtime({ onAudioChunk: async () => {} }) +runtime({ onAudioChunk: async () => null }) +runtime(option) + +// @ts-expect-error Root options preserve the historical void/Promise callback shape. +legacy({ onAudioChunk: () => 'Only the precise entry describes returned subtitles.' }) +// @ts-expect-error Root stop extraction remains void for historical replacement factories. +const rootPromise: Promise = old.stop() +// @ts-expect-error The legacy factory type must not demand or expose a self default alias. +bareRoot.default() +// @ts-expect-error Accurate stop is asynchronous. +const synchronous: void = correct.stop() +// @ts-expect-error Accurate result replacements cannot return void from stop. +const wrongStop: RuntimeResult = { name: 'artplayerPluginAsr', stop() {}, hide() {}, append(_text: string) {} } +// @ts-expect-error Callback results do not include booleans. +runtime({ onAudioChunk: () => true }) +// @ts-expect-error Async callback results do not include booleans. +runtime({ onAudioChunk: async () => true }) +// @ts-expect-error Options use numeric sample rates. +runtime({ sampleRate: '16000' }) +// @ts-expect-error Unknown options remain rejected. +runtime({ unknown: true }) +// @ts-expect-error Registration requires an Artplayer instance. +runtime()() +// @ts-expect-error Append accepts a string. +correct.append(123) +// @ts-expect-error PCM is an ArrayBuffer rather than encoded text. +const invalidChunk: AudioChunk = { pcm: 'raw audio', wav: new ArrayBuffer(4) } +void [assignToOld, assignFromOld, namespaceFactory, explicitLegacy, oldReturns, stopped, aliasResult, rootPromise, synchronous, wrongStop, invalidChunk] +export type { ChunkShape, PreciseArguments, PreciseCallback, PreciseFactory, PreciseResult, PreciseStop, RootArguments, RootFactory, RootOption, RootResult, RootShape, RootStop, RuntimeEntryTypes } diff --git a/test/types/asr-source.ts b/test/types/asr-source.ts new file mode 100644 index 000000000..269069a26 --- /dev/null +++ b/test/types/asr-source.ts @@ -0,0 +1,21 @@ +import type { AudioChunk, Factory, RuntimeFactory, RuntimeOption, RuntimeResult } from 'artplayer-plugin-asr' +import type source from '../../packages/artplayer-plugin-asr/src/index' +import type { AsrOptions, AsrResult, AudioChunk as SourceChunk } from '../../packages/artplayer-plugin-asr/src/types' + +declare const implementation: typeof source +declare const runtimeFactory: RuntimeFactory +declare const runtimeOption: RuntimeOption +declare const sourceResult: AsrResult +declare const publicResult: RuntimeResult +declare const sourceChunk: SourceChunk +declare const publicChunk: AudioChunk + +const historical: Factory = implementation +const accurate: Factory = runtimeFactory +const runtimeCall: (option?: RuntimeOption) => ReturnType = implementation +const sourceOption: AsrOptions = runtimeOption +const resultToPublic: RuntimeResult = sourceResult +const resultToSource: AsrResult = publicResult +const chunkToPublic: AudioChunk = sourceChunk +const chunkToSource: SourceChunk = publicChunk +void [historical, accurate, runtimeCall, sourceOption, resultToPublic, resultToSource, chunkToPublic, chunkToSource]