refactor(docs): [SITE-02] generate and validate standalone editor types

This commit is contained in:
Harvey Zhao committed 2026-09-14 07:24:03 +08:00
1 parent 1a5440a67f
commit 76499e7a3e
30 files changed
+2955 -459

No files matched your search

+21 -17
View File
@@ -1,21 +1,25 @@
export type Chapters = {
start: number
end: number
title: string
}[]
export interface Option {
chapters?: Chapters
// 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 artplayerPluginChapterDefinitions {
export type Chapters = {
start: number
end: number
title: string
}[]
export interface Option {
chapters?: Chapters
}
export interface Result {
name: 'artplayerPluginChapter'
update: (option: Option) => void
}
export const artplayerPluginChapter: (option?: Option) => (art: Artplayer) => Result
}
export interface Result {
name: 'artplayerPluginChapter'
update: (option: Option) => void
declare const artplayerPluginChapter: typeof artplayerPluginChapterDefinitions.artplayerPluginChapter
declare namespace artplayerPluginChapter {
export type Chapters = artplayerPluginChapterDefinitions.Chapters
export type Option = artplayerPluginChapterDefinitions.Option
export type Result = artplayerPluginChapterDefinitions.Result
}
declare const artplayerPluginChapter: (option?: Option) => (art: Artplayer) => Result
export default artplayerPluginChapter
export = artplayerPluginChapter
export as namespace artplayerPluginChapter;
@@ -0,0 +1,224 @@
@glomex/vast-ima-player
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 glomex GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@alugha/ima
# The MIT License (MIT)
**Copyright 2020 Alugha GmbH**
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File diff suppressed because it is too large. Load diff
+5 -4
View File
@@ -40,14 +40,14 @@
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/docs-smoke/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/{docs-smoke,editor-declarations}/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
"build:all": "yarn ci:build && yarn lint",
"check:toolchain": "node scripts/check-toolchain.mjs",
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/docs-smoke/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"scripts/{docs-smoke,editor-declarations}/**/*.ts\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:test && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
@@ -120,7 +120,8 @@
"test:vue-consumer": "node refactor/scripts/vue-consumer.mjs",
"lint:vue": "eslint example/vue.js --no-fix",
"typecheck:docs-tools": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.docs.json --noEmit",
"check:docs-smoke": "node scripts/build-test.js --check"
"check:docs-smoke": "node scripts/build-test.js --check",
"check:editor-types": "node scripts/build-ts.js --check"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+3
View File
@@ -42,6 +42,9 @@ The repository generators have different ownership:
- `scripts/build-types.mjs`: core public declaration sources to existing `types/`.
- `scripts/build-ts.js`: standalone editor declarations and the editor library list.
Checked TS modules in `scripts/editor-declarations/` validate all selected
declarations together with current and historical compilers before writing.
Use `yarn check:editor-types` for drift checks; see that module's README.
- `scripts/build-test.js`: extracts Chinese Run Code blocks into `docs/test/test.js`.
The TS implementation in `scripts/docs-smoke/` produces deterministic readiness
smoke cases with owned frames, error observation and cleanup. It does not prove
@@ -0,0 +1,444 @@
{
"task": "SITE-02",
"baseline": "1a5440a67f50c8c1c5c695099bea5ee5d4b0cc77",
"node": "24.21.0",
"yarn": "1.22.22",
"compilers": [
"5.9.3",
"4.3.5"
],
"scope": {
"generatedDeclarations": 22,
"changedDeclarations": [
"docs/assets/ts/artplayer-plugin-chapter.d.ts",
"docs/assets/ts/artplayer-plugin-vast.d.ts"
],
"unchangedDeclarations": 20,
"totalOutputs": 24,
"rootLintExistingWarnings": 1,
"packageRuntimeChanged": false,
"packagePublicTypesChanged": false,
"lockUnchanged": true
},
"logs": {
"reproduction": {
"file": "refactor/.cache/editor-before.log",
"sha256": "13639bee172bc86c2b8787f325fa251be989c16c1bbf00a0a77636fe591da7fc"
},
"targeted": {
"file": "refactor/.cache/editor-tests-final.log",
"sha256": "bdcc879dc19dc25a086442c982f7e9fa091ec011d80e7878dfa9bab222733f85",
"exitCode": 0,
"pass": 4
},
"baseline": {
"file": "refactor/.cache/editor-baseline.log",
"sha256": "c92fcf9cfe1c64dfecb23d44bb37542519b78f7da4318b1c80322a81948949f4",
"exitCode": 0,
"pass": 522
},
"browserBefore": {
"file": "refactor/.cache/editor-browser.log",
"sha256": "07ed1bbb4fdc0ba2f45e8ef659ad8d73bbb1a6ffb1cfee0c212f7bc4829996a3",
"exitCode": 1,
"pass": 0
},
"browserAccepted": {
"file": "refactor/.cache/editor-browser-verified.log",
"sha256": "72784c4ce8f6ce298abc93529d1c2d2dfde1bb4bc582ed889b02ba27ab856b93",
"exitCode": 0,
"pass": 3
},
"buildAll": {
"file": "refactor/.cache/editor-all-build.log",
"sha256": "b3c6a01e75ce9e33ac43e63d90a79c5b3924294985d054ba1ef267b8ab683baf",
"exitCode": 0
},
"readonlyCheck": {
"file": "refactor/.cache/editor-readonly-check.log",
"sha256": "91e7a45d96d8a86b96d49822ee6284e70e9c4577cc7ac8bc730fbfd2087b3517",
"exitCode": 0
},
"typecheckTools": {
"file": "refactor/.cache/editor-types-check.log",
"sha256": "713f8f9c2ea18e5da3e50f5e3a943515b4e93e7adb8172025ee31a7d7b566cff",
"exitCode": 0
},
"rootLint": {
"file": "refactor/.cache/editor-root-lint.log",
"sha256": "5d7ca80241e65fed23117d779a3e1fba0b2281003a304005ea014a0e3cb75cdf",
"exitCode": 0
},
"targetedLint": {
"file": "refactor/.cache/editor-final-lint.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"exitCode": 0
},
"ci": {
"file": "refactor/.cache/editor-ci-tests.log",
"sha256": "ebc514dfc48afff766aeba32c689a922efa36e8c85fc5d2552ca36f22248c0e5",
"exitCode": 0,
"pass": 50
},
"strictToolchainBefore": {
"file": "refactor/.cache/editor-toolchain-direct-before.log",
"sha256": "23de6d33eafd3123528d9a4f7b3d82131a32ee90e0724eaab8f2446bca9b48da",
"exitCode": 1
},
"strictToolchain": {
"file": "refactor/.cache/editor-toolchain.log",
"sha256": "3da883e18990c25a4eee9f46febb386fc33b0ea6a25ff748d5c129bb8d16ea85",
"exitCode": 0
}
},
"browser": {
"file": "refactor/.cache/editor-browser-accepted/report.json",
"sha256": "20de4582ac416faa3e69d7a1444018a99e7ba0f00f60a114c5905cee5fdf6ff7",
"stats": {
"startTime": "2026-09-13T23:16:22.090Z",
"duration": 23649.482000000004,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"cases": [
{
"title": "Monaco checks all editor declarations together and runs the Chapter consumer",
"project": "chromium",
"status": "expected",
"attempts": 1,
"browser": "153.0.8010.12",
"platform": "win32",
"errors": [],
"result": {
"syntax": [],
"semantic": [],
"declarations": [],
"bad": [
2322,
2322,
2322
],
"name": "artplayerPluginChapter",
"ready": true,
"instances": 0
},
"declarationInputs": {
"artplayer-plugin-ads.d.ts": "bf128828d5b104fc844a4f134a02162e57472d8c927a50df7b3878f4c5ed7652",
"artplayer-plugin-ambilight.d.ts": "9f06ccb7398053dca642ca369aa47f54cbb3e4dd4d5e252e999e59e496d8eee8",
"artplayer-plugin-asr.d.ts": "5096f097636c018bc05c497324c271af3fefaebccda7fa3e9cd43b63477704ad",
"artplayer-plugin-audio-track.d.ts": "604af2a3790eb7c583ba150c402dca0a3e458469cc74913b51fee2f3d3882541",
"artplayer-plugin-auto-thumbnail.d.ts": "f66bba7f633202c2ef359a989269c643b75d91594364f7ed6fb67f9898aeac03",
"artplayer-plugin-chapter.d.ts": "d5bd68579659afa44158c894e12372cedd6f958415ced370a65c294550aa828d",
"artplayer-plugin-chromecast.d.ts": "0a0b932081cac0efbcedea28dc1077484963b7f406808378704062661a4c0338",
"artplayer-plugin-danmuku-mask.d.ts": "06c6e938876edcc0ac2c8dcd1e1be4596b0f056685a3523d4c9836f2f2f90362",
"artplayer-plugin-danmuku.d.ts": "e3421bdbdfc6bea1b0b633300350fff1a745d8bc21c1430b6154a23fa3ad0415",
"artplayer-plugin-dash-control.d.ts": "c7e3c83f3377b615e503a270af12915dcc82fe3f2cfabdae396632855a27f73f",
"artplayer-plugin-document-pip.d.ts": "42faa0a6549149efd01b46c3c59246bd4dc33f2383b63b04e9f5376de29fdb8e",
"artplayer-plugin-hls-control.d.ts": "6a623bb6ba4cc0598a3dd1aab5ef7ca4fbae11973a787885eccba10cc7aa2c7c",
"artplayer-plugin-jassub.d.ts": "ce713e0a61a81bfc31087ea1a880a99398ef7c2b833e567c91e4e98f5ae6f621",
"artplayer-plugin-multiple-subtitles.d.ts": "edb15207df69a695311e849555cf989bfc32da20c2456731b6814de5400567bb",
"artplayer-plugin-vast.d.ts": "b21ed9a852b3ec3cc9b409cce08e91905e42abdc1d642acc9b412c33c83287b7",
"artplayer-plugin-vtt-thumbnail.d.ts": "d82c0fe08bcf9c60e2a634e9f1fd8ccc75dc8e0ddbfa8262cccaefc9604aa61b",
"artplayer-proxy-canvas.d.ts": "83c408e0724a598f9ff32d68d752f39e03feb69ba4f6a49ba02f9d4df898453e",
"artplayer-proxy-mediabunny.d.ts": "acbaa0a25f20ac15b3d79db00b3a16167f84452ab58933110f42dcb3262fbd3e",
"artplayer-tool-iframe.d.ts": "c06c6819146ba64bd6ee498871d9e19ab5a9e88c9cc7bed2c0c4968fa39dfed7",
"artplayer-tool-thumbnail.d.ts": "9b72d10a1fe964d54fcf4774770074058ea739b4f734b52b8f4adebb37fc83e6",
"artplayer.d.ts": "652c0fdf605e87203526777a05851bd69c95c1660d5ff29eab19c00967c9a985",
"artplayer-i18n.d.ts": "60851e22ea50ed540f1be0f5a110077cdd5e6ab87345a675838f6c6333517405"
}
},
{
"title": "Monaco checks all editor declarations together and runs the Chapter consumer",
"project": "firefox",
"status": "expected",
"attempts": 1,
"browser": "155.0",
"platform": "win32",
"errors": [],
"result": {
"syntax": [],
"semantic": [],
"declarations": [],
"bad": [
2322,
2322,
2322
],
"name": "artplayerPluginChapter",
"ready": true,
"instances": 0
},
"declarationInputs": {
"artplayer-plugin-ads.d.ts": "bf128828d5b104fc844a4f134a02162e57472d8c927a50df7b3878f4c5ed7652",
"artplayer-plugin-ambilight.d.ts": "9f06ccb7398053dca642ca369aa47f54cbb3e4dd4d5e252e999e59e496d8eee8",
"artplayer-plugin-asr.d.ts": "5096f097636c018bc05c497324c271af3fefaebccda7fa3e9cd43b63477704ad",
"artplayer-plugin-audio-track.d.ts": "604af2a3790eb7c583ba150c402dca0a3e458469cc74913b51fee2f3d3882541",
"artplayer-plugin-auto-thumbnail.d.ts": "f66bba7f633202c2ef359a989269c643b75d91594364f7ed6fb67f9898aeac03",
"artplayer-plugin-chapter.d.ts": "d5bd68579659afa44158c894e12372cedd6f958415ced370a65c294550aa828d",
"artplayer-plugin-chromecast.d.ts": "0a0b932081cac0efbcedea28dc1077484963b7f406808378704062661a4c0338",
"artplayer-plugin-danmuku-mask.d.ts": "06c6e938876edcc0ac2c8dcd1e1be4596b0f056685a3523d4c9836f2f2f90362",
"artplayer-plugin-danmuku.d.ts": "e3421bdbdfc6bea1b0b633300350fff1a745d8bc21c1430b6154a23fa3ad0415",
"artplayer-plugin-dash-control.d.ts": "c7e3c83f3377b615e503a270af12915dcc82fe3f2cfabdae396632855a27f73f",
"artplayer-plugin-document-pip.d.ts": "42faa0a6549149efd01b46c3c59246bd4dc33f2383b63b04e9f5376de29fdb8e",
"artplayer-plugin-hls-control.d.ts": "6a623bb6ba4cc0598a3dd1aab5ef7ca4fbae11973a787885eccba10cc7aa2c7c",
"artplayer-plugin-jassub.d.ts": "ce713e0a61a81bfc31087ea1a880a99398ef7c2b833e567c91e4e98f5ae6f621",
"artplayer-plugin-multiple-subtitles.d.ts": "edb15207df69a695311e849555cf989bfc32da20c2456731b6814de5400567bb",
"artplayer-plugin-vast.d.ts": "b21ed9a852b3ec3cc9b409cce08e91905e42abdc1d642acc9b412c33c83287b7",
"artplayer-plugin-vtt-thumbnail.d.ts": "d82c0fe08bcf9c60e2a634e9f1fd8ccc75dc8e0ddbfa8262cccaefc9604aa61b",
"artplayer-proxy-canvas.d.ts": "83c408e0724a598f9ff32d68d752f39e03feb69ba4f6a49ba02f9d4df898453e",
"artplayer-proxy-mediabunny.d.ts": "acbaa0a25f20ac15b3d79db00b3a16167f84452ab58933110f42dcb3262fbd3e",
"artplayer-tool-iframe.d.ts": "c06c6819146ba64bd6ee498871d9e19ab5a9e88c9cc7bed2c0c4968fa39dfed7",
"artplayer-tool-thumbnail.d.ts": "9b72d10a1fe964d54fcf4774770074058ea739b4f734b52b8f4adebb37fc83e6",
"artplayer.d.ts": "652c0fdf605e87203526777a05851bd69c95c1660d5ff29eab19c00967c9a985",
"artplayer-i18n.d.ts": "60851e22ea50ed540f1be0f5a110077cdd5e6ab87345a675838f6c6333517405"
}
},
{
"title": "Monaco checks all editor declarations together and runs the Chapter consumer",
"project": "webkit",
"status": "expected",
"attempts": 1,
"browser": "26.6",
"platform": "win32",
"errors": [],
"result": {
"syntax": [],
"semantic": [],
"declarations": [],
"bad": [
2322,
2322,
2322
],
"name": "artplayerPluginChapter",
"ready": true,
"instances": 0
},
"declarationInputs": {
"artplayer-plugin-ads.d.ts": "bf128828d5b104fc844a4f134a02162e57472d8c927a50df7b3878f4c5ed7652",
"artplayer-plugin-ambilight.d.ts": "9f06ccb7398053dca642ca369aa47f54cbb3e4dd4d5e252e999e59e496d8eee8",
"artplayer-plugin-asr.d.ts": "5096f097636c018bc05c497324c271af3fefaebccda7fa3e9cd43b63477704ad",
"artplayer-plugin-audio-track.d.ts": "604af2a3790eb7c583ba150c402dca0a3e458469cc74913b51fee2f3d3882541",
"artplayer-plugin-auto-thumbnail.d.ts": "f66bba7f633202c2ef359a989269c643b75d91594364f7ed6fb67f9898aeac03",
"artplayer-plugin-chapter.d.ts": "d5bd68579659afa44158c894e12372cedd6f958415ced370a65c294550aa828d",
"artplayer-plugin-chromecast.d.ts": "0a0b932081cac0efbcedea28dc1077484963b7f406808378704062661a4c0338",
"artplayer-plugin-danmuku-mask.d.ts": "06c6e938876edcc0ac2c8dcd1e1be4596b0f056685a3523d4c9836f2f2f90362",
"artplayer-plugin-danmuku.d.ts": "e3421bdbdfc6bea1b0b633300350fff1a745d8bc21c1430b6154a23fa3ad0415",
"artplayer-plugin-dash-control.d.ts": "c7e3c83f3377b615e503a270af12915dcc82fe3f2cfabdae396632855a27f73f",
"artplayer-plugin-document-pip.d.ts": "42faa0a6549149efd01b46c3c59246bd4dc33f2383b63b04e9f5376de29fdb8e",
"artplayer-plugin-hls-control.d.ts": "6a623bb6ba4cc0598a3dd1aab5ef7ca4fbae11973a787885eccba10cc7aa2c7c",
"artplayer-plugin-jassub.d.ts": "ce713e0a61a81bfc31087ea1a880a99398ef7c2b833e567c91e4e98f5ae6f621",
"artplayer-plugin-multiple-subtitles.d.ts": "edb15207df69a695311e849555cf989bfc32da20c2456731b6814de5400567bb",
"artplayer-plugin-vast.d.ts": "b21ed9a852b3ec3cc9b409cce08e91905e42abdc1d642acc9b412c33c83287b7",
"artplayer-plugin-vtt-thumbnail.d.ts": "d82c0fe08bcf9c60e2a634e9f1fd8ccc75dc8e0ddbfa8262cccaefc9604aa61b",
"artplayer-proxy-canvas.d.ts": "83c408e0724a598f9ff32d68d752f39e03feb69ba4f6a49ba02f9d4df898453e",
"artplayer-proxy-mediabunny.d.ts": "acbaa0a25f20ac15b3d79db00b3a16167f84452ab58933110f42dcb3262fbd3e",
"artplayer-tool-iframe.d.ts": "c06c6819146ba64bd6ee498871d9e19ab5a9e88c9cc7bed2c0c4968fa39dfed7",
"artplayer-tool-thumbnail.d.ts": "9b72d10a1fe964d54fcf4774770074058ea739b4f734b52b8f4adebb37fc83e6",
"artplayer.d.ts": "652c0fdf605e87203526777a05851bd69c95c1660d5ff29eab19c00967c9a985",
"artplayer-i18n.d.ts": "60851e22ea50ed540f1be0f5a110077cdd5e6ab87345a675838f6c6333517405"
}
}
],
"beforeArchive": "refactor/.cache/editor-browser-before",
"beforeReason": "Directory has 23 .d.ts assets; actual libUris references 22, excluding historical WebSR. Browser test now follows actual loader list."
},
"sources": [
{
"file": "scripts/build-ts.js",
"sha256Lf": "1ff2d13f4247bba1818a187eb945a86969f4c4d60245ba5cac8fc0b2b8edd3ba"
},
{
"file": "scripts/editor-types.mjs",
"sha256Lf": "62c6ef8affb5f28b9a9caa9810e3c5e565ca0d25fb031fe0a169beba6a3a1125"
},
{
"file": "scripts/plugin-editor-types.mjs",
"sha256Lf": "09b6bdc419e92d6052ba8575aa9b726e7231ca2579578cd91f9b34d1f99b44bc"
},
{
"file": "scripts/tsconfig.docs.json",
"sha256Lf": "4398157a60f8be091484d332455745708b999753cad3d7e0dd4a216c818da79e"
},
{
"file": "scripts/tsconfig.editor.json",
"sha256Lf": "ce57cedc75093d62f9d62efe1ff24c1d5d0fd157cbe830d082b8cebcf5f0e6a1"
},
{
"file": "package.json",
"sha256Lf": "0726e24a97b8107dd7c8effe37202b194d4211a3f06ea9c8c22058029a6d0871"
},
{
"file": "test/editor-types.test.js",
"sha256Lf": "2c431f49ca5b7428171ee7f494b48ac72df787c9a0824362855c49fc0c4cdbca"
},
{
"file": "test/browser/editor-declarations.spec.js",
"sha256Lf": "33e824e96b2fb385fa99fd384325f9cf660c49eff0afd4fb0234a3a5f4048e00"
},
{
"file": "scripts/editor-declarations/core.ts",
"sha256Lf": "0cda0fe093299099c62ae907a6f517f1cb5c49be9085d4c8fb20aed6579a0058"
},
{
"file": "scripts/editor-declarations/dependencies.ts",
"sha256Lf": "aa697e095f3e9ea476a2ae625714542f62db47e1257cb69cc5e249277db67f8c"
},
{
"file": "scripts/editor-declarations/generate.ts",
"sha256Lf": "6cf6ee27b2032e9e3a4aef949f2b54ba7bf12e40bed473478f63eb82759c8e5a"
},
{
"file": "scripts/editor-declarations/plugin.ts",
"sha256Lf": "02dd90a19b75235ac6080aa2088114f3ae51c45567789c7daf2653be63ceee7d"
},
{
"file": "scripts/editor-declarations/README.md",
"sha256Lf": "968b7d6b558a5dde76bc004517fa02eaa33d81a1051e34b33f21a112d85aa8a0"
},
{
"file": "scripts/editor-declarations/syntax.ts",
"sha256Lf": "d5c7d8f400b3053e1c1a588dab809f5d17e95be88ecb985cbc90f9ffaea3a0e6"
},
{
"file": "scripts/editor-declarations/validation.ts",
"sha256Lf": "1b5524844d030ba32df8bb47f1f3963e7df1f0843db61ad1cc7a983db2546517"
},
{
"file": "scripts/editor-declarations/vast-sdk.ts",
"sha256Lf": "6da9176284b7a697a922b90befa64263e016f12eef483793cc524bdfc028ad05"
}
],
"outputs": [
{
"file": "docs/assets/ts/artplayer-i18n.d.ts",
"sha256Lf": "60851e22ea50ed540f1be0f5a110077cdd5e6ab87345a675838f6c6333517405"
},
{
"file": "docs/assets/ts/artplayer-plugin-ads.d.ts",
"sha256Lf": "bf128828d5b104fc844a4f134a02162e57472d8c927a50df7b3878f4c5ed7652"
},
{
"file": "docs/assets/ts/artplayer-plugin-ambilight.d.ts",
"sha256Lf": "9f06ccb7398053dca642ca369aa47f54cbb3e4dd4d5e252e999e59e496d8eee8"
},
{
"file": "docs/assets/ts/artplayer-plugin-asr.d.ts",
"sha256Lf": "5096f097636c018bc05c497324c271af3fefaebccda7fa3e9cd43b63477704ad"
},
{
"file": "docs/assets/ts/artplayer-plugin-audio-track.d.ts",
"sha256Lf": "604af2a3790eb7c583ba150c402dca0a3e458469cc74913b51fee2f3d3882541"
},
{
"file": "docs/assets/ts/artplayer-plugin-auto-thumbnail.d.ts",
"sha256Lf": "f66bba7f633202c2ef359a989269c643b75d91594364f7ed6fb67f9898aeac03"
},
{
"file": "docs/assets/ts/artplayer-plugin-chapter.d.ts",
"sha256Lf": "d5bd68579659afa44158c894e12372cedd6f958415ced370a65c294550aa828d"
},
{
"file": "docs/assets/ts/artplayer-plugin-chromecast.d.ts",
"sha256Lf": "0a0b932081cac0efbcedea28dc1077484963b7f406808378704062661a4c0338"
},
{
"file": "docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts",
"sha256Lf": "06c6e938876edcc0ac2c8dcd1e1be4596b0f056685a3523d4c9836f2f2f90362"
},
{
"file": "docs/assets/ts/artplayer-plugin-danmuku.d.ts",
"sha256Lf": "e3421bdbdfc6bea1b0b633300350fff1a745d8bc21c1430b6154a23fa3ad0415"
},
{
"file": "docs/assets/ts/artplayer-plugin-dash-control.d.ts",
"sha256Lf": "c7e3c83f3377b615e503a270af12915dcc82fe3f2cfabdae396632855a27f73f"
},
{
"file": "docs/assets/ts/artplayer-plugin-document-pip.d.ts",
"sha256Lf": "42faa0a6549149efd01b46c3c59246bd4dc33f2383b63b04e9f5376de29fdb8e"
},
{
"file": "docs/assets/ts/artplayer-plugin-hls-control.d.ts",
"sha256Lf": "6a623bb6ba4cc0598a3dd1aab5ef7ca4fbae11973a787885eccba10cc7aa2c7c"
},
{
"file": "docs/assets/ts/artplayer-plugin-jassub.d.ts",
"sha256Lf": "ce713e0a61a81bfc31087ea1a880a99398ef7c2b833e567c91e4e98f5ae6f621"
},
{
"file": "docs/assets/ts/artplayer-plugin-multiple-subtitles.d.ts",
"sha256Lf": "edb15207df69a695311e849555cf989bfc32da20c2456731b6814de5400567bb"
},
{
"file": "docs/assets/ts/artplayer-plugin-vast.d.ts",
"sha256Lf": "b21ed9a852b3ec3cc9b409cce08e91905e42abdc1d642acc9b412c33c83287b7"
},
{
"file": "docs/assets/ts/artplayer-plugin-vtt-thumbnail.d.ts",
"sha256Lf": "d82c0fe08bcf9c60e2a634e9f1fd8ccc75dc8e0ddbfa8262cccaefc9604aa61b"
},
{
"file": "docs/assets/ts/artplayer-proxy-canvas.d.ts",
"sha256Lf": "83c408e0724a598f9ff32d68d752f39e03feb69ba4f6a49ba02f9d4df898453e"
},
{
"file": "docs/assets/ts/artplayer-proxy-mediabunny.d.ts",
"sha256Lf": "acbaa0a25f20ac15b3d79db00b3a16167f84452ab58933110f42dcb3262fbd3e"
},
{
"file": "docs/assets/ts/artplayer-tool-iframe.d.ts",
"sha256Lf": "c06c6819146ba64bd6ee498871d9e19ab5a9e88c9cc7bed2c0c4968fa39dfed7"
},
{
"file": "docs/assets/ts/artplayer-tool-thumbnail.d.ts",
"sha256Lf": "9b72d10a1fe964d54fcf4774770074058ea739b4f734b52b8f4adebb37fc83e6"
},
{
"file": "docs/assets/ts/artplayer.d.ts",
"sha256Lf": "652c0fdf605e87203526777a05851bd69c95c1660d5ff29eab19c00967c9a985"
},
{
"file": "docs/assets/js/common.js",
"sha256Lf": "cc9f7d780cf4f0ffb9aaee75da69e2b2b062f3bac07b4ffc9a75956b2e7cd7af"
},
{
"file": "docs/assets/ts/artplayer-plugin-vast.LICENSE.txt",
"sha256Lf": "967db6e5026a2fd3cc24d8a04e5c78859f543d0c0f6d4f9599c103e4e93c9b29"
}
],
"sdk": [
{
"name": "@glomex/vast-ima-player",
"version": "1.21.2",
"manifest": {
"file": "node_modules\\@glomex\\vast-ima-player\\package.json",
"sha256": "c7361321ce792f6830af9eb40e70b7508a75f68290a3c0077e26a6f36daa7097"
},
"license": {
"file": "node_modules\\@glomex\\vast-ima-player\\LICENSE",
"sha256": "68b35bb64e6269277318ff4e4ee66a738bdeb83bbcdc7e3c3af85a0bdded8d0b"
}
},
{
"name": "@alugha/ima",
"version": "2.1.0",
"manifest": {
"file": "node_modules\\@alugha\\ima\\package.json",
"sha256": "0c0f9d2a4cc8ea3f96239b44e4ef764630421998e5de91d05d04e42582b24852"
},
"license": {
"file": "node_modules\\@alugha\\ima\\LICENSE.md",
"sha256": "0fe0f6dd9671aecf84ea082cdcd7b3297b75ca942d082b485b94dd3b4dfd648a"
}
}
],
"limitations": [
"Browser uses repository Monaco, not an entire editor UI/route acceptance.",
"VAST is type-only; no ad SDK request, no runtime default behavior decision.",
"No full production/runtime or publication matrix rerun, no remote CI or manual Chrome/iab.",
"No new dependencies, push or publication."
]
}
+31 -3
View File
@@ -18101,15 +18101,15 @@
},
{
"file": "scripts/build-ts.js",
"sha256Lf": "338f3b5b4e4bdcb42c878997d13da6f71ce9842d730f57efece7566124dae9f6"
"sha256Lf": "1ff2d13f4247bba1818a187eb945a86969f4c4d60245ba5cac8fc0b2b8edd3ba"
},
{
"file": "scripts/editor-types.mjs",
"sha256Lf": "951d3d40dc7f90034561fd1a3b3015bdb1449593121bb410e3f15e4415547b8b"
"sha256Lf": "62c6ef8affb5f28b9a9caa9810e3c5e565ca0d25fb031fe0a169beba6a3a1125"
},
{
"file": "scripts/plugin-editor-types.mjs",
"sha256Lf": "429c304ce7c801836a311e76e85f2e6dc0328d599103e39d071a915164b94102"
"sha256Lf": "09b6bdc419e92d6052ba8575aa9b726e7231ca2579578cd91f9b34d1f99b44bc"
},
{
"file": "scripts/build-test.js",
@@ -18166,6 +18166,34 @@
{
"file": "scripts/docs-smoke/runtime.ts",
"sha256Lf": "9fae3aa1bc6a6ef9728d9a677de9e283d40314022b710b2ecf19a9b951f6291d"
},
{
"file": "scripts/editor-declarations/core.ts",
"sha256Lf": "0cda0fe093299099c62ae907a6f517f1cb5c49be9085d4c8fb20aed6579a0058"
},
{
"file": "scripts/editor-declarations/dependencies.ts",
"sha256Lf": "aa697e095f3e9ea476a2ae625714542f62db47e1257cb69cc5e249277db67f8c"
},
{
"file": "scripts/editor-declarations/generate.ts",
"sha256Lf": "6cf6ee27b2032e9e3a4aef949f2b54ba7bf12e40bed473478f63eb82759c8e5a"
},
{
"file": "scripts/editor-declarations/plugin.ts",
"sha256Lf": "02dd90a19b75235ac6080aa2088114f3ae51c45567789c7daf2653be63ceee7d"
},
{
"file": "scripts/editor-declarations/syntax.ts",
"sha256Lf": "d5c7d8f400b3053e1c1a588dab809f5d17e95be88ecb985cbc90f9ffaea3a0e6"
},
{
"file": "scripts/editor-declarations/validation.ts",
"sha256Lf": "1b5524844d030ba32df8bb47f1f3963e7df1f0843db61ad1cc7a983db2546517"
},
{
"file": "scripts/editor-declarations/vast-sdk.ts",
"sha256Lf": "6da9176284b7a697a922b90befa64263e016f12eef483793cc524bdfc028ad05"
}
],
"generationCommands": {
@@ -0,0 +1,63 @@
# SITE-02 编辑器声明生成链
起点 `1a5440a67f50c8c1c5c695099bea5ee5d4b0cc77`。SITE-SMOKE-01 已完成示例
生成半项,本次完成声明生成半项,保留全部父任务范围。原 build:ts 命令、按包
参数、声明 URL、i18n 入口和工具 MJS 导入路径继续可用。生产包源码、公开声明、
运行时事件/DOM、分发入口与 yarn.lock 未修改。
## 结构与修复
- `scripts/editor-declarations/` 包含 TS 的 core/plugin AST 转换、语法检查、
VAST SDK 类型闭包、虚拟编译器 host 和生成编排。旧 JS/MJS 文件仅转发,同属
严格 checkJs 范围。新模块维护地图、依赖方向和修改命令见该目录 README。
- 原 Chapter 文本回退产生 TS2309(default 与 export assignment 混用);
VAST 同时产生 TS2304/2552(删除 import 后丢失 Player/PlayerOptions)。
测试固定原转换表达式复现,不靠跳过声明检查或 any 替换解决。
- 所有 20 个生态库现在统一走语义转换;加 core/i18n 共 22 个加载声明。格式化
后以主 TS 5.9.3、旧 TS 4.3.5 整组检查,同时验证命名空间冲突和未解析依赖。
生成期间仅作 layout 格式修复;SDK 原方法与 type alias 形状保留,相关 lint
豁免限定在自动生成的 SDK 命名空间块。其余 20 份声明内容与起点一致。
- VAST 通过单独 type-only 入口收集 @glomex/vast-ima-player 1.21.2 和
@alugha/ima 2.1.0。直接 bundle VAST 根声明会因 workspace 链接一并内联核心,
因此只 bundle SDK 闭包,再用 AST 放入模块内部 namespace,避免重复核心。
原 Window 可选属性、必需回调、SDK 方法/私有成员约束保留;不新增 SDK 全局
值或运行时加载。自动生成邻接 LICENSE.txt 保存上游 notices。
- 新增 `check:editor-types` 并加入 ci:check;所有选择、声明检查、格式化、
notices 和 libUris 生成成功后才写结果。只读检查拒绝缺失/漂移,不写文件。
libUris 用 AST 定位唯一数组声明,避免找不到旧正则时静默漏更新;按包构建
保持不更新总列表的旧行为。多文件最终写盘本身不承诺事务性。
- 无新增依赖或锁变更;复用已固定 dts-bundle-generator 9.5.1、TS、ESLint、glob。
两个真实编译器模块的名义类型不同,仅在工具边界适配,测试实际执行旧编译器。
## 验证与修正过程
- 4 项编辑器 Node 测试通过:core/ASR 原消费者;Chapter/VAST 旧红新绿、模块
导入与全局消费;6 个类型误用分别被拒绝;全部 24 个输出与重生成一致;
未知/重复选包及缺失/重复/非数组 libUris 拒绝。
- 完整 baseline 522 通过,0 失败/跳过,覆盖旧 MJS 路径上的包类型回归。
CI 50 项回归通过;严格 docs-tools 检查、全仓 lint(0 error/1 既有生成
声明 warning)、目标 lint 与严格工具链通过。
- 三浏览器实际 Monaco 各 1 项,共 3 通过,0 跳过/flaky:实际 common.js 中
22 份加载声明一起检查,正例无诊断、三个坏参数各报 TS2322,并运行实际
emit 的 Chapter 代码,真实受控媒体 ready 后销毁、实例归零。没有执行 VAST
广告 SDK。完整 browser report 和版本/结果摘要见验证 JSON。
- 首次浏览器检查失败在 23 vs 22 文件数:目录遗留 artplayer-plugin-websr.d.ts
未列入实际 libUris。测试改为按实际加载列表取文件,原资产保留,未通过删除
声明消除失败。失败报告已归档。旧资产的整体处置仍属 SITE-07。
- 新虚拟 host 的模块正例最初失败,原因是 Windows 根路径尾部分隔符使目录
比较不匹配;用 path.resolve 统一后相对导入和全部负例通过。SDK 声明初次
layout lint 指出上游签名风格,采用限定范围保留原类型形状,未作语义格式替换。
- 一次直接 node 调严格工具链检查被 Yarn 身份检查拒绝;按规定使用 pinned
Yarn 重新执行成功,未绕过该门槛。原失败日志保留。
## 兼容与后续
本次兼容修正针对编辑器生成声明,生产根类型和 /runtime 类型均保持原内容。
Chapter/VAST 编辑器从无效声明变成可严格消费声明,原合法回调和配置不需改写。
VAST 初始化默认行为、Auto Thumbnail 首帧、真实设备/SDK、完整文档交互与三轮
发布复盘仍开放。SITE-03/04/05 与 EX-03 接续文档产品流程和完整验收。
未重新执行全仓生产运行时套件或全局发布矩阵;本次变化没有生产源码/锁变更。
没有手动 Chrome/iab 会话、远端 CI、推送或发布。构建/验证只证明上述工具与
消费者范围。任务单独提交后审计;回退该提交会恢复旧生成链及 Chapter/VAST
声明错误,需重新 build:ts,优先对具体回归单独修复。
+2 -1
View File
@@ -10,7 +10,8 @@
| `yarn lint:fix` | 显式自动修复相同范围 |
| `yarn typecheck` | 根/迁移包严格检查、当前与兼容 TS 消费;历史 NodeNext ESM 错误单独核对,见 typechecking.md |
| `yarn typecheck:react` / `yarn typecheck:vue` | 原 React TSX / Vue SFC 示例严格检查,ci:check 同时执行对应 lint |
| `yarn typecheck:docs-tools` / `yarn check:docs-smoke` | 严格检查 TS 示例生成器及 JS 命令门面;只读核对确定性生成的 readiness smoke,ci:check 执行 |
| `yarn typecheck:docs-tools` / `yarn check:docs-smoke` | 严格检查 TS 示例/声明生成器及 JS/MJS 门面;只读核对确定性生成的 readiness smoke,ci:check 执行 |
| `yarn check:editor-types` | 只读核对全部编辑器声明、SDK notices 和实际 libUris;主 TS 5.9.3/历史 4.3.5 整组语义检查,ci:check 执行 |
| `yarn test:react-consumer` / `yarn test:vue-consumer` | 仓库外 tarball 安装、原框架示例、开发/生产三引擎;browser-smoke 执行并上传独立目录,本地证据不代替远端矩阵 |
| `yarn test:unit` | 原播放/DASH 回归、同夹具的新旧公共契约与 JS/TS loader 验证 |
| `yarn test:node` | test:unit 加工具链/文档构建回归,保留原 test:playback/test:dash-control 入口 |
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 240 项,范围 22 个包及工作区/示例。
状态:todo 60 / doing 15 / blocked 0 / done 165 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 59 / doing 15 / blocked 0 / done 166 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -382,7 +382,7 @@
| --- | --- | --- | --- | --- | --- | --- |
| SITE-01 | artplayer-vitepress, workspace<br>清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径;明确 BASE-05 所记录站点实际分发与未设 private 的 manifest 意图;接续 VENDOR-05/06/07/08 和 BASE-SITE-01/BASE-MEDIA-01,核对字体、Monaco、vConsole、console bundle 与样本来源及分发范围 | M | done |
| SITE-SMOKE-01 | artplayer-vitepress, workspace<br>迁移示例生成器并验证真实就绪与清理 | SITE-01 | TS Markdown 解析/生成与浏览器运行模块;旧命令和 URL、稳定实例就绪 smoke、错误与资源清理 | 复现旧 malformed 死循环并保证有限失败;保留全部既有示例内容、生成确定且 check 只读;不再以 100ms 作为成功,验证原生就绪、异常、取消与清理;三浏览器检查,明确延迟交互与全部 233 示例仍需 EX-03 | M | done |
| SITE-02 | artplayer-vitepress<br>整理声明与示例生成器 | SITE-01, ENG-04, ENG-06, SITE-SMOKE-01 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制;坏代码块解析必须终止,覆盖 malformed 分支不推进 regexp 的回归 | M | todo |
| SITE-02 | artplayer-vitepress<br>整理声明与示例生成器 | SITE-01, ENG-04, ENG-06, SITE-SMOKE-01 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制;坏代码块解析必须终止,覆盖 malformed 分支不推进 regexp 的回归 | M | done |
| SITE-03 | artplayer-vitepress<br>整理 i18n/文档/LLM 生成流程 | SITE-02 | build-i18n/build-docs/build-llm/trans-docs 的任务边界和错误处理 | 原命令兼容、生成可复现,翻译步骤不隐式运行远程服务;覆盖移动 loader 失败恢复 define、脚本依赖顺序、localhost/127.0.0.1 Run Code 目标和语言重定向 | M | todo |
| SITE-04 | artplayer-vitepress<br>交叉核对逐包持续维护的文档 | CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 已随实现更新的中文/英文 API、包内实现地图、旧 JS 示例及已知能力限制的全包核对 | 未把缺环境的能力写成已验证,静态核对不等待设备任务;最终 demo 仍由 EX-03 验收;按 SITE-01 声明成员/候选标题清单逐项语义核对,补齐缺失的双语插件说明和 Danmuku 英文入口 | M | todo |
| SITE-05 | artplayer-vitepress<br>构建文档站和验证链接/示例 | SITE-04, EX-01, EX-02, SITE-07 | VitePress 构建、链接与嵌入 demo 检查 | 文档构建、链接、嵌入路径与声明注入通过;真实完整 demo 保留 EX-03 独立门槛;核对 ENG-PM-01 登记的搜索 peer 范围和真实搜索行为 | M | todo |
@@ -608,6 +608,7 @@
- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-public-types.md) [记录](baselines/thumbnail-public-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-emitter.md) [记录](baselines/thumbnail-emitter-validation.json)
- SITE-01: [记录](site-inventory.md) [记录](baselines/site-inventory.json) [记录](baselines/site-provenance.json) [记录](baselines/demo-additions.json) [记录](changes/2026-09-14-SITE-01-site-inventory.md) [记录](baselines/site-inventory-validation.json)
- SITE-SMOKE-01: [记录](changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md) [记录](baselines/docs-smoke-validation.json) [记录](scripts/docs-smoke.test.mjs)
- SITE-02: [记录](changes/2026-09-14-SITE-02-editor-declarations.md) [记录](baselines/editor-declarations-validation.json) [记录](changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md)
- SITE-07: [记录](site-inventory.md) [记录](baselines/site-provenance.json)
- EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs)
- EX-02: [记录](changes/2026-09-14-EX-02-vue-consumer.md) [记录](baselines/vue-consumer-validation.json) [记录](scripts/vue-consumer.mjs)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## SITE-02 编辑器声明生成链完成
build:ts 与 core/plugin 转换器完成 TS 模块拆分,原 JS/MJS 命令/导入路径保留。
修复 Chapter/VAST 文本回退产生的冲突导出与 SDK 类型丢失;22 份实际加载声明
在内存格式化后用主 TS 5.9.3/旧 TS 4.3.5 整组检查,通过才写文件。其余 20 份
声明与起点一致,生产源码/公开类型/锁不变。VAST SDK 类型保留私有命名空间与
原签名,生成上游 notices;新增只读 check:editor-types 接入 CI。4 项编辑器
测试、522 项 baseline、50 项 CI 回归、严格工具链/工具类型/lint 通过(根 lint
1 条既有 warning)。三浏览器实际 Monaco 检查 22 份声明、拒绝坏参数并执行
Chapter emit 的 ready/destroy,3 项通过。VAST 仅验类型,未执行广告。
见[变更](changes/2026-09-14-SITE-02-editor-declarations.md)和
[验证](baselines/editor-declarations-validation.json)。当前 240 项:166 done、
15 doing、59 todo。下一步 SITE-03 站点/移动加载与编辑器流程;VAST 默认行为、
Auto Thumbnail 首帧及插件最终验收保持开放。独立本地提交,无推送或发布。
## SITE-SMOKE-01 示例生成器与就绪检查完成
从 SITE-02 拆出文档示例工具,TS parser/generator/runtime 与严格检查的原 JS
+1 -1
View File
@@ -78,7 +78,7 @@ export function captureSiteInventory() {
pages: demo.pages,
baselinePaths: { examplesAdded: demo.examples.filter(row => !historical.examples.some(old => old.source === row.source)).map(row => row.source), examplesRemoved: historical.examples.filter(row => !demo.examples.some(now => now.source === row.source)).map(row => row.source), htmlAdded: demo.pages.filter(row => !historical.pages.some(old => old.source === row.source)).map(row => row.source), htmlRemoved: historical.pages.filter(row => !demo.pages.some(now => now.source === row.source)).map(row => row.source) },
editorDeclarations: [...read('docs/assets/js/common.js').matchAll(/'\.\/assets\/ts\/([^']+\.d\.ts)'/g)].map(match => ({ file: `docs/assets/ts/${match[1]}`, exists: fs.existsSync(path.join(root, 'docs/assets/ts', match[1])), owner: 'SITE-02' })),
scripts: [...scriptFiles, ...files('scripts/docs-smoke').filter(file => file.endsWith('.ts'))].map(file => ({ file, sha256Lf: hash(read(file)) })),
scripts: [...scriptFiles, ...files('scripts/docs-smoke').filter(file => file.endsWith('.ts')), ...files('scripts/editor-declarations').filter(file => file.endsWith('.ts'))].map(file => ({ file, sha256Lf: hash(read(file)) })),
generationCommands: Object.fromEntries(Object.entries(json('package.json').scripts).filter(([name]) => /^(?:build:(?:types|ts|test|i18n|docs|llm|all)|ci:build)$/.test(name))),
siteManifest: json('packages/artplayer-vitepress/package.json'),
assets,
+4 -1
View File
@@ -3,7 +3,10 @@
SITE-SMOKE-01 后续已将 build:test 拆为 TS 解析、生成和浏览器运行模块,新增
确定性 examples.json 和 readonly check,替换 100ms done 为实例 ready/错误/清理。
以下 SITE-01 的旧生成器问题属于历史发现;当前维护入口见
[docs-smoke README](../scripts/docs-smoke/README.md)。声明生成仍归 SITE-02。
[docs-smoke README](../scripts/docs-smoke/README.md)。SITE-02 后续已将声明生成
迁到 `scripts/editor-declarations/`:全部 22 份加载声明整组验证,Chapter/VAST
不再走删除 import 的文本回退。见[模块说明](../scripts/editor-declarations/README.md)。
以下 SITE-01 对这些旧生成问题的描述是历史发现,站点/编辑器交互仍需后续验收。
SITE-01 在 `d62ab13a35c756ea567c426d4fbe00d893dd7e2b` 后核对当前源码。
可重跑清单见 [site-inventory.json](baselines/site-inventory.json);它登记
+6 -2
View File
@@ -4138,11 +4138,15 @@
"ENG-06",
"SITE-SMOKE-01"
],
"status": "todo",
"status": "done",
"risk": "M",
"deliverable": "build-ts/build-test 生成链的可验证 TS 脚本",
"acceptance": "不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制;坏代码块解析必须终止,覆盖 malformed 分支不推进 regexp 的回归",
"evidence": []
"evidence": [
"changes/2026-09-14-SITE-02-editor-declarations.md",
"baselines/editor-declarations-validation.json",
"changes/2026-09-14-SITE-SMOKE-01-documentation-smoke.md"
]
},
{
"id": "SITE-03",
+2 -95
View File
@@ -1,97 +1,4 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { ESLint } from 'eslint'
import { glob } from 'glob'
import compat from 'typescript-compat'
import { generateCoreEditorDeclaration } from './editor-types.mjs'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from './plugin-editor-types.mjs'
import { runEditorDeclarations } from './editor-declarations/generate.ts'
function ensureDirExists(filePath) {
const dir = path.dirname(filePath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
function parsePluginInfo(pluginPath) {
// Use path.basename to handle both Unix and Windows paths correctly
const file = path.basename(pluginPath)
const baseName = file.replace('.d.ts', '')
const classNames = { 'artplayer-tool-iframe': 'ArtplayerToolIframe', 'artplayer-tool-thumbnail': 'ArtplayerToolThumbnail' }
const name = classNames[baseName]
? classNames[baseName]
: baseName
.split('-')
.map((word, index) =>
index === 0 ? word : word[0].toUpperCase() + word.slice(1),
)
.join('')
return { name, file }
}
const reg = /^import.*$/gim
const artplayerTSoutput = path.join('docs/assets/ts/artplayer.d.ts')
const code = generateCoreEditorDeclaration()
ensureDirExists(artplayerTSoutput)
fs.writeFileSync(artplayerTSoutput, code.trim())
console.log(`✨ Built ${artplayerTSoutput}`);
(async function () {
const packageOf = file => path.basename(path.dirname(path.dirname(file)))
const available = glob.sync('packages/artplayer-*-*/types/*.d.ts').filter(file => path.basename(file) === `${packageOf(file)}.d.ts`)
const selected = process.argv.slice(2)
for (const name of selected)
assert(available.some(file => packageOf(file) === name), `Unknown declaration package: ${name}`)
const pluginsTS = selected.length ? available.filter(file => selected.includes(packageOf(file))) : available
const pluginFiles = []
for (let index = 0; index < pluginsTS.length; index++) {
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginAutoThumbnail', 'artplayerPluginJassub', '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)
: `${source.replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
if (semanticPlugin) {
const core = fs.readFileSync(artplayerTSoutput, 'utf8')
const diagnostics = [...checkPluginEditorDeclaration(code, core), ...checkPluginEditorDeclaration(code, core, '', compat)]
if (diagnostics.length)
throw new Error(`Invalid ${name} editor declaration: ${JSON.stringify(diagnostics)}`)
}
const output = path.join('docs/assets/ts', file)
ensureDirExists(output)
fs.writeFileSync(output, code.trim())
console.log(`✨ Built ${output}`)
pluginFiles.push(file)
}
pluginFiles.sort()
const languageFile = 'artplayer-i18n.d.ts'
fs.writeFileSync(path.join('docs/assets/ts', languageFile), `declare module 'artplayer/i18n/*' {\n const language: NonNullable<Artplayer.I18n['en']>\n export default language\n}\n`)
const allFiles = [...pluginFiles, 'artplayer.d.ts', languageFile]
const eslint = new ESLint({ fix: true, fixTypes: ['layout'] })
const results = await eslint.lintFiles(allFiles.map(file => path.join('docs/assets/ts', file)))
await ESLint.outputFixes(results)
if (results.some(result => result.errorCount)) {
const formatter = await eslint.loadFormatter('stylish')
throw new Error(formatter.format(results))
}
if (selected.length)
return
const commonJsPath = path.join('docs/assets/js/common.js')
const commonJsContent = fs.readFileSync(commonJsPath, 'utf-8')
const newLibUris = allFiles.map(file => `'./assets/ts/${file}'`).join(',\n ')
const newContent = commonJsContent.replace(
/let libUris = \[([\s\S]*?)\]/,
`let libUris = [\n ${newLibUris},\n ]`,
)
ensureDirExists(commonJsPath)
fs.writeFileSync(commonJsPath, newContent)
console.log(`✨ Updated libUris in ${commonJsPath}`)
})()
await runEditorDeclarations(process.argv.slice(2))
+70
View File
@@ -0,0 +1,70 @@
# Editor declarations
Run `yarn build:ts` from the repository root using Node 24.21.0 and Yarn 1.22.22.
Existing declaration URLs and package selection arguments remain supported.
`yarn build:ts artplayer-plugin-chapter` selects that package plus shared core
and language declarations. `yarn check:editor-types` checks the complete output
without writing. Unknown/duplicate selections fail before generating or writing.
| Module | Responsibility |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `../build-ts.js` | Existing CLI path, checked JavaScript shim |
| `generate.ts` | Discovery, names, formatting, whole-set validation, writes/checks and editor libUris |
| `core.ts` | Core public source bundling, constructor and named/generic aliases |
| `plugin.ts` | AST conversion of default exports, callable CommonJS namespaces, type aliases and Window augmentations |
| `syntax.ts` | Public compiler syntax diagnostics and supported dependency definition kinds |
| `dependencies.ts`, `vast-sdk.ts` | Type-only VAST SDK dependency closure; no advertising scripts |
| `validation.ts` | Standalone virtual compiler host, relative imports and rejection of hidden external dependencies |
| `../editor-types.mjs`, `../plugin-editor-types.mjs` | Existing tooling import paths forwarding to TypeScript |
`yarn typecheck:docs-tools` checks these modules and the original JS/MJS shims.
Orchestration depends on conversion and validation; conversion does not write.
Public package declarations remain the source of truth. Do not hand-edit
`docs/assets/ts/*.d.ts`.
All selected declarations are formatted in memory using layout-only ESLint fixes,
then checked together with TypeScript 5.9.3 and 4.3.5, without `skipLibCheck` or
ambient dependency discovery. This checks cross-package globals too. SDK methods
and type aliases retain upstream shapes; the generated SDK block has a narrowly
scoped lint exception for those two style rules. The two compiler packages have
nominally distinct AST types: the historical module is adapted at the boundary
and exercised as the real older compiler. Node native type stripping runs tools.
Legacy MJS helpers retain synchronous functions and diagnostic shapes; existing
package consumer tests still use them. Core retains its existing bundler and
private definitions namespace. Plugins preserve callable/default aliases and
exported type namespaces. Unsupported imports/re-exports and invalid syntax fail.
VAST's `Player` and `PlayerOptions` come from the installed, lockfile-pinned
`@glomex/vast-ima-player` 1.21.2 and `@alugha/ima` 2.1.0 declarations. A dedicated
type-only SDK entry avoids bundling a second copy of the linked workspace core.
SDK types stay in the plugin's module-private definitions namespace, not global
runtime values. The optional Window hook and required callback remain unchanged.
Adjacent generated `artplayer-plugin-vast.LICENSE.txt` preserves upstream notices.
This does not settle VAST's runtime default-behavior compatibility decision.
The editor loads 22 declarations: core, 20 ecosystem libraries and i18n. The
unreferenced legacy `artplayer-plugin-websr.d.ts` asset is not in the `common.js`
library list and is not owned or deleted here. Monaco tests follow the actual
list. AST updates require exactly one array-valued `libUris` declaration; missing
or ambiguous declarations fail. Package-only builds leave that list alone.
Generation validates all selected results before any writes, but does not provide
a multi-file filesystem transaction. `--check` normalizes CRLF for comparison,
writes nothing and rejects missing/stale output. No timestamps are generated.
Rebuild then check after changes to public types, SDK versions or formatting.
```sh
yarn typecheck:docs-tools
yarn build:ts
yarn check:editor-types
node --test test/editor-types.test.js
yarn test:baseline
yarn test:browser test/browser/editor-declarations.spec.js --workers=1
```
Browser coverage uses the actual repository Monaco assets, all loaded declarations,
positive/negative consumers and emitted Chapter code against controlled media.
VAST is type-checked only: no ad request or SDK/network/device acceptance. Full
editor UI, routes, example coverage and release reviews remain SITE-03/04/05,
EX-03 and release tasks. SITE-02 installs no new dependencies.
+145
View File
@@ -0,0 +1,145 @@
import assert from 'node:assert/strict'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { generateDtsBundle } from 'dts-bundle-generator'
import ts from 'typescript'
import compat from 'typescript-compat'
const root = fileURLToPath(new URL('../../', import.meta.url))
const factory = ts.factory
/** Put flattened definitions in a private scope so public aliases cannot refer to themselves. */
export function asGlobalDeclaration(code: string, globalName: string) {
assert(/^[A-Z_$][\w$]*$/i.test(globalName), 'Invalid editor global name')
const source = ts.createSourceFile('bundle.d.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
const exports = new Map<string, string>()
const declarations = new Map<string, ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.ClassDeclaration>()
const definitions: ts.Statement[] = []
for (const node of source.statements) {
if (ts.isExportDeclaration(node)) {
assert(!node.moduleSpecifier && node.exportClause && ts.isNamedExports(node.exportClause), 'Editor bundle must contain no unresolved exports')
for (const item of node.exportClause.elements)
exports.set(item.name.text, (item.propertyName || item.name).text)
continue
}
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node), `Unsupported editor declaration: ${ts.SyntaxKind[node.kind]}`)
assert(node.name, 'Editor definitions must be named')
const name = node.name.text
assert(!declarations.has(name), `Duplicate bundled declaration: ${name}`)
declarations.set(name, node)
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword))
exports.set(name, name)
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
}
const defaultName = exports.get('default')
const defaultDeclaration = defaultName && declarations.get(defaultName)
assert(defaultName && defaultDeclaration && ts.isClassDeclaration(defaultDeclaration), 'Core editor requires the actual default constructor class')
const definitionName = `${globalName}Definitions`
assert(!declarations.has(definitionName), 'Editor definition namespace collides with source')
const qualified = (name: string) => factory.createQualifiedName(factory.createIdentifier(definitionName), factory.createIdentifier(name))
const aliases: ts.TypeAliasDeclaration[] = []
for (const [name, local] of exports) {
if (name === 'default')
continue
const declaration = declarations.get(local)
assert(declaration, `Missing bundled export: ${local}`)
const parameters = declaration.typeParameters
aliases.push(factory.createTypeAliasDeclaration(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
name,
parameters,
factory.createTypeReferenceNode(qualified(local), parameters?.map(parameter => factory.createTypeReferenceNode(parameter.name))),
))
}
const namespace = (name: string, nodes: ts.Statement[]) => factory.createModuleDeclaration(
[factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
factory.createIdentifier(name),
factory.createModuleBlock(nodes),
ts.NodeFlags.Namespace,
)
const statements = [
namespace(definitionName, definitions),
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
factory.createVariableDeclaration(globalName, undefined, factory.createTypeQueryNode(qualified(defaultName))),
], ts.NodeFlags.Const)),
factory.createTypeAliasDeclaration(undefined, globalName, undefined, factory.createTypeReferenceNode(qualified(defaultName))),
namespace(globalName, aliases),
factory.createExportAssignment(undefined, true, factory.createIdentifier(globalName)),
factory.createNamespaceExportDeclaration(factory.createIdentifier(globalName)),
]
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
return `// Generated from packages/artplayer/public/artplayer.ts by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare, ts/no-namespace -- UMD constructor and named types share the global export. */\n${printer.printFile(factory.updateSourceFile(source, statements))}`
}
export function generateCoreEditorDeclaration() {
const require = createRequire(import.meta.url)
const bundlerRequire = createRequire(require.resolve('dts-bundle-generator'))
assert.equal(bundlerRequire('typescript'), ts, 'Editor bundler must use the pinned workspace compiler')
const [code] = generateDtsBundle([{
filePath: path.join(root, 'packages/artplayer/public/artplayer.ts'),
output: { exportReferencedTypes: false, noBanner: true },
}], { preferredConfigPath: path.join(root, 'scripts/tsconfig.editor.json') })
assert(code, 'Core bundle is empty')
const declaration = asGlobalDeclaration(code, 'Artplayer')
for (const compiler of [ts, compat])
assert.deepEqual(checkCoreEditorDeclaration(declaration, compiler as unknown as typeof ts), [], `Invalid editor declaration: TS ${compiler.version}`)
return declaration
}
export function checkCoreEditorDeclaration(code: string, compiler: typeof ts = ts) {
const folder = path.join(root, 'refactor/.cache/editor-semantic')
const declaration = path.join(folder, 'artplayer.d.ts')
const sources = new Map([
[declaration, code],
[path.join(folder, 'global.ts'), `
const option: Artplayer.OptionInput = { container: '#player' }
const player: Artplayer = new Artplayer(option, function (art) {
const same: Artplayer = this
const other: Artplayer = art
void [same, other]
})
const factory: Artplayer.PluginFactory<Artplayer, { name: string }> = function (art) {
return { name: String(art.id + this.id) }
}
const plugins: Promise<Artplayer.Plugins> = player.plugins.add(factory)
const emitter: Artplayer.Emitter<{ value: [number] }> = new Artplayer.Emitter()
emitter.on('value', count => count.toFixed()).emit('value', 2)
const language: NonNullable<Artplayer.I18n['en']> = { Play: 'Play' }
const configuration: Artplayer.Config = Artplayer.config
// @ts-expect-error Private flattened definitions must not leak into the editor global scope.
const leaked: ArtplayerDefinitions.Config = Artplayer.config
// @ts-expect-error Required container is not optional.
new Artplayer({ url: '' })
// @ts-expect-error Named aliases must not degrade to any through circular definitions.
const invalidOption: Artplayer.Option = { container: 123, url: '' }
// @ts-expect-error Plugin callback results retain the selected result type.
const invalidFactory: Artplayer.PluginFactory<Artplayer, number> = () => 'bad'
// @ts-expect-error Generic event payloads stay checked.
emitter.emit('value', 'bad')
void [plugins, language, configuration, invalidOption, invalidFactory, leaked]
`],
[path.join(folder, 'module.ts'), `
import Player = require('./artplayer')
const player: Player = new Player({ container: '#player' })
const option: Player.OptionInput = { container: '#player' }
// @ts-expect-error The UMD export is the constructor, not a default wrapper object.
Player.default
void [player, option]
`],
])
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
const host = compiler.createCompilerHost(options)
const getSourceFile = host.getSourceFile.bind(host)
const exists = host.fileExists.bind(host)
const directoryExists = host.directoryExists?.bind(host)
host.fileExists = file => sources.has(path.resolve(file)) || exists(file)
host.directoryExists = directory => path.resolve(directory) === folder || !!directoryExists?.(directory)
host.getSourceFile = (file, version, onError, create) => sources.has(path.resolve(file))
? compiler.createSourceFile(file, sources.get(path.resolve(file))!, version, true)
: getSourceFile(file, version, onError, create)
const program = compiler.createProgram([...sources.keys()], options, host)
for (const file of program.getSourceFiles())
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Editor types escaped the standalone bundle: ${file.fileName}`)
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
}
@@ -0,0 +1,23 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { generateDtsBundle } from 'dts-bundle-generator'
import ts from 'typescript'
import { parseDeclaration } from './syntax.ts'
export function vastSdkDeclarations(): ts.Statement[] {
const root = fileURLToPath(new URL('../../', import.meta.url))
const [code] = generateDtsBundle([{
filePath: path.join(root, 'scripts/editor-declarations/vast-sdk.ts'),
libraries: { inlinedLibraries: ['@glomex/vast-ima-player', '@alugha/ima'], allowedTypesLibraries: [] },
output: { noBanner: true, exportReferencedTypes: false },
}], { preferredConfigPath: path.join(root, 'scripts/tsconfig.editor.json') })
assert(code, 'SDK bundle is empty')
return [...parseDeclaration(code, 'vast-sdk.d.ts').statements].filter((node) => {
if (!ts.isExportDeclaration(node))
return true
assert(!node.moduleSpecifier && node.exportClause && ts.isNamedExports(node.exportClause)
&& node.exportClause.elements.length === 0, 'Unexpected SDK re-export')
return false
})
}
+90
View File
@@ -0,0 +1,90 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import { ESLint } from 'eslint'
import { globSync } from 'glob'
import ts from 'typescript'
import compat from 'typescript-compat'
import { generateCoreEditorDeclaration } from './core.ts'
import { vastSdkDeclarations } from './dependencies.ts'
import { generatePluginEditorDeclaration } from './plugin.ts'
import { checkStandaloneDeclarations } from './validation.ts'
export function pluginIdentity(file: string): { name: string, file: string, packageName: string } {
const packageName = path.basename(path.dirname(path.dirname(file)))
const names: Record<string, string> = { 'artplayer-tool-iframe': 'ArtplayerToolIframe', 'artplayer-tool-thumbnail': 'ArtplayerToolThumbnail' }
const name = names[packageName] || packageName.split('-').map((word, index) => index ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('')
return { name, file: path.basename(file), packageName }
}
export function editorLibUris(code: string, files: string[]): string {
const source = ts.createSourceFile('common.js', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS)
const lists: ts.ArrayLiteralExpression[] = []
const visit = (node: ts.Node): void => {
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'libUris') {
assert(node.initializer && ts.isArrayLiteralExpression(node.initializer), 'libUris must be an array initializer')
lists.push(node.initializer)
}
ts.forEachChild(node, visit)
}
visit(source)
assert(lists.length === 1 && lists[0], 'Expected exactly one libUris declaration')
const list = lists[0]
const value = `[\n ${files.map(file => `'./assets/ts/${file}'`).join(',\n ')},\n ]`
return code.slice(0, list.getStart(source)) + value + code.slice(list.end)
}
export async function generateEditorDeclarations(selected: string[] = []): Promise<Map<string, string>> {
const available = globSync('packages/artplayer-*-*/types/*.d.ts').filter(file => path.basename(file) === `${pluginIdentity(file).packageName}.d.ts`).sort()
for (const name of selected)
assert(available.some(file => pluginIdentity(file).packageName === name), `Unknown declaration package: ${name}`)
assert(new Set(selected).size === selected.length, 'Duplicate declaration package selection')
const sources = selected.length ? available.filter(file => selected.includes(pluginIdentity(file).packageName)) : available
const core = generateCoreEditorDeclaration()
const outputs = new Map<string, string>([['docs/assets/ts/artplayer.d.ts', core]])
const pluginFiles: string[] = []
for (const sourceFile of sources) {
const { name, file, packageName } = pluginIdentity(sourceFile)
const localTypes: Record<string, string> = packageName === 'artplayer-proxy-mediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(sourceFile), 'media.d.ts'), 'utf8') } : {}
const dependencies = packageName === 'artplayer-plugin-vast' ? vastSdkDeclarations() : []
const code = generatePluginEditorDeclaration(fs.readFileSync(sourceFile, 'utf8'), name, localTypes, dependencies)
outputs.set(path.join('docs/assets/ts', file), code)
pluginFiles.push(file)
}
const languageFile = 'artplayer-i18n.d.ts'
outputs.set(path.join('docs/assets/ts', languageFile), `declare module 'artplayer/i18n/*' {\n const language: NonNullable<Artplayer.I18n['en']>\n export default language\n}\n`)
const eslint = new ESLint({ fix: true, fixTypes: ['layout'] })
for (const [file, code] of outputs) {
const [result] = await eslint.lintText(code, { filePath: file })
assert(result && !result.errorCount, `Invalid formatted editor declaration ${file}: ${JSON.stringify(result?.messages)}`)
outputs.set(file, result.output || code)
}
// Both actual compilers check the formatted files together, including global collisions.
const declarations = new Map([...outputs].map(([file, code]) => [path.basename(file), code]))
for (const compiler of [ts, compat as unknown as typeof ts])
assert.deepEqual(checkStandaloneDeclarations(declarations, compiler), [], `Invalid editor declarations (TS ${compiler.version})`)
if (!selected.length) {
const file = 'docs/assets/js/common.js'
outputs.set(file, editorLibUris(fs.readFileSync(file, 'utf8'), [...pluginFiles.sort(), 'artplayer.d.ts', languageFile]))
}
if (sources.some(file => pluginIdentity(file).packageName === 'artplayer-plugin-vast')) {
const notices = [['@glomex/vast-ima-player', 'LICENSE'], ['@alugha/ima', 'LICENSE.md']].map(([name, file]) => `${name}\n${fs.readFileSync(path.join('node_modules', name!, file!), 'utf8').replaceAll('\r\n', '\n').trim()}\n`).join('\n')
outputs.set('docs/assets/ts/artplayer-plugin-vast.LICENSE.txt', notices)
}
return outputs
}
export async function runEditorDeclarations(args: string[]): Promise<void> {
assert(args.every(arg => arg === '--check' || !arg.startsWith('-')), 'Use yarn build:ts [--check] [package ...]')
const outputs = await generateEditorDeclarations(args.filter(arg => arg !== '--check'))
for (const [file, code] of outputs) {
if (args.includes('--check')) {
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), code.replaceAll('\r\n', '\n'), `Editor declaration drift: ${file}`)
}
else {
fs.mkdirSync(path.dirname(file), { recursive: true })
fs.writeFileSync(file, code)
}
}
console.log(`Editor declarations ${args.includes('--check') ? 'checked' : 'generated'}: ${outputs.size} outputs; all selected declarations checked with current and compatibility compilers`)
}
+185
View File
@@ -0,0 +1,185 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
import { exportedDefinition, parseDeclaration } from './syntax.ts'
// Migrate each package explicitly; unknown imports/exports must not disappear silently.
export function generatePluginEditorDeclaration(code: string, name: string, localTypes: Record<string, string> = {}, dependencies: ts.Statement[] = []) {
assert(/^[a-z_$][\w$]*$/i.test(name), 'Invalid plugin global')
let source = parseDeclaration(code)
const expanded: ts.Statement[] = []
for (const node of source.statements) {
if (!ts.isExportDeclaration(node) || !node.moduleSpecifier) {
expanded.push(node)
continue
}
assert(node.isTypeOnly, 'Unsupported plugin editor declaration: runtime re-export')
assert(ts.isStringLiteral(node.moduleSpecifier), 'Expected a literal type module')
const dependency = localTypes[node.moduleSpecifier.text]
assert(typeof dependency === 'string' && node.exportClause && ts.isNamedExports(node.exportClause), 'Unsupported editor type re-export')
const names = node.exportClause.elements.map((item) => {
assert(!item.propertyName, 'Renamed editor type re-export is unsupported')
return item.name.text
})
const types = parseDeclaration(dependency, 'dependency.d.ts')
const definitions = types.statements.filter(item => !ts.isImportDeclaration(item))
assert(definitions.every(item => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item)), 'Editor dependencies must contain only types')
assert.deepEqual(definitions.map(item => item.name.text).sort(), names.sort(), 'Editor re-export must explicitly cover its local type definitions')
expanded.push(...types.statements)
}
// Reparse combined statements so comments and text positions belong to one file.
source = ts.createSourceFile('plugin.d.ts', expanded.map(node => ts.createPrinter().printNode(ts.EmitHint.Unspecified, node, node.getSourceFile())).join('\n'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
if (source.statements.some(node => ts.isExportAssignment(node) && node.isExportEquals))
return generateCommonJSPluginEditor(source, name)
const factory = ts.factory
const definitions: ts.Statement[] = []
const globals: ts.ModuleDeclaration[] = []
const aliases: ts.TypeAliasDeclaration[] = []
const internal = `${name}Definitions`
let exported = false
let callable = false
let classExport = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(ts.isStringLiteral(node.moduleSpecifier), 'Expected a literal import')
if (dependencies.length && node.moduleSpecifier.text === '@glomex/vast-ima-player') {
const bindings = node.importClause?.namedBindings
assert(node.importClause?.isTypeOnly && !node.importClause.name && bindings && ts.isNamedImports(bindings), 'Expected type-only SDK imports')
assert.deepEqual(bindings.elements.map((item) => {
assert(!item.propertyName)
return item.name.text
}).sort(), ['Player', 'PlayerOptions'])
continue
}
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported plugin editor import')
continue
}
if (ts.isModuleDeclaration(node)) {
assert(node.flags & ts.NodeFlags.GlobalAugmentation, 'Only explicit global augmentations are supported')
assert(node.body && ts.isModuleBlock(node.body) && node.body.statements.every(item => ts.isInterfaceDeclaration(item) && item.name.text === 'Window'), 'Only Window augmentations are supported')
globals.push(node)
continue
}
if (ts.isExportAssignment(node)) {
assert(!node.isExportEquals && ts.isIdentifier(node.expression) && node.expression.text === name, 'Unexpected plugin default export')
exported = true
continue
}
if (ts.isVariableStatement(node)) {
const values = node.declarationList.declarations
const value = values[0]
assert(values.length === 1 && value && ts.isIdentifier(value.name) && value.name.text === name && value.type && ts.isFunctionTypeNode(value.type), 'Unexpected plugin callable variable')
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
callable = true
continue
}
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node), 'Unsupported plugin editor declaration')
assert(node.name && node.name.text !== internal, 'Invalid plugin declaration name')
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin callable')
callable = true
classExport = ts.isClassDeclaration(node)
continue
}
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
aliases.push(factory.createTypeAliasDeclaration(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
node.name,
node.typeParameters,
factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), node.name), node.typeParameters?.map(parameter => factory.createTypeReferenceNode(parameter.name))),
))
}
}
assert(exported && callable, 'Plugin editor requires a default function or class')
const namespace = (identifier: string, statements: ts.Statement[]) => factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createIdentifier(identifier), factory.createModuleBlock(statements), ts.NodeFlags.Namespace)
const statements = [
...globals,
namespace(internal, definitions),
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
factory.createVariableDeclaration(name, undefined, factory.createTypeQueryNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name)))),
], ts.NodeFlags.Const)),
...(classExport ? [factory.createTypeAliasDeclaration(undefined, name, undefined, factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name))))] : []),
namespace(name, aliases),
factory.createExportAssignment(undefined, true, factory.createIdentifier(name)),
factory.createNamespaceExportDeclaration(factory.createIdentifier(name)),
]
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
const dependencySource = dependencies[0]?.getSourceFile()
assert(dependencies.every(node => node.getSourceFile() === dependencySource), 'SDK definitions must share one source file')
const dependencyNamespace = dependencySource
? `/* eslint-disable ts/method-signature-style, ts/consistent-type-definitions -- Preserve upstream SDK declaration shapes. */\n${printer.printNode(ts.EmitHint.Unspecified, namespace(internal, dependencies.map(exportedDefinition)), dependencySource)}\n/* eslint-enable ts/method-signature-style, ts/consistent-type-definitions */\n`
: ''
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n${dependencyNamespace}${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
function generateCommonJSPluginEditor(source: ts.SourceFile, name: string) {
const statements: ts.Statement[] = []
let namespace = false
let callable = false
let exported = false
let global = false
let classExport = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(ts.isStringLiteral(node.moduleSpecifier) && node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported CommonJS plugin editor import')
continue
}
if (ts.isModuleDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin namespace')
assert(node.body && ts.isModuleBlock(node.body) && node.body.statements.every(item => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item)), 'Editor namespace must contain only public type declarations')
namespace = true
}
else if (ts.isClassDeclaration(node)) {
assert.equal(node.name?.text, name, 'Unexpected CommonJS editor class')
callable = true
classExport = true
}
else if (ts.isVariableStatement(node)) {
const definitions = node.declarationList.declarations
assert.equal(definitions.length, 1)
const definition = definitions[0]
assert(definition && ts.isIdentifier(definition.name) && definition.type)
assert.equal(definition.name.text, name)
assert(ts.isTypeReferenceNode(definition.type) && ts.isQualifiedName(definition.type.typeName)
&& ts.isIdentifier(definition.type.typeName.left) && definition.type.typeName.left.text === name && definition.type.typeName.right.text === 'Factory', 'Editor export must use its public Factory interface')
callable = true
}
else if (ts.isExportAssignment(node)) {
assert(node.isExportEquals && ts.isIdentifier(node.expression) && node.expression.text === name, 'Unexpected CommonJS plugin export')
exported = true
}
else if (ts.isNamespaceExportDeclaration(node)) {
assert.equal(node.name.text, name)
global = true
}
else {
assert.fail('Unsupported CommonJS plugin editor declaration')
}
statements.push(node)
}
assert(namespace && callable && exported && global, 'Incomplete CommonJS plugin editor declaration')
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
const bridge = classExport ? '' : '/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n'
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n${bridge}${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
export function checkPluginEditorDeclaration(code: string, core: string, consumer = '', compiler: typeof ts = ts) {
const folder = fileURLToPath(new URL('../../refactor/.cache/plugin-editor-semantic/', import.meta.url))
const sources = new Map([
[path.join(folder, 'artplayer.d.ts'), core],
[path.join(folder, 'plugin.d.ts'), code],
[path.join(folder, 'consumer.ts'), consumer],
])
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
const host = compiler.createCompilerHost(options)
const getSourceFile = host.getSourceFile.bind(host)
host.getSourceFile = (file, version, onError, create) => sources.has(path.resolve(file))
? compiler.createSourceFile(file, sources.get(path.resolve(file))!, version, true)
: getSourceFile(file, version, onError, create)
const program = compiler.createProgram([...sources.keys()], options, host)
for (const file of program.getSourceFiles())
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Plugin editor types escaped the standalone bundle: ${file.fileName}`)
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
}
+18
View File
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict'
import ts from 'typescript'
export function parseDeclaration(code: string, file = 'plugin.d.ts'): ts.SourceFile {
const source = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
const options: ts.CompilerOptions = { noLib: true, noResolve: true, types: [] }
const host = ts.createCompilerHost(options)
host.getSourceFile = name => name === file ? source : undefined
const program = ts.createProgram([file], options, host)
assert.equal(program.getSyntacticDiagnostics(source).length, 0, 'Invalid plugin declaration syntax')
return source
}
export function exportedDefinition(node: ts.Statement): ts.Statement {
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node)
|| ts.isModuleDeclaration(node) || ts.isEnumDeclaration(node), 'Unsupported SDK declaration kind')
return ts.factory.replaceModifiers(node, [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)])
}
+24
View File
@@ -0,0 +1,24 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
export function checkStandaloneDeclarations(files: Map<string, string>, compiler: typeof ts = ts) {
const folder = path.resolve(fileURLToPath(new URL('../../refactor/.cache/editor-check/', import.meta.url)))
const sources = new Map([...files].map(([file, code]) => [path.join(folder, file), code]))
const options: ts.CompilerOptions = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
const host = compiler.createCompilerHost(options)
const getSourceFile = host.getSourceFile.bind(host)
const exists = host.fileExists.bind(host)
const directoryExists = host.directoryExists?.bind(host)
host.fileExists = file => sources.has(path.resolve(file)) || exists(file)
host.directoryExists = directory => path.resolve(directory) === folder || !!directoryExists?.(directory)
host.getSourceFile = (file, version, onError, create) => {
const code = sources.get(path.resolve(file))
return code === undefined ? getSourceFile(file, version, onError, create) : compiler.createSourceFile(file, code, version, true)
}
const program = compiler.createProgram([...sources.keys()], options, host)
for (const file of program.getSourceFiles())
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Editor types escaped the standalone bundle: ${file.fileName}`)
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, file: diagnostic.file && path.basename(diagnostic.file.fileName), message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
}
+2
View File
@@ -0,0 +1,2 @@
// Types only: the editor does not fetch or execute the advertising SDK.
export type { Player, PlayerOptions } from '@glomex/vast-ima-player'
+2 -143
View File
@@ -1,143 +1,2 @@
import assert from 'node:assert/strict'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { generateDtsBundle } from 'dts-bundle-generator'
import ts from 'typescript'
import compat from 'typescript-compat'
const root = fileURLToPath(new URL('../', import.meta.url))
const factory = ts.factory
/** Put flattened definitions in a private scope so public aliases cannot refer to themselves. */
export function asGlobalDeclaration(code, globalName) {
assert(/^[A-Z_$][\w$]*$/i.test(globalName), 'Invalid editor global name')
const source = ts.createSourceFile('bundle.d.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
const exports = new Map()
const declarations = new Map()
const definitions = []
for (const node of source.statements) {
if (ts.isExportDeclaration(node)) {
assert(!node.moduleSpecifier && node.exportClause && ts.isNamedExports(node.exportClause), 'Editor bundle must contain no unresolved exports')
for (const item of node.exportClause.elements)
exports.set(item.name.text, (item.propertyName || item.name).text)
continue
}
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isClassDeclaration(node), `Unsupported editor declaration: ${ts.SyntaxKind[node.kind]}`)
assert(node.name, 'Editor definitions must be named')
const name = node.name.text
assert(!declarations.has(name), `Duplicate bundled declaration: ${name}`)
declarations.set(name, node)
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword))
exports.set(name, name)
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
}
const defaultName = exports.get('default')
assert(defaultName && ts.isClassDeclaration(declarations.get(defaultName)), 'Core editor requires the actual default constructor class')
const definitionName = `${globalName}Definitions`
assert(!declarations.has(definitionName), 'Editor definition namespace collides with source')
const qualified = name => factory.createQualifiedName(factory.createIdentifier(definitionName), factory.createIdentifier(name))
const aliases = []
for (const [name, local] of exports) {
if (name === 'default')
continue
const declaration = declarations.get(local)
assert(declaration, `Missing bundled export: ${local}`)
const parameters = declaration.typeParameters
aliases.push(factory.createTypeAliasDeclaration(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
name,
parameters,
factory.createTypeReferenceNode(qualified(local), parameters?.map(parameter => factory.createTypeReferenceNode(parameter.name))),
))
}
const namespace = (name, nodes) => factory.createModuleDeclaration(
[factory.createModifier(ts.SyntaxKind.DeclareKeyword)],
factory.createIdentifier(name),
factory.createModuleBlock(nodes),
ts.NodeFlags.Namespace,
)
const statements = [
namespace(definitionName, definitions),
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
factory.createVariableDeclaration(globalName, undefined, factory.createTypeQueryNode(qualified(defaultName))),
], ts.NodeFlags.Const)),
factory.createTypeAliasDeclaration(undefined, globalName, undefined, factory.createTypeReferenceNode(qualified(defaultName))),
namespace(globalName, aliases),
factory.createExportAssignment(undefined, true, factory.createIdentifier(globalName)),
factory.createNamespaceExportDeclaration(factory.createIdentifier(globalName)),
]
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
return `// Generated from packages/artplayer/public/artplayer.ts by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare, ts/no-namespace -- UMD constructor and named types share the global export. */\n${printer.printFile(factory.updateSourceFile(source, statements))}`
}
export function generateCoreEditorDeclaration() {
const require = createRequire(import.meta.url)
const bundlerRequire = createRequire(require.resolve('dts-bundle-generator'))
assert.equal(bundlerRequire('typescript'), ts, 'Editor bundler must use the pinned workspace compiler')
const [code] = generateDtsBundle([{
filePath: path.join(root, 'packages/artplayer/public/artplayer.ts'),
output: { exportReferencedTypes: false, noBanner: true },
}], { preferredConfigPath: path.join(root, 'scripts/tsconfig.editor.json') })
const declaration = asGlobalDeclaration(code, 'Artplayer')
for (const compiler of [ts, compat])
assert.deepEqual(checkCoreEditorDeclaration(declaration, compiler), [], `Invalid editor declaration: TS ${compiler.version}`)
return declaration
}
export function checkCoreEditorDeclaration(code, compiler = ts) {
const folder = path.join(root, 'refactor/.cache/editor-semantic')
const declaration = path.join(folder, 'artplayer.d.ts')
const sources = new Map([
[declaration, code],
[path.join(folder, 'global.ts'), `
const option: Artplayer.OptionInput = { container: '#player' }
const player: Artplayer = new Artplayer(option, function (art) {
const same: Artplayer = this
const other: Artplayer = art
void [same, other]
})
const factory: Artplayer.PluginFactory<Artplayer, { name: string }> = function (art) {
return { name: String(art.id + this.id) }
}
const plugins: Promise<Artplayer.Plugins> = player.plugins.add(factory)
const emitter: Artplayer.Emitter<{ value: [number] }> = new Artplayer.Emitter()
emitter.on('value', count => count.toFixed()).emit('value', 2)
const language: NonNullable<Artplayer.I18n['en']> = { Play: 'Play' }
const configuration: Artplayer.Config = Artplayer.config
// @ts-expect-error Private flattened definitions must not leak into the editor global scope.
const leaked: ArtplayerDefinitions.Config = Artplayer.config
// @ts-expect-error Required container is not optional.
new Artplayer({ url: '' })
// @ts-expect-error Named aliases must not degrade to any through circular definitions.
const invalidOption: Artplayer.Option = { container: 123, url: '' }
// @ts-expect-error Plugin callback results retain the selected result type.
const invalidFactory: Artplayer.PluginFactory<Artplayer, number> = () => 'bad'
// @ts-expect-error Generic event payloads stay checked.
emitter.emit('value', 'bad')
void [plugins, language, configuration, invalidOption, invalidFactory, leaked]
`],
[path.join(folder, 'module.ts'), `
import Player = require('./artplayer')
const player: Player = new Player({ container: '#player' })
const option: Player.OptionInput = { container: '#player' }
// @ts-expect-error The UMD export is the constructor, not a default wrapper object.
Player.default
void [player, option]
`],
])
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
const host = compiler.createCompilerHost(options)
const getSourceFile = host.getSourceFile.bind(host)
const exists = host.fileExists.bind(host)
const directoryExists = host.directoryExists.bind(host)
host.fileExists = file => sources.has(path.resolve(file)) || exists(file)
host.directoryExists = directory => path.resolve(directory) === folder || directoryExists(directory)
host.getSourceFile = (file, version, onError, create) => sources.has(path.resolve(file))
? compiler.createSourceFile(file, sources.get(path.resolve(file)), version, true)
: getSourceFile(file, version, onError, create)
const program = compiler.createProgram([...sources.keys()], options, host)
for (const file of program.getSourceFiles())
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Editor types escaped the standalone bundle: ${file.fileName}`)
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
}
// Preserve existing tooling imports while implementation lives in checked TypeScript.
export { asGlobalDeclaration, checkCoreEditorDeclaration, generateCoreEditorDeclaration } from './editor-declarations/core.ts'
+2 -160
View File
@@ -1,160 +1,2 @@
import assert from 'node:assert/strict'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import ts from 'typescript'
// Migrate each package explicitly; unknown imports/exports must not disappear silently.
export function generatePluginEditorDeclaration(code, name, localTypes = {}) {
assert(/^[a-z_$][\w$]*$/i.test(name), 'Invalid plugin global')
let source = ts.createSourceFile('plugin.d.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
assert.equal(source.parseDiagnostics.length, 0, 'Invalid plugin declaration syntax')
const expanded = []
for (const node of source.statements) {
if (!ts.isExportDeclaration(node) || !node.moduleSpecifier) {
expanded.push(node)
continue
}
assert(node.isTypeOnly, 'Unsupported plugin editor declaration: runtime re-export')
const dependency = localTypes[node.moduleSpecifier.text]
assert(typeof dependency === 'string' && node.exportClause && ts.isNamedExports(node.exportClause), 'Unsupported editor type re-export')
const names = node.exportClause.elements.map((item) => {
assert(!item.propertyName, 'Renamed editor type re-export is unsupported')
return item.name.text
})
const types = ts.createSourceFile('dependency.d.ts', dependency, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
assert.equal(types.parseDiagnostics.length, 0)
const definitions = types.statements.filter(item => !ts.isImportDeclaration(item))
assert(definitions.every(item => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item)), 'Editor dependencies must contain only types')
assert.deepEqual(definitions.map(item => item.name.text).sort(), names.sort(), 'Editor re-export must explicitly cover its local type definitions')
expanded.push(...types.statements)
}
// Reparse combined statements so comments and text positions belong to one file.
source = ts.createSourceFile('plugin.d.ts', expanded.map(node => ts.createPrinter().printNode(ts.EmitHint.Unspecified, node, node.getSourceFile())).join('\n'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS)
if (source.statements.some(node => ts.isExportAssignment(node) && node.isExportEquals))
return generateCommonJSPluginEditor(source, name)
const factory = ts.factory
const definitions = []
const aliases = []
const internal = `${name}Definitions`
let exported = false
let callable = false
let classExport = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported plugin editor import')
continue
}
if (ts.isExportAssignment(node)) {
assert(!node.isExportEquals && ts.isIdentifier(node.expression) && node.expression.text === name, 'Unexpected plugin default export')
exported = true
continue
}
if (ts.isVariableStatement(node)) {
const values = node.declarationList.declarations
assert(values.length === 1 && values[0].name.text === name && ts.isFunctionTypeNode(values[0].type), 'Unexpected plugin callable variable')
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
callable = true
continue
}
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node), 'Unsupported plugin editor declaration')
assert(node.name && node.name.text !== internal, 'Invalid plugin declaration name')
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin callable')
callable = true
classExport = ts.isClassDeclaration(node)
continue
}
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
aliases.push(factory.createTypeAliasDeclaration(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
node.name,
node.typeParameters,
factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), node.name), node.typeParameters?.map(parameter => factory.createTypeReferenceNode(parameter.name))),
))
}
}
assert(exported && callable, 'Plugin editor requires a default function or class')
const namespace = (identifier, statements) => factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createIdentifier(identifier), factory.createModuleBlock(statements), ts.NodeFlags.Namespace)
const statements = [
namespace(internal, definitions),
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
factory.createVariableDeclaration(name, undefined, factory.createTypeQueryNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name)))),
], ts.NodeFlags.Const)),
...(classExport ? [factory.createTypeAliasDeclaration(undefined, name, undefined, factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name))))] : []),
namespace(name, aliases),
factory.createExportAssignment(undefined, true, factory.createIdentifier(name)),
factory.createNamespaceExportDeclaration(factory.createIdentifier(name)),
]
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
function generateCommonJSPluginEditor(source, name) {
const statements = []
let namespace = false
let callable = false
let exported = false
let global = false
let classExport = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported CommonJS plugin editor import')
continue
}
if (ts.isModuleDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin namespace')
assert(node.body && ts.isModuleBlock(node.body) && node.body.statements.every(item => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item)), 'Editor namespace must contain only public type declarations')
namespace = true
}
else if (ts.isClassDeclaration(node)) {
assert.equal(node.name?.text, name, 'Unexpected CommonJS editor class')
callable = true
classExport = true
}
else if (ts.isVariableStatement(node)) {
const definitions = node.declarationList.declarations
assert.equal(definitions.length, 1)
const definition = definitions[0]
assert.equal(definition.name.text, name)
assert(ts.isTypeReferenceNode(definition.type) && ts.isQualifiedName(definition.type.typeName)
&& definition.type.typeName.left.text === name && definition.type.typeName.right.text === 'Factory', 'Editor export must use its public Factory interface')
callable = true
}
else if (ts.isExportAssignment(node)) {
assert(node.isExportEquals && node.expression.text === name, 'Unexpected CommonJS plugin export')
exported = true
}
else if (ts.isNamespaceExportDeclaration(node)) {
assert.equal(node.name.text, name)
global = true
}
else {
assert.fail('Unsupported CommonJS plugin editor declaration')
}
statements.push(node)
}
assert(namespace && callable && exported && global, 'Incomplete CommonJS plugin editor declaration')
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
const bridge = classExport ? '' : '/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */\n'
return `// Generated from the package public declaration by yarn build:ts. Do not edit.\n${bridge}${statements.map(node => printer.printNode(ts.EmitHint.Unspecified, node, source)).join('\n')}\n`
}
export function checkPluginEditorDeclaration(code, core, consumer = '', compiler = ts) {
const folder = fileURLToPath(new URL('../refactor/.cache/plugin-editor-semantic/', import.meta.url))
const sources = new Map([
[path.join(folder, 'artplayer.d.ts'), core],
[path.join(folder, 'plugin.d.ts'), code],
[path.join(folder, 'consumer.ts'), consumer],
])
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs }
const host = compiler.createCompilerHost(options)
const getSourceFile = host.getSourceFile.bind(host)
host.getSourceFile = (file, version, onError, create) => sources.has(path.resolve(file))
? compiler.createSourceFile(file, sources.get(path.resolve(file)), version, true)
: getSourceFile(file, version, onError, create)
const program = compiler.createProgram([...sources.keys()], options, host)
for (const file of program.getSourceFiles())
assert(sources.has(path.resolve(file.fileName)) || program.isSourceFileDefaultLibrary(file), `Plugin editor types escaped the standalone bundle: ${file.fileName}`)
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n') }))
}
// Preserve existing tooling imports while implementation lives in checked TypeScript.
export { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from './editor-declarations/plugin.ts'
+16 -3
View File
@@ -1,10 +1,23 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"types": ["node"],
"lib": [
"ES2021",
"DOM",
"DOM.Iterable"
],
"types": [
"node"
],
"allowImportingTsExtensions": true,
"checkJs": true
},
"include": ["docs-smoke/**/*.ts", "build-test.js"]
"include": [
"docs-smoke/**/*.ts",
"build-test.js",
"build-ts.js",
"editor-declarations/**/*.ts",
"editor-types.mjs",
"plugin-editor-types.mjs"
]
}
+5
View File
@@ -1,5 +1,10 @@
# Tests and fixture ownership
Editor tooling lives in `scripts/editor-declarations/`; see its README.
`test/editor-types.test.js` checks standalone globals and module consumers with
current/old compilers, Chapter/VAST regressions, generated files and library-list
failures. Package baseline tests continue to exercise the old MJS import paths.
Use the pinned Node/Yarn toolchain from `../refactor/toolchain-setup.md`.
`yarn build:test` generates deterministic documentation readiness smoke and its
+6
View File
@@ -1,5 +1,11 @@
# Browser regression entry
`editor-declarations.spec.js` loads the repository Monaco with the 22 declarations
actually listed in `common.js`, checks positive/negative consumers and runs emitted
Chapter code through ready/destroy with controlled media. VAST coverage is types
only, not advertising SDK execution. The unreferenced legacy WebSR declaration
asset is preserved; this test follows the actual editor's library list.
`docs-smoke.spec.js` verifies the documentation runner in three engines with
actual core/media, controlled failures and three selected generated snippets.
It checks pending readiness beyond the former 100ms timer, returned/unhandled
+82
View File
@@ -0,0 +1,82 @@
import fs from 'node:fs'
import { hash } from '../../refactor/scripts/releases.mjs'
import { expect, test } from './fixtures.js'
test('Monaco checks all editor declarations together and runs the Chapter consumer', async ({ page }, testInfo) => {
const common = fs.readFileSync('docs/assets/js/common.js', 'utf8')
const list = common.match(/let libUris = \[([\s\S]*?)\]/)?.[1] || ''
const names = [...list.matchAll(/'\.\/assets\/ts\/([^']+\.d\.ts)'/g)].map(match => match[1])
expect(names).toHaveLength(22)
await testInfo.attach('editor-declaration-inputs', { contentType: 'application/json', body: JSON.stringify(Object.fromEntries(names.map(name => [name, hash(fs.readFileSync(`docs/assets/ts/${name}`))]))) })
await page.goto('/test/player.html?core=candidate&chapter=candidate')
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
const result = await page.evaluate(async (names) => {
window.require.config({ paths: { vs: '/assets/js/vs' } })
await new Promise((resolve, reject) => window.require(['vs/editor/editor.main'], resolve, reject))
const api = window.monaco
const types = api.languages.typescript
types.typescriptDefaults.setCompilerOptions({ target: types.ScriptTarget.ES2020, strict: true, skipLibCheck: false, noEmit: false, types: [], allowNonTsExtensions: true })
const libraries = await Promise.all(names.map(async (name) => {
const source = await (await fetch(`/assets/ts/${name}`)).text()
const uri = `file:///${name}`
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
}))
const model = api.editor.createModel(`
const chapters: artplayerPluginChapter.Chapters = [{ start: 0, end: 2, title: 'Intro' }];
const player = new Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, plugins: [artplayerPluginChapter({ chapters })] });
const chapter = player.plugins.artplayerPluginChapter as artplayerPluginChapter.Result;
chapter.update({ chapters: [{ start: 0, end: 1, title: 'Updated' }] });
const adOption: artplayerPluginVast.ArtplayerPluginVastOption = context => {
const volume: number = context.init().volume;
context.playerOptions.autoResize = true;
return Promise.resolve();
};
`, 'typescript', api.Uri.parse('file:///editor-consumer.ts'))
const invalid = api.editor.createModel(`
artplayerPluginVast(context => { context.init().volume = 'loud'; context.playerOptions.autoResize = 'yes'; });
artplayerPluginChapter({ chapters: [{ start: '0', end: 2, title: '' }] });
`, 'typescript', api.Uri.parse('file:///editor-invalid.ts'))
try {
const worker = await (await types.getTypeScriptWorker())(model.uri, invalid.uri)
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
const declarations = (await Promise.all(libraries.map(lib => worker.getSemanticDiagnostics(lib.uri)))).flat()
const bad = (await worker.getSemanticDiagnostics(invalid.uri.toString())).map(item => item.code)
if (syntax.length || semantic.length || declarations.length)
return { syntax, semantic, declarations, bad }
const output = await worker.getEmitOutput(model.uri.toString())
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
if (!script)
throw new Error('Monaco did not emit the consumer')
// eslint-disable-next-line no-new-func -- Execute the actual editor output with local controlled media.
const player = new Function(`${script.text}\nreturn player`)()
try {
await new Promise((resolve, reject) => {
player.on('ready', resolve)
player.on('error', reject)
})
const name = player.plugins.artplayerPluginChapter.name
const ready = player.isReady
player.destroy(true)
return { syntax, semantic, declarations, bad, name, ready, instances: window.Artplayer.instances.length }
}
finally {
if (!player.isDestroy)
player.destroy(true)
}
}
finally {
model.dispose()
invalid.dispose()
for (const library of libraries) library.handle.dispose()
}
}, names)
await testInfo.attach('editor-declaration-result', { contentType: 'application/json', body: JSON.stringify(result) })
expect(result.syntax).toEqual([])
expect(result.semantic).toEqual([])
expect(result.declarations).toEqual([])
expect(result.bad).toEqual([2322, 2322, 2322])
expect(result.name).toBe('artplayerPluginChapter')
expect(result.ready).toBe(true)
expect(result.instances).toBe(0)
})
+71
View File
@@ -4,6 +4,9 @@ import fs from 'node:fs'
import { test } from 'node:test'
import { ESLint } from 'eslint'
import compat from 'typescript-compat'
import { vastSdkDeclarations } from '../scripts/editor-declarations/dependencies.ts'
import { editorLibUris, generateEditorDeclarations } from '../scripts/editor-declarations/generate.ts'
import { checkStandaloneDeclarations } from '../scripts/editor-declarations/validation.ts'
import { asGlobalDeclaration, checkCoreEditorDeclaration, generateCoreEditorDeclaration } from '../scripts/editor-types.mjs'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../scripts/plugin-editor-types.mjs'
@@ -21,6 +24,74 @@ test('editor declarations are reproducible, standalone and preserve constructor/
assert.throws(() => asGlobalDeclaration('export { default } from "./missing"', 'Artplayer'), /unresolved exports/)
})
test('Chapter and VAST editor consumers preserve SDK types, optional Window hook and named results', () => {
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const generated = new Map([['artplayer.d.ts', core]])
for (const [suffix, global] of [['chapter', 'artplayerPluginChapter'], ['vast', 'artplayerPluginVast']]) {
const source = fs.readFileSync(`packages/artplayer-plugin-${suffix}/types/artplayer-plugin-${suffix}.d.ts`, 'utf8')
const old = `${source.replace(/^import.*$/gim, '')}\nexport = ${global};\nexport as namespace ${global};\n`
const diagnostics = checkPluginEditorDeclaration(old, core)
assert(diagnostics.some(item => item.code === 2309))
if (suffix === 'vast')
assert(diagnostics.some(item => item.code === 2304 && item.message.includes('Player')))
generated.set(`${suffix}.d.ts`, generatePluginEditorDeclaration(source, global, {}, suffix === 'vast' ? vastSdkDeclarations() : []))
}
const consumer = `
import chapter = require('./chapter');
import vast = require('./vast');
declare const art: Artplayer;
const chapters: chapter.Chapters = [{ start: 0, end: 10, title: 'Intro' }];
const result: chapter.Result = chapter({ chapters })(art);
result.update({ chapters: [] });
const option: vast.ArtplayerPluginVastOption = async context => {
const player = context.init();
const volume: number = player.volume;
player.volume = 0.5;
player.pause();
player.play();
context.playUrl('ad.xml', { oldUntypedConfig: true });
// @ts-expect-error SDK numeric property remains checked.
player.volume = 'loud';
// @ts-expect-error SDK methods must not degrade to any.
player.missingMethod();
// @ts-expect-error Private SDK implementation stays private.
player._setupIma();
void volume;
};
const instance: vast.ArtplayerPluginVastInstance = vast(option)(art);
const windowFactory: typeof vast | undefined = window.artplayerPluginVast;
// @ts-expect-error Historical required callback is still required.
vast();
// @ts-expect-error Chapter timestamps stay numeric.
chapter({ chapters: [{ start: '0', end: 1, title: '' }] });
// @ts-expect-error SDK internals are private to the declaration module.
declare const leaked: artplayerPluginVastDefinitions.Player;
void [result, instance, windowFactory, leaked];
`
generated.set('global.ts', 'declare const globalArt: Artplayer; const globalResult: artplayerPluginChapter.Result = artplayerPluginChapter()(globalArt);')
for (const compiler of [undefined, compat]) {
generated.set('consumer.ts', consumer)
assert.deepEqual(checkStandaloneDeclarations(generated, compiler), [])
generated.set('consumer.ts', consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
const errors = checkStandaloneDeclarations(generated, compiler)
assert.equal(errors.length, 6, 'Every intentional misuse must remain rejected')
}
})
test('all editor outputs are generated and checked together without modifying source declarations', async () => {
const outputs = await generateEditorDeclarations()
assert.equal([...outputs.keys()].filter(file => file.endsWith('.d.ts')).length, 22)
for (const [file, code] of outputs)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), code.replaceAll('\r\n', '\n'), `Stale editor output: ${file}`)
await assert.rejects(generateEditorDeclarations(['missing-plugin']), /Unknown declaration package/)
await assert.rejects(generateEditorDeclarations(['artplayer-plugin-chapter', 'artplayer-plugin-chapter']), /Duplicate/)
const source = 'const note = \'libUris\'; let libUris = [\'old\']; const untouched = 1'
assert.equal(editorLibUris(source, ['chapter.d.ts']), 'const note = \'libUris\'; let libUris = [\n \'./assets/ts/chapter.d.ts\',\n ]; const untouched = 1')
assert.throws(() => editorLibUris('const unrelated = []', []), /exactly one/)
assert.throws(() => editorLibUris('let libUris = []; function x() { let libUris = [] }', []), /exactly one/)
assert.throws(() => editorLibUris('let libUris = fetch()', []), /array initializer/)
})
test('ASR editor generation supports named types without mixing default and export assignment', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-asr/types/artplayer-plugin-asr.d.ts', 'utf8')
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')