mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
test(ambilight): [PKG-AMBILIGHT-05] checkpoint installed canvas combinations
This commit is contained in:
1 parent
e7f31b6fc8
commit
765ddb816f
19 files changed
+1032
-85
No files matched your search
@@ -135,7 +135,14 @@ jobs:
|
||||
run: yarn test:react-consumer 2>&1 | tee refactor/.cache/ci/react-consumer.log
|
||||
- name: Verify Vue installed consumers
|
||||
run: yarn test:vue-consumer 2>&1 | tee refactor/.cache/ci/vue-consumer.log
|
||||
- name: Run all three engines
|
||||
- name: Prepare installed browser plugin packages
|
||||
# Node/framework consumers above retain their two-package fixture scope.
|
||||
# These extra installed packages are required by the browser helpers.
|
||||
# Other ecosystem installation/SDK gates remain tracked by CI-01.
|
||||
run: |
|
||||
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas 2>&1 | tee refactor/.cache/ci/browser-packages.log
|
||||
node --input-type=module -e 'import fs from "node:fs"; const { output } = JSON.parse(fs.readFileSync("refactor/.cache/packages/latest.json")); fs.appendFileSync(process.env.GITHUB_ENV, `ARTPLAYER_BROWSER_ARTIFACTS=${output}/browser-artifacts.json\n`);'
|
||||
- name: Run installed browser checks
|
||||
run: yarn test:browser 2>&1 | tee refactor/.cache/ci/browser.log
|
||||
- name: Verify iframe cached history and interrupted navigation
|
||||
run: yarn test:iframe-history 2>&1 | tee refactor/.cache/ci/iframe-history.log
|
||||
|
||||
@@ -89,5 +89,15 @@ resized output geometry. Browser tests play real video with the actual workspace
|
||||
proxy and paint a uniform nine-color palette through its public post-processing
|
||||
callback; getImageData is native. Actual core 5.1.7 has no proxy option and uses
|
||||
VIDEO, so its case checks that capability boundary and native playback. Only 5.4.0
|
||||
and candidate cases validate the canvas proxy combination. These tests do not
|
||||
establish that the unrefactored proxy's own async draw loop is terminally safe.
|
||||
and candidate cases validate the canvas proxy combination. The combination now
|
||||
observes both native RAF owners and verifies that no proxy callback runs after
|
||||
destroy. Canvas-specific tests additionally cover paused seeks, source switching,
|
||||
native pixels and destruction inside the post-processing callback.
|
||||
|
||||
For installed-package verification run
|
||||
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`,
|
||||
set `ARTPLAYER_BROWSER_ARTIFACTS` to its `browser-artifacts.json`, then run the
|
||||
Ambilight lifecycle/proxy and Canvas lifecycle browser suites. Helpers verify the
|
||||
installed hashes and current package/build inputs; an explicit artifact map never
|
||||
falls back to a source build. This supplements source tests. It does not replace
|
||||
physical mobile devices, npm entrypoint/type matrices or the remaining 05/06 gates.
|
||||
@@ -1,5 +1,19 @@
|
||||
# Ambilight 测试维护
|
||||
|
||||
## 已安装产物检查点
|
||||
|
||||
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`
|
||||
生成包含两包的实际隔离安装及 browser-artifacts.json。把该路径传给
|
||||
ARTPLAYER_BROWSER_ARTIFACTS 后,Ambilight/Canvas helper 校验当前源码与构建快照
|
||||
及安装文件摘要,不回退内存构建。原生生命周期/代理组合测试可直接复用;同目录
|
||||
的 legacy map 必须指向 tarball 中已登记的 legacy 文件。
|
||||
|
||||
现代45项、增强组合销毁断言后的现代9项、legacy45项三引擎全部通过;49+57+10项
|
||||
专项/工程检查通过。两个原生 RAF owner 的销毁后归零和 proxy 绘制停止都有断言。
|
||||
见[安装证据](baselines/ambilight-installed-validation.json)与
|
||||
[检查点记录](changes/2026-09-14-PKG-AMBILIGHT-05-installed-combination.md)。
|
||||
05仍doing,物理移动设备与完整分发验收未完成;以下早期记录保留其当时范围。
|
||||
|
||||
先读 [发布契约](baselines/ambilight-contract.md)。ambilight-release.json冻结两个真实
|
||||
发布及工作区Git来源,verifyAmbilightContract逐成员核对,不随候选更新历史哈希。
|
||||
|
||||
|
||||
@@ -0,0 +1,732 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-AMBILIGHT-05",
|
||||
"status": "checkpoint",
|
||||
"environment": {
|
||||
"node": "24.21.0",
|
||||
"yarn": "1.22.22",
|
||||
"browsers": {
|
||||
"chromium": "153.0.8010.12",
|
||||
"firefox": "155.0",
|
||||
"webkit": "26.6"
|
||||
},
|
||||
"physicalDevices": false,
|
||||
"remoteCI": false
|
||||
},
|
||||
"packageRuns": [
|
||||
{
|
||||
"path": "refactor/.cache/packages/run-IstjHZ",
|
||||
"reportSha256": "ac1d90ef3fe400d81bfc3dda9ee3098c611d6bf0f8a51c59f233210b40459568"
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/packages/run-IIEOt4",
|
||||
"reportSha256": "6efd5011592a1f2d81b785934c5365f6bb3cd4d05568a40c8465e9255b2df2a1"
|
||||
}
|
||||
],
|
||||
"packages": [
|
||||
{
|
||||
"name": "artplayer",
|
||||
"archive": "artplayer.tgz",
|
||||
"sha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454",
|
||||
"changedSinceInitialRun": [],
|
||||
"runtime": {
|
||||
"package/dist/artplayer.js": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6",
|
||||
"package/dist/artplayer.legacy.js": "9bde35a6d64234cae15300e37b03204c075ef26126058f48bb3bf97d6e002449",
|
||||
"package/dist/artplayer.mjs": "265bca0ad3f8373acc84333c19cdded0cbd6b309ede9e51c0bec35685c77eebd",
|
||||
"package/dist/i18n/ar.js": "bd0bef65f4f7dc3f8ed562904cc6fa9071ff4537fb64c1e0fc3d554141bcd3cb",
|
||||
"package/dist/i18n/ar.mjs": "59310b7bbeea83bbb7d73aadc5ca82a6ec269c3530d19aa741c94d5c64fc5da8",
|
||||
"package/dist/i18n/cs.js": "6e2c93daba24c2e48dfa206bcf4f2f2a8a02713b856e404d2e4e4ecd8309ca2f",
|
||||
"package/dist/i18n/cs.mjs": "c73fba18797d62d7b2581e67fe7340c35fe84510b2250ff1ecaad253d9cabbac",
|
||||
"package/dist/i18n/es.js": "f4133ac6ea06b69a474b33c4a1809a4066e2376bb9464a3f3aa8330f01f1944a",
|
||||
"package/dist/i18n/es.mjs": "1390bca3def2be115e811de337df0a0c3f63998e51359f283023246245c552a7",
|
||||
"package/dist/i18n/fa.js": "8d3ef2e0f0cc0ea480c2167b6fcb87cdf444e90759c3519e8daa139fec576da4",
|
||||
"package/dist/i18n/fa.mjs": "ab441e8a2d79043b29ceca87c783bf06874bb9d3a149c1604bc167eb481be2a9",
|
||||
"package/dist/i18n/fr.js": "89dc4194a6c0a962b9ce756b52c90401c1acf3b159f265f3bc0d7d6d7d987f03",
|
||||
"package/dist/i18n/fr.mjs": "99491878430fb7b6f2b7bf93da0588a831704b412e01116f23ba1d9d45863816",
|
||||
"package/dist/i18n/id.js": "4c6dc86db741bcc2857df55a9d037e82e91d375fa6fd22e0562809ff621b92db",
|
||||
"package/dist/i18n/id.mjs": "383e5803a6b6686c6de4e867770241de75b6f517dcca2bdedc6f2004bf6e1fb3",
|
||||
"package/dist/i18n/pl.js": "3a38828b1c3d719c0aca9906cf6a46c23555a8411f8d57645f0e0ce8147dc664",
|
||||
"package/dist/i18n/pl.mjs": "079ed96732979035297af387c64a7b99519b5a43107fb86735a654ea4fe8e9b5",
|
||||
"package/dist/i18n/ru.js": "62fac9dddb302eb16382e6e9301fc4b2c66f5f69d4a358a26b67026a5fa4e787",
|
||||
"package/dist/i18n/ru.mjs": "a893aeaee1d79409db72e324fbff200e3864f33ed168f04d91d25f7cf2efa765",
|
||||
"package/dist/i18n/tr.js": "1492c36b069a7d73db4f3913bd18085faff7afd8b3c158138fe264e31600e38c",
|
||||
"package/dist/i18n/tr.mjs": "a90478cac078836b76c2a98261907610fee07f3e4b28671b8e7dbf533778f918",
|
||||
"package/dist/i18n/vi.js": "c7216ef7e608a79b7e2c479a07bf310d4ab79577947ac4a62d676faaced0e9cf",
|
||||
"package/dist/i18n/vi.mjs": "66270fdfa0b5da396322d1769169b7df37a030ba15ac18eb30bd87b3af8074ec",
|
||||
"package/dist/i18n/zh-tw.js": "11c49857d6d40a5ffd224ffa59c9026e71621b345f63c5a086326db80a22371a",
|
||||
"package/dist/i18n/zh-tw.mjs": "a38e630cb96de96c15d824686aec750e25cf6c8ada01285a292442a81566314f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "artplayer-plugin-chapter",
|
||||
"archive": "artplayer-plugin-chapter.tgz",
|
||||
"sha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05",
|
||||
"changedSinceInitialRun": [],
|
||||
"runtime": {
|
||||
"package/dist/artplayer-plugin-chapter.js": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72",
|
||||
"package/dist/artplayer-plugin-chapter.legacy.js": "dade919ed044cbf46ac16e505f538f86a86edccf177da2e14d79d111559d4bef",
|
||||
"package/dist/artplayer-plugin-chapter.mjs": "754fbcfc60add74d6b6ad3bc8db253df3eb14ace13ac8a5439be0592bd5512d0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "artplayer-plugin-ambilight",
|
||||
"archive": "artplayer-plugin-ambilight.tgz",
|
||||
"sha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7",
|
||||
"changedSinceInitialRun": [
|
||||
"package/ARCHITECTURE.md"
|
||||
],
|
||||
"runtime": {
|
||||
"package/dist/artplayer-plugin-ambilight.js": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599",
|
||||
"package/dist/artplayer-plugin-ambilight.legacy.js": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d",
|
||||
"package/dist/artplayer-plugin-ambilight.mjs": "ef126959819c4973da27dbb1e60ccccc54386c23f8c7750ce1ba4d38f306aaaa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "artplayer-proxy-canvas",
|
||||
"archive": "artplayer-proxy-canvas.tgz",
|
||||
"sha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d",
|
||||
"changedSinceInitialRun": [],
|
||||
"runtime": {
|
||||
"package/dist/artplayer-proxy-canvas.js": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73",
|
||||
"package/dist/artplayer-proxy-canvas.legacy.js": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97",
|
||||
"package/dist/artplayer-proxy-canvas.mjs": "66ca7271f52545a8796f563f2d5ca04581bed8d60d218463923325acf5b73ccf"
|
||||
}
|
||||
}
|
||||
],
|
||||
"runtimeTypeScope": [
|
||||
"artplayer",
|
||||
"artplayer-plugin-chapter"
|
||||
],
|
||||
"additionalBrowserPackages": [
|
||||
"artplayer-plugin-ambilight",
|
||||
"artplayer-proxy-canvas"
|
||||
],
|
||||
"runtimeChecks": 36,
|
||||
"legacyTypeModes": 5,
|
||||
"preciseTypeModes": 8,
|
||||
"sourceInputs": [
|
||||
{
|
||||
"path": "scripts/package-check.mjs",
|
||||
"sha256": "d55266708fd2f6d23743daaf0a54c6543b8c2987503eb1fd54a05bbb11420a1a"
|
||||
},
|
||||
{
|
||||
"path": "scripts/installed-artifacts.mjs",
|
||||
"sha256": "0b1c85a48117551c7a3f06f5df8426fea19e302f874dd6ca2e44418dfed92b3b"
|
||||
},
|
||||
{
|
||||
"path": "scripts/performance-artifacts.mjs",
|
||||
"sha256": "4e9b9b3149b710eed20a1513f8bc1e13772ebcf14d84424b780addccdace8519"
|
||||
},
|
||||
{
|
||||
"path": "test/helpers/ambilight.js",
|
||||
"sha256": "56cd57e6cc52a4ef233492c45d9a51d16713e9fb40de123df1619395d0fb1723"
|
||||
},
|
||||
{
|
||||
"path": "test/helpers/canvas.js",
|
||||
"sha256": "fb1e86db48e098aa5cb0469241f814ddfaed2c35151f58028bcda19d8feed14a"
|
||||
},
|
||||
{
|
||||
"path": "test/browser/ambilight-proxy.spec.js",
|
||||
"sha256": "9ef43bb84ef4440bfa4257ec3998018a71e857d7bcb61c415923a8c0b2291e20"
|
||||
},
|
||||
{
|
||||
"path": "test/browser/ambilight-lifecycle.spec.js",
|
||||
"sha256": "321693a24d33f0c7aee02fd4137b55175386562a645ba138e15c3a51d6348d2c"
|
||||
},
|
||||
{
|
||||
"path": "test/browser/canvas-lifecycle.spec.js",
|
||||
"sha256": "5c50ad4f11d1df91c4247dc24c07edd2c9540e128e00fd07ee096406174512a7"
|
||||
},
|
||||
{
|
||||
"path": ".github/workflows/nodejs.yml",
|
||||
"sha256": "5e7d3cbaee3d96e9088b53205657614d8386a528a1a2fd9cc0687c90b4e7dfd3"
|
||||
},
|
||||
{
|
||||
"path": "refactor/scripts/ci-workflow.mjs",
|
||||
"sha256": "35148f6c9b75f3a4ee0fd20fd13211acddd45ca2f187f1a5c2581ce903796521"
|
||||
},
|
||||
{
|
||||
"path": "refactor/scripts/ci-workflow.test.mjs",
|
||||
"sha256": "923718fcc4897088ed92554cf4db5b95b82b8e1099db9c8b897806a87b08095a"
|
||||
},
|
||||
{
|
||||
"path": "test/package-check.test.js",
|
||||
"sha256": "e4e2010a00c6b6319ad36340a656e9d5f8821d76a30e87df6de7375a54cc2f35"
|
||||
},
|
||||
{
|
||||
"path": "test/performance-report.test.js",
|
||||
"sha256": "0304a90a07a86b2f71bde01ae28bf535ec7b9922cd463e16e066cea47a9af1d9"
|
||||
}
|
||||
],
|
||||
"browserRuns": [
|
||||
{
|
||||
"path": "refactor/.cache/ambilight05-installed-main-initial-browser/report.json",
|
||||
"sha256": "bde43cc3bc56c2ff7ff128a85364e5d2f83fdc1a6dc5ae4bf869f78537b5281f",
|
||||
"stats": {
|
||||
"startTime": "2026-09-14T09:37:03.054Z",
|
||||
"duration": 92475.65999999999,
|
||||
"expected": 45,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/ambilight05-installed-legacy-browser/report.json",
|
||||
"sha256": "7f8ffd2ddee4ecd026a674161f5055e367ffed2681f9a7a06b806a3a4ec9c797",
|
||||
"stats": {
|
||||
"startTime": "2026-09-14T09:40:52.738Z",
|
||||
"duration": 93430.18999999999,
|
||||
"expected": 45,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight ready starts native sampling and destruction releases only its frames and view",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight recovers from native cross-origin taint after a same-origin source switch",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate canvas paints native pixels, resizes and switches media",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate paused seek draws the new video frame without restarting RAF",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: candidate destroy inside callback leaves no media or native RAF and escaped play is inert",
|
||||
"status": "expected"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/ambilight05-installed-main-final-browser/report.json",
|
||||
"sha256": "606ac1f446d62c379bd37aff2ef2e23804ffc59572ec59ad6e8462a43e77bf13",
|
||||
"stats": {
|
||||
"startTime": "2026-09-14T09:43:17.253Z",
|
||||
"duration": 18682.41,
|
||||
"expected": 9,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "chromium",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "firefox",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published-5.1.7: historical core has no proxy option and retains native Ambilight playback",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "published: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
},
|
||||
{
|
||||
"browser": "webkit",
|
||||
"title": "candidate: Ambilight samples all nine canvas proxy regions after output resizing",
|
||||
"status": "expected"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"unit": {
|
||||
"ambilight": 49,
|
||||
"canvas": 57,
|
||||
"packageAndPerformance": 10,
|
||||
"failures": 0,
|
||||
"ciWorkflow": 32
|
||||
},
|
||||
"maps": [
|
||||
{
|
||||
"path": "refactor/.cache/packages/run-IIEOt4/browser-artifacts.json",
|
||||
"sha256": "8a62e2910193654b510c948aece932b3328966650b27b997e20c80776909b36b"
|
||||
},
|
||||
{
|
||||
"path": "refactor/.cache/packages/run-IIEOt4/browser-artifacts-legacy.json",
|
||||
"sha256": "9eca95c02a0abb16cb9397b71ab71ca4c7080865c791eb7730a3a48cebd1d42d"
|
||||
}
|
||||
],
|
||||
"limits": [
|
||||
"Only the Ambilight ARCHITECTURE.md changed between the two installations; runtime files are byte-identical.",
|
||||
"36 runtime and 13 type mode groups are core/chapter consumers, not whole-ecosystem consumer acceptance.",
|
||||
"Nine-color Canvas palette is painted through the public post-processing callback, not inferred from natural video frames.",
|
||||
"Physical mobile devices, full package distribution, Canvas Document PiP/subtitle combinations and remote CI remain open."
|
||||
],
|
||||
"checks": {
|
||||
"strictToolchain": "passed",
|
||||
"rootLint": {
|
||||
"errors": 0,
|
||||
"warnings": 1
|
||||
},
|
||||
"ciConfiguration": "passed",
|
||||
"engineeringLog": "refactor/.cache/ambilight05-final-engineering.log"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# PKG-AMBILIGHT-05 已安装产物组合检查点
|
||||
|
||||
## 解决的证据缺口
|
||||
|
||||
此前 Ambilight/Canvas 回归通过 helper 在内存中构建源码。即使指定核心的已安装
|
||||
artifact map,插件仍会构建源码,不能据此宣称整个组合验证了 npm 安装内容。
|
||||
本次扩展既有隔离打包流程,纳入有冻结发布契约的 Ambilight 和 Canvas:源快照
|
||||
重建、Yarn pack、仓库外离线安装、冻结重装、逐文件摘要验证后保存浏览器产物。
|
||||
|
||||
使用 `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`。
|
||||
未审核的额外包与重复包名被拒绝;额外包模式不允许 `--release`。报告明确 36 项
|
||||
runtime 与 5 组旧/8 组精确类型检查仍只覆盖 core/chapter;另外两包的打包安装
|
||||
成功不自动成为其全部运行时/类型/发布验收。原有逐包类型矩阵仍独立保留。
|
||||
|
||||
## 结构与兼容
|
||||
|
||||
把性能模块中通用的安装来源验证提取到 `scripts/installed-artifacts.mjs`,原
|
||||
`verifyPerformanceArtifacts` 名称作为兼容导出保留。通用验证可指定包集合,检查
|
||||
安装文件摘要、当前 src/public/package.json 与构建快照,以及锁和构建工具输入。
|
||||
Ambilight/Canvas helper 收到明确 map 后只加载这些已验证文件;缺包、文件变化、
|
||||
源码漂移或同时启用 frozen-workspace 都失败,不再回退源码。未设置 map 的原源码
|
||||
测试保留。没有新增依赖、改变 bundler、库生产代码、公开 API 或声明。
|
||||
|
||||
CI 在 Node/React/Vue 消费者之后、浏览器之前额外准备两包的安装产物并更新 map;
|
||||
避免默认 core/chapter map 触发缺包。性能兼容导出只返回其已校验的两包,不把
|
||||
额外插件自动加入旧性能基线。配置反例保护新增步骤不能缺失或被跳过;其他插件的
|
||||
installed/SDK 全矩阵仍归 CI-01,不宣称这次能使整个远端 CI 全绿。
|
||||
|
||||
组合测试额外观察两个独立 RAF owner。真实视频解码后通过公开 Canvas callback
|
||||
绘制九色区域,验证输出 resize 和九格取色,再检查销毁后两队列为空且没有新绘制。
|
||||
这仍是真实 DOM/Canvas/getImageData/RAF;颜色来自显式后处理 callback,不声称是
|
||||
自然视频画面的取色结果。5.1.7 核心没有 proxy 配置,明确只测原生 video 能力边界。
|
||||
|
||||
## 验证与余项
|
||||
|
||||
结果、两次打包差异、产物映射与报告摘要见
|
||||
[机器证据](../baselines/ambilight-installed-validation.json)。现代首轮 45 项,增强
|
||||
组合销毁断言后的现代 9 项与 legacy 45 项均使用实际安装产物;legacy 指向同一
|
||||
安装目录的真实 legacy 文件,其摘要仍须匹配 tarball。三个引擎的旧核心是冻结
|
||||
已发布版本,新核心/插件/代理来自该次安装;没有用浏览器模拟替代 SDK 或真机。
|
||||
|
||||
Ambilight 49、Canvas 57、打包/来源/性能回归 10 项通过。新增反例保护额外包不能
|
||||
误标 release-ready、显式 map 不得回退源码以及额外插件的源码漂移。包维护说明与
|
||||
测试流程同批更新。另32项CI配置回归通过,工程合计42项。首轮 lint 的新导入
|
||||
顺序/未用 import 等已修正;根lint保留1条既有生成声明unused-disable warning。
|
||||
|
||||
PKG-AMBILIGHT-05 继续 doing:物理移动设备、剩余完整组合/分发仍缺证据;本次
|
||||
没有关闭 PKG-CANVAS-05 的字幕/Document PiP/真机要求,也不关闭 CI-01 的全包
|
||||
消费者与远端矩阵。不推送、部署或发布。回退本检查点可恢复原默认 core/chapter
|
||||
安装流程与源码 helper;注意不能再把旧 helper 的运行当成完整 installed 组合。
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 256 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 51 / doing 18 / blocked 0 / done 187 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 50 / doing 19 / blocked 0 / done 187 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
| PKG-AMBILIGHT-03 | artplayer-plugin-ambilight<br>整理内部职责与资源 | PKG-AMBILIGHT-02, CORE-03, CORE-12 | 取色计算与绘帧分离,统一 RAF 和 DOM 清理 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | M | done |
|
||||
| PKG-AMBILIGHT-04 | artplayer-plugin-ambilight<br>迁移自有源码和公开类型 | PKG-AMBILIGHT-03, ENG-04, ENG-06, CORE-07 | canvas 上下文、参数及 start/stop 的明确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | done |
|
||||
| PKG-AMBILIGHT-PROXY-01 | artplayer-plugin-ambilight, artplayer-proxy-canvas<br>修复Canvas代理输出尺寸与取色区域 | PKG-AMBILIGHT-04, CORE-22 | 按Canvas实际输出buffer取色,原生video保留intrinsic尺寸,明确5.1.7没有proxy配置 | 旧错误复现、三引擎实际代理九色区域对照及原生视频回归通过;不把本子项当作最终代理/设备验收 | M | done |
|
||||
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight<br>验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22, PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-04, PKG-FACTORY-01 | 原生 video、canvas proxy 的能力边界及销毁无帧循环 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
|
||||
| PKG-AMBILIGHT-05 | artplayer-plugin-ambilight<br>验证新旧核心和组合 | PKG-AMBILIGHT-04, CORE-22, PKG-AMBILIGHT-PROXY-01, PKG-CANVAS-04, PKG-FACTORY-01 | 原生 video、canvas proxy 的能力边界及销毁无帧循环 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | doing |
|
||||
| PKG-AMBILIGHT-06 | artplayer-plugin-ambilight<br>验证分发并同步文档 | PKG-AMBILIGHT-05, ENG-07 | ambilight.js 示例及样式/分发验证 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-audio-track
|
||||
@@ -534,7 +534,7 @@
|
||||
- PKG-AMBILIGHT-03: [记录](changes/2026-09-12-PKG-AMBILIGHT-03-lifecycle.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-lifecycle-validation.json)
|
||||
- PKG-AMBILIGHT-04: [记录](changes/2026-09-12-PKG-AMBILIGHT-04-types.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-types-validation.json)
|
||||
- PKG-AMBILIGHT-PROXY-01: [记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-proxy-validation.json)
|
||||
- PKG-AMBILIGHT-05: [记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md) [记录](ambilight-validation.md)
|
||||
- PKG-AMBILIGHT-05: [记录](changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md) [记录](ambilight-validation.md) [记录](changes/2026-09-14-PKG-AMBILIGHT-05-installed-combination.md) [记录](baselines/ambilight-installed-validation.json)
|
||||
- PKG-AUDIO-01: [记录](changes/2026-09-12-PKG-AUDIO-01-contract.md) [记录](baselines/audio-track-contract.md) [记录](baselines/audio-track-release.json)
|
||||
- PKG-AUDIO-02: [记录](changes/2026-09-12-PKG-AUDIO-02-tests.md) [记录](audio-validation.md) [记录](baselines/audio-validation.json)
|
||||
- PKG-AUDIO-03: [记录](changes/2026-09-12-PKG-AUDIO-03-lifecycle.md) [记录](baselines/audio-lifecycle-validation.json)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-AMBILIGHT-05 已安装产物组合检查点(仍doing)
|
||||
|
||||
隔离构建/打包流程显式纳入 Ambilight/Canvas,仓库外离线安装与冻结重装后校验
|
||||
全部文件;加载器指定 map 时校验安装摘要和源码/构建输入,拒绝缺包或源码 fallback。
|
||||
现代首轮45项、增强销毁检查后现代9项、legacy45项三引擎回归全部通过;覆盖实际
|
||||
播放/取色、尺寸变化、跨域恢复、暂停寻址、切源与销毁。组合额外验证插件/代理两
|
||||
个 RAF 队列均归零且无后续绘制。49+57+10项专项/工程测试、工具链与lint通过。
|
||||
CI在Node/框架消费者后准备额外浏览器安装,新增顺序/不可跳过校验;另32项CI回归
|
||||
通过。性能检查保持core/chapter两包范围,不向其他插件自动扩张性能基线。
|
||||
两次安装仅 Ambilight 架构文档变化,运行时文件一致;36 runtime、5旧+8精确类型
|
||||
组只属于 core/chapter,报告已明确范围,额外包模式禁止误用严格 release 准入。
|
||||
见[变更](changes/2026-09-14-PKG-AMBILIGHT-05-installed-combination.md)与
|
||||
[证据](baselines/ambilight-installed-validation.json)。256项:187 done、19 doing、50 todo。
|
||||
Ambilight移动真机、Canvas字幕/Document PiP/真机、完整分发与远端CI仍未完成;
|
||||
本检查点不关闭其他任务,不推送/部署/发布。下一步继续剩余组合与分包消费者验收。
|
||||
|
||||
## SITE-VCONSOLE-01 站点控制台生命周期修复完成
|
||||
|
||||
冻结 vConsole 3.15.0 原始 bundle,以严格 TS 和六个唯一构建替换点修复日志 RAF、
|
||||
|
||||
@@ -90,6 +90,10 @@ export function validateCIWorkflow(source) {
|
||||
const vueIndex = browser.steps.findIndex(step => step.run === 'yarn test:vue-consumer 2>&1 | tee refactor/.cache/ci/vue-consumer.log')
|
||||
assert(vueIndex > consumerIndex && !Object.hasOwn(browser.steps[vueIndex], 'if'), 'Run Vue installed consumers after restoring canonical Node')
|
||||
assert(browser.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()' && step.with.path.split('\n').includes('refactor/.cache/vue-consumer-*/')), 'Retain Vue consumer failure evidence')
|
||||
const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas '))
|
||||
const engineIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser '))
|
||||
assert(extraIndex > Math.max(consumerIndex, reactIndex, vueIndex) && extraIndex < engineIndex, 'Prepare installed Ambilight/Canvas after consumer probes and before browser checks')
|
||||
assert(!Object.hasOwn(browser.steps[extraIndex], 'if') && browser.steps[extraIndex].run.includes('GITHUB_ENV') && browser.steps[extraIndex].run.includes('ARTPLAYER_BROWSER_ARTIFACTS='), 'Always select the additional installed browser artifact map')
|
||||
const pages = workflow.jobs.checks.steps.find(step => step.uses?.startsWith('actions/upload-pages-artifact@'))
|
||||
assert.equal(pages?.if, 'inputs.pages-artifact && github.ref == \'refs/heads/master\' && matrix.os == \'ubuntu-latest\'', 'Only one trusted matrix leg can prepare Pages')
|
||||
const prepareIndex = workflow.jobs.checks.steps.findIndex(step => step.id === 'pages')
|
||||
|
||||
@@ -33,6 +33,8 @@ for (const [name, mutate] of [
|
||||
['conditional Node setup', w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/setup-node@')).if = 'false'],
|
||||
['wrong consumer Node version', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'consumer-node-20').with['node-version'] = '24'],
|
||||
['skipped installed consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.includes('--expected-node 22.12.0')).if = 'false'],
|
||||
['missing browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).run = 'echo omitted'],
|
||||
['skipped browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).if = 'false'],
|
||||
['browser runtime not restored', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'restore-canonical-node').with = { 'node-version': '22.12.0', 'package-manager-cache': false }],
|
||||
['skipped React consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:react-consumer')).if = 'false'],
|
||||
['removed React consumer', w => w.jobs['browser-smoke'].steps = w.jobs['browser-smoke'].steps.filter(s => !s.run?.startsWith('yarn test:react-consumer'))],
|
||||
|
||||
+4
-2
@@ -1584,13 +1584,15 @@
|
||||
"PKG-CANVAS-04",
|
||||
"PKG-FACTORY-01"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "M",
|
||||
"deliverable": "原生 video、canvas proxy 的能力边界及销毁无帧循环",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-AMBILIGHT-PROXY-01-sampling.md",
|
||||
"ambilight-validation.md"
|
||||
"ambilight-validation.md",
|
||||
"changes/2026-09-14-PKG-AMBILIGHT-05-installed-combination.md",
|
||||
"baselines/ambilight-installed-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
|
||||
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||
const walk = directory => fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => entry.isDirectory() ? walk(path.join(directory, entry.name)) : [path.join(directory, entry.name)])
|
||||
function digest(file) {
|
||||
const bytes = fs.readFileSync(file)
|
||||
return hash(/\.(?:[cm]?[jt]s|json|css|less|html|svg|txt)$/.test(file) ? bytes.toString('utf8').replaceAll('\r\n', '\n') : bytes)
|
||||
}
|
||||
|
||||
export function verifyInstalledArtifacts(root, mapFile, names = ['artplayer', 'artplayer-plugin-chapter']) {
|
||||
assert(mapFile, 'Run yarn test:package and set ARTPLAYER_BROWSER_ARTIFACTS to its browser-artifacts.json')
|
||||
const directory = path.dirname(path.resolve(mapFile))
|
||||
const artifacts = read(mapFile)
|
||||
const report = read(path.join(directory, 'report.json'))
|
||||
assert.equal(report.task, 'ENG-07', 'Browser checks require isolated installation evidence')
|
||||
assert.equal(report.knownTypeBlockers, 0)
|
||||
assert.equal(report.node, process.versions.node, 'Build and browser-check Node versions differ')
|
||||
const toolchain = {}
|
||||
const currentManifest = read(path.join(root, 'package.json'))
|
||||
const buildManifest = read(path.join(directory, 'build/package.json'))
|
||||
for (const key of ['packageManager', 'devDependencies', 'resolutions'])
|
||||
assert.deepEqual(buildManifest[key], currentManifest[key], `Build toolchain changed: ${key}`)
|
||||
for (const file of ['yarn.lock', 'scripts/build.js', 'scripts/utils.js', 'scripts/projects.js']) {
|
||||
toolchain[file] = digest(path.join(root, file))
|
||||
assert.equal(digest(path.join(directory, 'build', file)), toolchain[file], `Stale build input: ${file}`)
|
||||
}
|
||||
const libraryInputs = location => Object.fromEntries(walk(path.join(location, 'scripts/library')).filter(file => /\.(?:[cm]?[jt]s|json)$/.test(file)).sort().map(file => [path.relative(location, file).replaceAll('\\', '/'), digest(file)]))
|
||||
const currentLibrary = libraryInputs(root)
|
||||
assert.deepEqual(libraryInputs(path.join(directory, 'build')), currentLibrary, 'Stale library build inputs; rerun yarn test:package')
|
||||
Object.assign(toolchain, currentLibrary)
|
||||
const inputs = []
|
||||
for (const name of names) {
|
||||
const pkg = report.packages.find(pkg => pkg.name === name)
|
||||
assert(pkg && artifacts[name], `Missing installed package: ${name}`)
|
||||
const file = path.resolve(directory, artifacts[name])
|
||||
const member = `package/dist/${path.basename(file)}`
|
||||
assert.equal(hash(fs.readFileSync(file)), pkg.files[member], `Installed browser artifact changed: ${name}`)
|
||||
const sourceHashes = {}
|
||||
for (const folder of ['src', 'public']) {
|
||||
const current = path.join(root, 'packages', name, folder)
|
||||
const snapshot = path.join(directory, 'build/packages', name, folder)
|
||||
if (!fs.existsSync(current)) {
|
||||
assert(!fs.existsSync(snapshot), `Source directory was removed: ${name}/${folder}`)
|
||||
continue
|
||||
}
|
||||
const inventory = location => Object.fromEntries(walk(location).map(file => [path.relative(location, file).replaceAll('\\', '/'), digest(file)]))
|
||||
const expected = inventory(current)
|
||||
assert.deepEqual(inventory(snapshot), expected, `Stale package source: ${name}/${folder}; rerun yarn test:package`)
|
||||
Object.assign(sourceHashes, Object.fromEntries(Object.entries(expected).map(([file, digest]) => [`${folder}/${file}`, digest])))
|
||||
}
|
||||
for (const file of ['package.json', 'THIRD_PARTY_NOTICES', 'tsconfig.json']) {
|
||||
const current = path.join(root, 'packages', name, file)
|
||||
const snapshot = path.join(directory, 'build/packages', name, file)
|
||||
assert.equal(fs.existsSync(current), fs.existsSync(snapshot), `Package input removed or added: ${name}/${file}`)
|
||||
if (fs.existsSync(current)) {
|
||||
sourceHashes[file] = digest(current)
|
||||
assert.equal(digest(snapshot), sourceHashes[file], `Stale package input: ${name}/${file}`)
|
||||
}
|
||||
}
|
||||
inputs.push({ name, file, member, sha256: pkg.files[member], archiveSha256: pkg.sha256, sourceHashes })
|
||||
}
|
||||
return { directory, inputs, toolchain, packages: report.packages.filter(pkg => names.includes(pkg.name)) }
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { ensureArchive, hash, readMember } from '../refactor/scripts/releases.mjs'
|
||||
import { consumerDirectory, names, readJson, removeConsumer, run, runtimeConsumer, typeConsumers, workspace, writeJson } from './package-consumer.mjs'
|
||||
import { consumerDirectory, names as defaultNames, readJson, removeConsumer, run, runtimeConsumer, typeConsumers, workspace, writeJson } from './package-consumer.mjs'
|
||||
|
||||
export function checkFiles(manifest, files, historical = []) {
|
||||
for (const field of ['main', 'module', 'types', 'legacy']) {
|
||||
@@ -67,10 +67,23 @@ export async function publishedConsumer() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkPackages({ release = false } = {}) {
|
||||
export async function checkPackages({ release = false, include = [] } = {}) {
|
||||
assert(!release || include.length === 0, 'Additional package browser preparation is not full release acceptance')
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Run yarn test:package with the pinned Yarn')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn), 'Missing Yarn executable')
|
||||
const supported = {
|
||||
'artplayer-plugin-ambilight': async () => (await import('../refactor/scripts/ambilight-contract.mjs')).verifyAmbilightContract(),
|
||||
'artplayer-proxy-canvas': async () => (await import('../refactor/scripts/canvas-contract.mjs')).verifyCanvasContract(),
|
||||
}
|
||||
assert(new Set(include).size === include.length, 'Duplicate additional package')
|
||||
const historicalFiles = new Map()
|
||||
for (const name of include) {
|
||||
assert(Object.hasOwn(supported, name), `Additional package needs a reviewed contract: ${name}`)
|
||||
const contract = await supported[name]()
|
||||
historicalFiles.set(name, Object.keys(contract.baseline.release.files))
|
||||
}
|
||||
const names = [...defaultNames, ...include]
|
||||
const parent = path.join(workspace, 'refactor/.cache/packages')
|
||||
fs.mkdirSync(parent, { recursive: true })
|
||||
const output = fs.mkdtempSync(path.join(parent, 'run-'))
|
||||
@@ -98,7 +111,7 @@ export async function checkPackages({ release = false } = {}) {
|
||||
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
assert.equal(manifest.name, name)
|
||||
assert(!['preinstall', 'install', 'postinstall'].some(hook => manifest.scripts?.[hook]), 'Add lifecycle fixtures before accepting install hooks')
|
||||
checkFiles(manifest, files, Object.keys(baseline.releases.find(r => r.name === name).files))
|
||||
checkFiles(manifest, files, historicalFiles.get(name) || Object.keys(baseline.releases.find(r => r.name === name).files))
|
||||
packages.push({ name, version: manifest.version, archive: path.basename(archive), sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(file => [file, hash(readMember(archive, file))])) })
|
||||
}
|
||||
writeJson(path.join(installed, 'package.json'), { private: true, name: 'artplayer-isolated-consumer', dependencies: Object.fromEntries(packages.map(pkg => [pkg.name, `file:${path.join(output, pkg.archive).replaceAll('\\', '/')}`])) })
|
||||
@@ -123,11 +136,13 @@ export async function checkPackages({ release = false } = {}) {
|
||||
const preciseTypes = typeConsumers(installed, { precise: true })
|
||||
const source = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8' }).trim()
|
||||
const publishedPackages = baseline.releases.map(({ name, version, files }) => ({ name, version, files }))
|
||||
const report = { task: 'ENG-07', capturedAt: new Date().toISOString(), source, toolchain: { yarn: '1.22.22', yarnPath: yarn }, node: process.versions.node, packages, runtime, publishedPackages, publishedRuntime: oldRuntime, types, preciseTypes, knownRuntimeBlockers: runtime.observations.defaultsWithoutNavigator.resolved ? 0 : 1, knownTypeBlockers: [...types, ...preciseTypes].reduce((sum, result) => sum + result.diagnostics.length, 0) }
|
||||
const report = { task: 'ENG-07', capturedAt: new Date().toISOString(), source, toolchain: { yarn: '1.22.22', yarnPath: yarn }, node: process.versions.node, packages, runtimeScope: defaultNames, typeScope: defaultNames, additionalBrowserPackages: include, runtime, publishedPackages, publishedRuntime: oldRuntime, types, preciseTypes, knownRuntimeBlockers: runtime.observations.defaultsWithoutNavigator.resolved ? 0 : 1, knownTypeBlockers: [...types, ...preciseTypes].reduce((sum, result) => sum + result.diagnostics.length, 0) }
|
||||
writeJson(path.join(output, 'report.json'), report)
|
||||
writeJson(path.join(output, 'browser-artifacts.json'), artifacts)
|
||||
writeJson(path.join(parent, 'latest.json'), { output: path.relative(workspace, output).replaceAll('\\', '/') })
|
||||
console.log(`Installed tarball contracts passed: ${runtime.checks.length} runtime checks; ${types.filter(t => !t.diagnostics.length).length}/${types.length} legacy type modes; ${preciseTypes.filter(t => !t.diagnostics.length).length}/${preciseTypes.length} precise type modes. Report: ${output}`)
|
||||
if (include.length)
|
||||
console.log(`Runtime/type fixture scope: ${defaultNames.join(', ')}. Additional packages require their own consumer and browser acceptance: ${include.join(', ')}.`)
|
||||
if (release) {
|
||||
assert.equal(report.knownTypeBlockers, 0, 'Known type blockers remain; this candidate is not release-ready')
|
||||
assert.equal(report.knownRuntimeBlockers, 0, 'Known runtime blockers remain; this candidate is not release-ready')
|
||||
@@ -141,7 +156,8 @@ export async function checkPackages({ release = false } = {}) {
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
checkPackages({ release: process.argv.includes('--release') }).catch((error) => {
|
||||
const include = process.argv.find(arg => arg.startsWith('--include='))?.slice('--include='.length).split(',') || []
|
||||
checkPackages({ release: process.argv.includes('--release'), include }).catch((error) => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
|
||||
@@ -1,71 +1,11 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { ensureArchive, hash, readMember } from '../refactor/scripts/releases.mjs'
|
||||
import { ensureArchive, readMember } from '../refactor/scripts/releases.mjs'
|
||||
import { measureBytes } from '../refactor/scripts/sizes.mjs'
|
||||
|
||||
export { verifyInstalledArtifacts as verifyPerformanceArtifacts } from './installed-artifacts.mjs'
|
||||
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||
const walk = directory => fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => entry.isDirectory() ? walk(path.join(directory, entry.name)) : [path.join(directory, entry.name)])
|
||||
function digest(file) {
|
||||
const bytes = fs.readFileSync(file)
|
||||
return hash(/\.(?:[cm]?[jt]s|json|css|less|html|svg|txt)$/.test(file) ? bytes.toString('utf8').replaceAll('\r\n', '\n') : bytes)
|
||||
}
|
||||
|
||||
export function verifyPerformanceArtifacts(root, mapFile) {
|
||||
assert(mapFile, 'Run yarn test:package and set ARTPLAYER_BROWSER_ARTIFACTS to its browser-artifacts.json')
|
||||
const directory = path.dirname(path.resolve(mapFile))
|
||||
const artifacts = read(mapFile)
|
||||
const report = read(path.join(directory, 'report.json'))
|
||||
assert.equal(report.task, 'ENG-07', 'Performance requires isolated installation evidence')
|
||||
assert.equal(report.knownTypeBlockers, 0)
|
||||
assert.equal(report.node, process.versions.node, 'Build and benchmark Node versions differ')
|
||||
const toolchain = {}
|
||||
const currentManifest = read(path.join(root, 'package.json'))
|
||||
const buildManifest = read(path.join(directory, 'build/package.json'))
|
||||
for (const key of ['packageManager', 'devDependencies', 'resolutions'])
|
||||
assert.deepEqual(buildManifest[key], currentManifest[key], `Build toolchain changed: ${key}`)
|
||||
for (const file of ['yarn.lock', 'scripts/build.js', 'scripts/utils.js', 'scripts/projects.js']) {
|
||||
toolchain[file] = digest(path.join(root, file))
|
||||
assert.equal(digest(path.join(directory, 'build', file)), toolchain[file], `Stale build input: ${file}`)
|
||||
}
|
||||
const libraryInputs = location => Object.fromEntries(walk(path.join(location, 'scripts/library')).filter(file => /\.(?:[cm]?[jt]s|json)$/.test(file)).sort().map(file => [path.relative(location, file).replaceAll('\\', '/'), digest(file)]))
|
||||
const currentLibrary = libraryInputs(root)
|
||||
assert.deepEqual(libraryInputs(path.join(directory, 'build')), currentLibrary, 'Stale library build inputs; rerun yarn test:package')
|
||||
Object.assign(toolchain, currentLibrary)
|
||||
const inputs = []
|
||||
for (const name of ['artplayer', 'artplayer-plugin-chapter']) {
|
||||
const pkg = report.packages.find(pkg => pkg.name === name)
|
||||
assert(pkg && artifacts[name], `Missing installed package: ${name}`)
|
||||
const file = path.resolve(directory, artifacts[name])
|
||||
const member = `package/dist/${path.basename(file)}`
|
||||
assert.equal(hash(fs.readFileSync(file)), pkg.files[member], `Installed performance artifact changed: ${name}`)
|
||||
const sourceHashes = {}
|
||||
for (const folder of ['src', 'public']) {
|
||||
const current = path.join(root, 'packages', name, folder)
|
||||
const snapshot = path.join(directory, 'build/packages', name, folder)
|
||||
if (!fs.existsSync(current)) {
|
||||
assert(!fs.existsSync(snapshot), `Source directory was removed: ${name}/${folder}`)
|
||||
continue
|
||||
}
|
||||
const inventory = location => Object.fromEntries(walk(location).map(file => [path.relative(location, file).replaceAll('\\', '/'), digest(file)]))
|
||||
const expected = inventory(current)
|
||||
assert.deepEqual(inventory(snapshot), expected, `Stale package source: ${name}/${folder}; rerun yarn test:package`)
|
||||
Object.assign(sourceHashes, Object.fromEntries(Object.entries(expected).map(([file, digest]) => [`${folder}/${file}`, digest])))
|
||||
}
|
||||
for (const file of ['package.json', 'THIRD_PARTY_NOTICES', 'tsconfig.json']) {
|
||||
const current = path.join(root, 'packages', name, file)
|
||||
const snapshot = path.join(directory, 'build/packages', name, file)
|
||||
assert.equal(fs.existsSync(current), fs.existsSync(snapshot), `Package input removed or added: ${name}/${file}`)
|
||||
if (fs.existsSync(current)) {
|
||||
sourceHashes[file] = digest(current)
|
||||
assert.equal(digest(snapshot), sourceHashes[file], `Stale package input: ${name}/${file}`)
|
||||
}
|
||||
}
|
||||
inputs.push({ name, file, member, sha256: pkg.files[member], archiveSha256: pkg.sha256, sourceHashes })
|
||||
}
|
||||
return { directory, inputs, toolchain, packages: report.packages }
|
||||
}
|
||||
|
||||
export async function compareArtifactSizes(root, artifacts) {
|
||||
const releases = read(path.join(root, 'refactor/baselines/releases.json')).releases
|
||||
|
||||
@@ -11,8 +11,20 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
|
||||
const scenario = core === 'published-5.1.7' ? 'historical core has no proxy option and retains native Ambilight playback' : 'Ambilight samples all nine canvas proxy regions after output resizing'
|
||||
test(`${core}: ${scenario}`, async ({ page }, testInfo) => {
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
for (const [name, source] of [['ambilightFactory', implementation.source], ['canvasProxy', proxy]])
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports; ${source}; window.${name} = module.exports.default || module.exports; })();` })
|
||||
for (const [name, source] of [['ambilightFactory', implementation.source], ['canvasProxy', proxy]]) {
|
||||
await page.addScriptTag({ content: `(() => {
|
||||
const pending = new Set();
|
||||
const requestAnimationFrame = callback => {
|
||||
const id = window.requestAnimationFrame(time => { pending.delete(id); callback(time); });
|
||||
pending.add(id); return id;
|
||||
};
|
||||
const cancelAnimationFrame = id => { pending.delete(id); window.cancelAnimationFrame(id); };
|
||||
const module = { exports: {} }; const exports = module.exports;
|
||||
${source};
|
||||
window.${name} = module.exports.default || module.exports;
|
||||
(window.combinationFrames ||= {})['${name}'] = pending;
|
||||
})();` })
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
window.palette = ['rgb(255, 0, 0)', 'rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'rgb(255, 255, 0)', 'rgb(0, 255, 255)', 'rgb(255, 0, 255)', 'rgb(120, 0, 0)', 'rgb(0, 120, 0)', 'rgb(0, 0, 120)']
|
||||
window.drawCount = 0
|
||||
@@ -61,8 +73,14 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) {
|
||||
expect(dimensions.canvas).not.toEqual(dimensions.video)
|
||||
await testInfo.attach('ambilight-proxy-inputs', { contentType: 'application/json', body: JSON.stringify({ core, dimensions, plugin: hash(implementation.source), proxy: hash(proxySource), media: '/test/pattern.mp4', scope: 'real video decode and proxy post-processing callback paints a nine-cell palette using native Canvas' }) })
|
||||
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('.artplayer-plugin-ambilight > div')].map(node => node.style.backgroundColor))).toEqual(await page.evaluate(() => window.palette))
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
const drawsAtDestroy = await page.evaluate(async () => {
|
||||
window.art.destroy()
|
||||
const draws = window.drawCount
|
||||
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
||||
return draws
|
||||
})
|
||||
expect(await page.locator('.artplayer-plugin-ambilight').count()).toBe(0)
|
||||
expect(await page.evaluate(() => ({ draws: window.drawCount, pluginFrames: window.combinationFrames.ambilightFactory.size, proxyFrames: window.combinationFrames.canvasProxy.size }))).toEqual({ draws: drawsAtDestroy, pluginFrames: 0, proxyFrames: 0 })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -6,12 +7,19 @@ import vm from 'node:vm'
|
||||
import { build, transform } from 'esbuild'
|
||||
import { verifyAmbilightContract } from '../../refactor/scripts/ambilight-contract.mjs'
|
||||
import { readMember } from '../../refactor/scripts/releases.mjs'
|
||||
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
|
||||
import { getEntryFile } from '../../scripts/projects.js'
|
||||
|
||||
export async function ambilightCandidate() {
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) {
|
||||
assert.notEqual(process.env.ARTPLAYER_AMBILIGHT_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
|
||||
const { inputs } = verifyInstalledArtifacts(root, process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight'])
|
||||
const input = inputs.find(item => item.name === 'artplayer-plugin-ambilight')
|
||||
return { name: 'candidate-installed', source: fs.readFileSync(input.file, 'utf8'), format: 'artifact', provenance: input }
|
||||
}
|
||||
if (process.env.ARTPLAYER_AMBILIGHT_BASELINE === '1')
|
||||
return (await ambilightHistorical()).find(item => item.name === 'frozen-workspace')
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
const result = await build({ entryPoints: [getEntryFile(path.join(root, 'packages/artplayer-plugin-ambilight'))], bundle: true, write: false, platform: 'browser', format: 'cjs', target: 'es2020' })
|
||||
return { name: 'candidate-source', source: result.outputFiles[0].text, format: 'artifact' }
|
||||
}
|
||||
|
||||
+10
-1
@@ -1,3 +1,5 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -5,12 +7,19 @@ import vm from 'node:vm'
|
||||
import { build, transform } from 'esbuild'
|
||||
import { verifyCanvasContract } from '../../refactor/scripts/canvas-contract.mjs'
|
||||
import { readMember } from '../../refactor/scripts/releases.mjs'
|
||||
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
|
||||
import { getEntryFile } from '../../scripts/projects.js'
|
||||
|
||||
export async function canvasCandidate() {
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) {
|
||||
assert.notEqual(process.env.ARTPLAYER_CANVAS_BASELINE, '1', 'Installed candidate cannot use the frozen workspace')
|
||||
const { inputs } = verifyInstalledArtifacts(root, process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-proxy-canvas'])
|
||||
const input = inputs.find(item => item.name === 'artplayer-proxy-canvas')
|
||||
return { name: 'candidate-installed', source: fs.readFileSync(input.file, 'utf8'), format: 'artifact', provenance: input }
|
||||
}
|
||||
if (process.env.ARTPLAYER_CANVAS_BASELINE === '1')
|
||||
return (await canvasHistorical()).find(item => item.name === 'frozen-workspace')
|
||||
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||
const result = await build({ entryPoints: [getEntryFile(path.join(root, 'packages/artplayer-proxy-canvas'))], bundle: true, write: false, platform: 'browser', format: 'cjs', target: 'es2020' })
|
||||
return { name: 'candidate-source', source: result.outputFiles[0].text, format: 'artifact' }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
// eslint-disable-next-line test/no-import-node-test -- This fixture exercises the repository's Node test runner.
|
||||
import { test } from 'node:test'
|
||||
import { checkFiles, publishedConsumer } from '../scripts/package-check.mjs'
|
||||
import { checkFiles, checkPackages, publishedConsumer } from '../scripts/package-check.mjs'
|
||||
import { removeConsumer, runtimeConsumer } from '../scripts/package-consumer.mjs'
|
||||
import { emitterContracts } from './contracts/emitter.js'
|
||||
import { ambilightCandidate } from './helpers/ambilight.js'
|
||||
import { canvasCandidate } from './helpers/canvas.js'
|
||||
|
||||
test('Additional browser packages cannot be mistaken for full release consumer acceptance', async () => {
|
||||
await assert.rejects(checkPackages({ release: true, include: ['artplayer-plugin-ambilight'] }), /not full release acceptance/)
|
||||
})
|
||||
|
||||
test('Explicit installed plugin maps never fall back to source or frozen workspace', async () => {
|
||||
const keys = ['ARTPLAYER_BROWSER_ARTIFACTS', 'ARTPLAYER_AMBILIGHT_BASELINE', 'ARTPLAYER_CANVAS_BASELINE']
|
||||
const previous = Object.fromEntries(keys.map(key => [key, process.env[key]]))
|
||||
try {
|
||||
process.env.ARTPLAYER_BROWSER_ARTIFACTS = path.resolve('refactor/.cache/absent-installed-plugin-map.json')
|
||||
assert(!fs.existsSync(process.env.ARTPLAYER_BROWSER_ARTIFACTS))
|
||||
delete process.env.ARTPLAYER_AMBILIGHT_BASELINE
|
||||
delete process.env.ARTPLAYER_CANVAS_BASELINE
|
||||
await assert.rejects(ambilightCandidate(), { code: 'ENOENT' })
|
||||
await assert.rejects(canvasCandidate(), { code: 'ENOENT' })
|
||||
process.env.ARTPLAYER_AMBILIGHT_BASELINE = '1'
|
||||
process.env.ARTPLAYER_CANVAS_BASELINE = '1'
|
||||
await assert.rejects(ambilightCandidate(), /cannot use the frozen workspace/)
|
||||
await assert.rejects(canvasCandidate(), /cannot use the frozen workspace/)
|
||||
}
|
||||
finally {
|
||||
for (const key of keys) {
|
||||
if (previous[key] === undefined)
|
||||
delete process.env[key]
|
||||
else process.env[key] = previous[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('Package checks reject missing files, missing wildcard exports and internal configuration', () => {
|
||||
const manifest = { name: 'fixture', main: './dist/main.js', module: './dist/main.mjs', types: './types/main.d.ts', legacy: './dist/legacy.js', exports: { '.': './dist/main.js', './lang/*': './dist/lang/*.js' } }
|
||||
|
||||
+15
-2
@@ -1,8 +1,14 @@
|
||||
# Installed package checks
|
||||
|
||||
Run `yarn test:package` with the pinned Node and Yarn after a frozen install. The
|
||||
initial scope is core and chapter; extend `names` in `scripts/package-consumer.mjs`
|
||||
and add package-specific consumers when migrating another package.
|
||||
default consumer scope is core and chapter. For Ambilight/Canvas browser work,
|
||||
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`.
|
||||
These two additional packages have reviewed published contracts and use the same
|
||||
source snapshot/build/pack/offline-install/frozen-reinstall pipeline. Installed
|
||||
files are copied only after archive hash checks. The report explicitly separates
|
||||
the core/chapter runtime/type scope from the additional browser package scope;
|
||||
`--release` rejects additional packages until complete consumer coverage exists.
|
||||
Other packages need a reviewed contract and their own consumers before inclusion.
|
||||
|
||||
`scripts/package-check.mjs` copies source into an ignored build snapshot, rebuilds
|
||||
all three formats and core languages using repository scripts, and runs Yarn pack.
|
||||
@@ -72,6 +78,13 @@ and run `yarn test:browser`. The service then uses installed package bytes witho
|
||||
falling back to source. CI performs this sequence and uploads only reports and
|
||||
artifacts, excluding the build snapshot's node_modules link.
|
||||
|
||||
`scripts/installed-artifacts.mjs` verifies selected packages against the installed
|
||||
report and the current source/build inputs; performance retains its existing
|
||||
two-package wrapper. Ambilight and Canvas browser helpers use this verifier when
|
||||
an artifact map is supplied, including optional legacy maps pointing to checked
|
||||
legacy members in the same installation. Missing packages, stale source, modified
|
||||
bundles or contradictory frozen-workspace flags fail instead of rebuilding source.
|
||||
|
||||
`test/package-check.test.js` verifies missing targets, leaked configuration and
|
||||
removed historical files, then deliberately removes an actual published default
|
||||
export and required entry in an isolated consumer to prove runtime failures are
|
||||
|
||||
@@ -6,6 +6,7 @@ import process from 'node:process'
|
||||
import { test } from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
import { verifyInstalledArtifacts } from '../scripts/installed-artifacts.mjs'
|
||||
import { verifyPerformanceArtifacts } from '../scripts/performance-artifacts.mjs'
|
||||
import { performanceHtml, performanceScript, waitForObservation } from '../scripts/performance-fixture.mjs'
|
||||
import { validatePairedPerformance } from '../scripts/performance-report.mjs'
|
||||
@@ -128,7 +129,7 @@ test('performance refuses stale sources, build tools and altered installed bundl
|
||||
write(path.join(root, file), content)
|
||||
write(path.join(snapshot, file), content)
|
||||
}
|
||||
const names = ['artplayer', 'artplayer-plugin-chapter']
|
||||
const names = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight']
|
||||
const packages = names.map((name) => {
|
||||
const entry = `packages/${name}/src/index.ts`
|
||||
write(path.join(root, entry), 'export default 1\n')
|
||||
@@ -144,6 +145,13 @@ test('performance refuses stale sources, build tools and altered installed bundl
|
||||
write(map, JSON.stringify(Object.fromEntries(names.map(name => [name, `artifacts/${name}/dist/${name}.js`]))))
|
||||
write(path.join(output, 'report.json'), JSON.stringify({ task: 'ENG-07', knownTypeBlockers: 0, node: process.versions.node, packages }))
|
||||
assert.equal(verifyPerformanceArtifacts(root, map).inputs.length, 2)
|
||||
assert.equal(verifyPerformanceArtifacts(root, map).packages.length, 2)
|
||||
assert.equal(verifyInstalledArtifacts(root, map, names).inputs.length, 3)
|
||||
assert.throws(() => verifyInstalledArtifacts(root, map, [...names, 'artplayer-proxy-canvas']), /Missing installed package/)
|
||||
const pluginSource = path.join(root, 'packages/artplayer-plugin-ambilight/src/index.ts')
|
||||
write(pluginSource, 'export default 2\n')
|
||||
assert.throws(() => verifyInstalledArtifacts(root, map, names), /Stale package source/)
|
||||
write(pluginSource, 'export default 1\n')
|
||||
const source = path.join(root, 'packages/artplayer/src/index.ts')
|
||||
write(source, 'export default 2\n')
|
||||
assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale package source/)
|
||||
@@ -158,5 +166,5 @@ test('performance refuses stale sources, build tools and altered installed bundl
|
||||
assert.throws(() => verifyPerformanceArtifacts(root, map), /Stale library build inputs/)
|
||||
fs.unlinkSync(path.join(snapshot, 'scripts/library/removed.ts'))
|
||||
write(path.join(output, 'artifacts/artplayer/dist/artplayer.js'), 'changed bundle\n')
|
||||
assert.throws(() => verifyPerformanceArtifacts(root, map), /Installed performance artifact changed/)
|
||||
assert.throws(() => verifyPerformanceArtifacts(root, map), /Installed browser artifact changed/)
|
||||
})
|
||||
Reference in new issue
Block a user