From 01f3d00e4c20ff39ce0e5e7db885cee15fd820ee Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Tue, 15 Sep 2026 20:15:58 +0800 Subject: [PATCH] test(ci): [CI-TYPES-02] gate installed types across all libraries --- .github/workflows/nodejs.yml | 5 + package.json | 5 +- .../baselines/ecosystem-types-validation.json | 631 ++++++++++++++++++ .../2026-09-15-CI-TYPES-02-ecosystem.md | 52 ++ refactor/ci-setup.md | 7 + refactor/impact-analysis.md | 7 +- refactor/impact-policy.json | 5 + refactor/plan.md | 8 +- refactor/progress.md | 15 + refactor/scripts/ci-workflow.mjs | 3 + refactor/scripts/ci-workflow.test.mjs | 6 + refactor/scripts/impact-model.mjs | 1 + refactor/tasks.json | 23 +- scripts/consumers/README.md | 38 ++ scripts/consumers/ecosystem.ts | 95 +++ test/ecosystem-types.test.js | 74 ++ test/package/README.md | 7 + 17 files changed, 974 insertions(+), 8 deletions(-) create mode 100644 refactor/baselines/ecosystem-types-validation.json create mode 100644 refactor/changes/2026-09-15-CI-TYPES-02-ecosystem.md create mode 100644 scripts/consumers/ecosystem.ts create mode 100644 test/ecosystem-types.test.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 46397a4cc..8b9c3bea1 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -139,6 +139,8 @@ jobs: run: yarn test:iframe-history 2>&1 | tee refactor/.cache/ci/iframe-history.log - name: Compare installed performance and verify resource cleanup run: yarn test:performance 2>&1 | tee refactor/.cache/ci/performance.log + - name: Verify all installed library type contracts + run: yarn test:ecosystem-types 2>&1 | tee refactor/.cache/ci/ecosystem-types.log - name: Upload browser evidence if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -150,6 +152,9 @@ jobs: refactor/.cache/react-consumer-*/ refactor/.cache/vue-consumer-*/ refactor/.cache/performance/ + refactor/.cache/ecosystem-types/ + refactor/.cache/*-package-types-*/*.json + refactor/.cache/*-package-types-*/*.log refactor/.cache/packages/latest.json refactor/.cache/packages/run-*/*.json refactor/.cache/packages/run-*/*.log diff --git a/package.json b/package.json index d5ce7905c..6cc141fa9 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "check:toolchain": "node scripts/check-toolchain.mjs", "lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor,browser-validation,release,consumers}/**/*.ts\" \"scripts/plugin/*.{js,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/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js test/pages-artifact.test.js test/site-notices.test.js test/monaco-provenance.test.js test/monaco-unicode.test.js test/site-console.test.js test/vconsole-lifecycle.test.js test/browser-validation.test.js test/rollback-files.test.js test/pages-recovery.test.js test/installed-types.test.js", + "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/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js test/pages-artifact.test.js test/site-notices.test.js test/monaco-provenance.test.js test/monaco-unicode.test.js test/site-console.test.js test/vconsole-lifecycle.test.js test/browser-validation.test.js test/rollback-files.test.js test/pages-recovery.test.js test/installed-types.test.js test/ecosystem-types.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 check:release-ledger && yarn typecheck:release && yarn check:rollback-inventory && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn check:vconsole && yarn check:console && yarn check:site-notices && yarn typecheck:site-assets && yarn check:site-assets && 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:llm && yarn build:vconsole && yarn build:console && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports", @@ -178,7 +178,8 @@ "verify:monaco-unicode": "node scripts/site-vendor/monaco/reproduce-unicode.ts", "release:bundle": "yarn check:toolchain --strict && node scripts/prepare-release.mjs", "typecheck:release": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.release.json --noEmit", - "test:release-bundle": "node --test refactor/scripts/release-bundle.test.mjs" + "test:release-bundle": "node --test refactor/scripts/release-bundle.test.mjs", + "test:ecosystem-types": "node scripts/consumers/ecosystem.ts" }, "browserslist": "last 1 Chrome version", "devDependencies": { diff --git a/refactor/baselines/ecosystem-types-validation.json b/refactor/baselines/ecosystem-types-validation.json new file mode 100644 index 000000000..77c1fbe2a --- /dev/null +++ b/refactor/baselines/ecosystem-types-validation.json @@ -0,0 +1,631 @@ +{ + "task": "CI-TYPES-02", + "baselineCommit": "00c131bff130fad4ff03f90d7f02c124bc177cb5", + "environment": { + "node": "v24.21.0", + "yarn": "1.22.22", + "platform": "win32" + }, + "command": "yarn test:ecosystem-types", + "exitCode": 0, + "seconds": 579.13, + "aggregateReport": "refactor/.cache/ecosystem-types/run-NPPwIw/report.json", + "aggregateSha256": "8ca0708e1ab8aba81a5e37275ea995cebcb3eb13a39b14b69d544c2c9b8e6252", + "packages": [ + "artplayer", + "artplayer-plugin-ads", + "artplayer-plugin-ambilight", + "artplayer-plugin-asr", + "artplayer-plugin-audio-track", + "artplayer-plugin-auto-thumbnail", + "artplayer-plugin-chapter", + "artplayer-plugin-chromecast", + "artplayer-plugin-danmuku", + "artplayer-plugin-danmuku-mask", + "artplayer-plugin-dash-control", + "artplayer-plugin-document-pip", + "artplayer-plugin-hls-control", + "artplayer-plugin-jassub", + "artplayer-plugin-multiple-subtitles", + "artplayer-plugin-vast", + "artplayer-plugin-vtt-thumbnail", + "artplayer-proxy-canvas", + "artplayer-proxy-mediabunny", + "artplayer-tool-iframe", + "artplayer-tool-thumbnail" + ], + "results": [ + { + "command": [ + "build:types" + ], + "packages": [], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 4758, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/prepare-0.log", + "sha256": "79bf1fe2f501cc8b002a2a756d20d45a868d0484abe5fd4240b55e82339822c6" + } + }, + { + "command": [ + "build", + "all" + ], + "packages": [], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 41694, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/prepare-1.log", + "sha256": "a0dc92d79f844a45ca64cbf27a0a0c36b0cfee8db5d26fe3b73408c8f819bdb5" + } + }, + { + "command": [ + "build:i18n" + ], + "packages": [], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 993, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/prepare-2.log", + "sha256": "eeb53f950f80b68326d7612c4cb2476070e15bdd74dd731154642477c13256c6" + } + }, + { + "command": [ + "test:package", + "--include=artplayer-plugin-audio-track,artplayer-plugin-hls-control" + ], + "packages": [ + "artplayer", + "artplayer-plugin-chapter", + "artplayer-plugin-audio-track", + "artplayer-plugin-hls-control" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 35692, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-package.log", + "sha256": "30282ff060bdcf0d174ee6d12bd0da3f0cd2a062c01c1b21cdebd615531c0726" + } + }, + { + "command": [ + "test:ads-types-package" + ], + "packages": [ + "artplayer-plugin-ads" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 9381, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-ads-types-package.log", + "sha256": "355ebf54ffe2a4b915260bfd1071c233d2cfd7f7bf6f612b3b7e575485f7ff0f" + } + }, + { + "command": [ + "test:ambilight-types-package" + ], + "packages": [ + "artplayer-plugin-ambilight" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 28370, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-ambilight-types-package.log", + "sha256": "c2ce904605747fd42c02cd4aab02e1050a94b8f482a2dcc31a9387a30232783d" + } + }, + { + "command": [ + "test:asr-types-package" + ], + "packages": [ + "artplayer-plugin-asr" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 25925, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-asr-types-package.log", + "sha256": "87cdcdcab0511196d448c13214842da5511a66b3605719ab6093a7dc46ef0235" + } + }, + { + "command": [ + "test:auto-thumbnail-types-package" + ], + "packages": [ + "artplayer-plugin-auto-thumbnail" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 26710, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-auto-thumbnail-types-package.log", + "sha256": "a602097dbe7489915fc8881284e966543a207fec407eaba7d7a1ad2fa7563b42" + } + }, + { + "command": [ + "test:canvas-types-package" + ], + "packages": [ + "artplayer-proxy-canvas" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 25716, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-canvas-types-package.log", + "sha256": "38d3a11a95c6f06dbbd34ff16b46fd267367aa9d47944101c45c3eca730d6bc8" + } + }, + { + "command": [ + "test:chromecast-types-package" + ], + "packages": [ + "artplayer-plugin-chromecast" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 26983, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-chromecast-types-package.log", + "sha256": "07090017f97a95c522239a0e72826fc542370a84e738c6a18309b932faef75ed" + } + }, + { + "command": [ + "test:danmuku-types-package" + ], + "packages": [ + "artplayer-plugin-danmuku" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 42308, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-danmuku-types-package.log", + "sha256": "b6ea2178fb074987aa992a1874a70d5cd3e304b7315fd42c40b3842b92d76b5e" + } + }, + { + "command": [ + "test:danmuku-mask-types-package" + ], + "packages": [ + "artplayer-plugin-danmuku-mask" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 36319, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-danmuku-mask-types-package.log", + "sha256": "97d8163be9147cc4d7e60e542a885fd7946d1888c5a6d0e5e6eb0a787921bdb6" + } + }, + { + "command": [ + "test:dash-types-package" + ], + "packages": [ + "artplayer-plugin-dash-control" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 9525, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-dash-types-package.log", + "sha256": "7e4fdaad5d56e1a4620082ae9f09cea34f9d6906cfe65168afe0064167657c54" + } + }, + { + "command": [ + "test:dpip-types-package" + ], + "packages": [ + "artplayer-plugin-document-pip" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 23822, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-dpip-types-package.log", + "sha256": "755c6dc4ce2a7ebe9b3d91a3f280694cb41e5361238949dd4466a9f34d939c76" + } + }, + { + "command": [ + "test:iframe-types-package" + ], + "packages": [ + "artplayer-tool-iframe" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 15444, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-iframe-types-package.log", + "sha256": "2de2e66ffdf92fc866d7fdcb072ce2e17a7f58a203d594ab5465233545522211" + } + }, + { + "command": [ + "test:jassub-types-package" + ], + "packages": [ + "artplayer-plugin-jassub" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 66399, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-jassub-types-package.log", + "sha256": "4dc33ad76235d7f0eee97de5be04e212d1c6ec6b82a0079d5d85c4d1ad11fc23" + } + }, + { + "command": [ + "test:mediabunny-types-package" + ], + "packages": [ + "artplayer-proxy-mediabunny" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 36646, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-mediabunny-types-package.log", + "sha256": "d85d7599393a537820ba540ccc3753aa5406dade9f6d57061c5038d9f84eb401" + } + }, + { + "command": [ + "test:multiple-subtitles-types-package" + ], + "packages": [ + "artplayer-plugin-multiple-subtitles" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 33383, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-multiple-subtitles-types-package.log", + "sha256": "53950e4474089f8a599919e20e6ad262942c6bfbb399537bdfe4d75a66658495" + } + }, + { + "command": [ + "test:thumbnail-types-package" + ], + "packages": [ + "artplayer-tool-thumbnail" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 12524, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-thumbnail-types-package.log", + "sha256": "68b90cef4929bc5061e4a6c49a5eaa2ef9769c6347db8c59697c2eabdefe7732" + } + }, + { + "command": [ + "test:vast-types-package" + ], + "packages": [ + "artplayer-plugin-vast" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 33901, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-vast-types-package.log", + "sha256": "650aa1d7548bb1353cab5068781d359772abe6adec82c5a00dcd6999d3a2a835" + } + }, + { + "command": [ + "test:vtt-thumbnail-types-package" + ], + "packages": [ + "artplayer-plugin-vtt-thumbnail" + ], + "outcome": { + "code": 0, + "signal": null, + "elapsedMs": 42444, + "log": "refactor/.cache/ecosystem-types/run-NPPwIw/test-vtt-thumbnail-types-package.log", + "sha256": "e4b21b6506f543a1b3550d50279aa093562f7fa19cf9b9c1ad885372c5be7732" + } + } + ], + "detailedReports": [ + { + "command": [ + "test:package", + "--include=artplayer-plugin-audio-track,artplayer-plugin-hls-control" + ], + "packages": [ + "artplayer", + "artplayer-plugin-chapter", + "artplayer-plugin-audio-track", + "artplayer-plugin-hls-control" + ], + "report": "refactor/.cache/packages/run-A5vxPK/report.json", + "reportSha256": "e060e12c9cb37340a28a6d5b58f7591dfa0bb776231573174b3510a248db2ff4", + "matrixCases": null, + "scope": null + }, + { + "command": [ + "test:ads-types-package" + ], + "packages": [ + "artplayer-plugin-ads" + ], + "report": "refactor/.cache/ads-package-types-nJB3n8/report.json", + "reportSha256": "d5079cad3a9603b7d217f097c80898188cf9351854053052837babc576a21998", + "matrixCases": 5, + "scope": "Packed current core and Ads, offline Yarn install and frozen reinstall outside workspace; five type modes and Node export identity. Historical scalar inference correction accepted with scope by maintainer; not complete media or distribution release validation." + }, + { + "command": [ + "test:ambilight-types-package" + ], + "packages": [ + "artplayer-plugin-ambilight" + ], + "report": "refactor/.cache/ambilight-package-types-9qd1GZ/report.json", + "reportSha256": "b032daba8ef473ccd5ac623a440b6d0468918f67b3b593528dc97a929e62ed5e", + "matrixCases": 17, + "scope": "Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, full factory replacements and required root option; runtime optional calls and self alias. Published 1.1.0 NodeNext ESM namespace and four old direct-call diagnostics are retained. Published 1.0.0 export-assignment and required fields have documented migration. This is not proxy, device or complete distribution acceptance." + }, + { + "command": [ + "test:asr-types-package" + ], + "packages": [ + "artplayer-plugin-asr" + ], + "report": "refactor/.cache/asr-package-types-gxEkbj/report.json", + "reportSha256": "90585c9248caf2c1828344fda412ec0fa2ad41e49f59cd7e1648acc401df0a1c", + "matrixCases": 17, + "scope": null + }, + { + "command": [ + "test:auto-thumbnail-types-package" + ], + "packages": [ + "artplayer-plugin-auto-thumbnail" + ], + "report": "refactor/.cache/auto-thumbnail-package-types-2SiaVM/report.json", + "reportSha256": "1e8446f3552a10333fb8843c98da0947f777e70f894abb4d8aaabf220f33cad6", + "matrixCases": 10, + "scope": "Actual npm 1.1.0 and packed candidate installed outside the workspace. Root historical type shape and conditional runtime entry are checked with exact negative lines. Registration and module identities use installed artifacts; no native extraction or device acceptance is implied." + }, + { + "command": [ + "test:canvas-types-package" + ], + "packages": [ + "artplayer-proxy-canvas" + ], + "report": "refactor/.cache/canvas-package-types-ofGZBs/report.json", + "reportSha256": "eca64c507424d94d09966e662a4a8ebdabb329d16fdaeb76e2b25e510f936810", + "matrixCases": 17, + "scope": "Two actual published proxies and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes and root/legacy/runtime contracts. Root preserves published 1.1.0 pure factory, optional Parameters, exact HTMLCanvasElement result and NodeNext ESM namespace shape. Runtime adds accurate self identity with CommonJS/ESM and old Node10 resolution. Published 1.0.0 required callback/export= differences and 1.1.0 direct NodeNext ESM errors remain recorded. No native media, device or complete distribution acceptance." + }, + { + "command": [ + "test:chromecast-types-package" + ], + "packages": [ + "artplayer-plugin-chromecast" + ], + "report": "refactor/.cache/chromecast-package-types-QwWDt7/report.json", + "reportSha256": "4f5a829be2847f8c1d035e32307b74e335c71a33afe3de5d1dd20dfbbe677083", + "matrixCases": 17, + "scope": "Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, full factory replacements and required root argument. Runtime adds precise callbacks, asynchronous registration and self alias. Published 1.1.0 NodeNext ESM namespace and eight original direct-consumer diagnostics are retained. Published 1.0.0 export-assignment typing conflicts are recorded separately; both versions had four optional option fields. This is not real SDK, device or complete distribution acceptance." + }, + { + "command": [ + "test:danmuku-types-package" + ], + "packages": [ + "artplayer-plugin-danmuku" + ], + "report": "refactor/.cache/danmuku-package-types-DwgaqW/report.json", + "reportSha256": "61d3f7d9e2a12d2d4d6cc7735752550ae18c47ff31e814abcf1b96605d38558d", + "matrixCases": 12, + "scope": "Actual npm 5.3.0 and packed candidate outside workspace; offline and frozen reinstall, exact installed bytes, unchanged root declaration, full factory replacement and extraction. Old synchronous emit and object points remain root typing only. Runtime types are checked separately. NodeNext root namespace failures are compared by exact codes and line positions. This is not real playback or complete publication acceptance." + }, + { + "command": [ + "test:danmuku-mask-types-package" + ], + "packages": [ + "artplayer-plugin-danmuku-mask" + ], + "report": "refactor/.cache/danmuku-mask-package-types-fpF1fe/report.json", + "reportSha256": "999cb03078600fbcbb8ad0d46d1a2a804dc978c252e90f8481d4837563b0ddb3", + "matrixCases": 15, + "scope": "Actual published and candidate packages installed outside workspace, offline/frozen lock stability, byte checks, strict types and CJS/legacy registration. No model startup, native inference, ESM browser or GPU acceptance." + }, + { + "command": [ + "test:dash-types-package" + ], + "packages": [ + "artplayer-plugin-dash-control" + ], + "report": "refactor/.cache/dash-package-types-0nHsFN/report.json", + "reportSha256": "9d51cdacbfc12043d9d00bf382b8e950e240e0e26444f4e42e8f4f7d8070e1b4", + "matrixCases": 5, + "scope": "Packed current core and DASH, offline Yarn install and frozen reinstall outside workspace; isolated public type acceptance only, not complete package runtime/SDK/media release validation." + }, + { + "command": [ + "test:dpip-types-package" + ], + "packages": [ + "artplayer-plugin-document-pip" + ], + "report": "refactor/.cache/dpip-package-types-Oo6fcg/report.json", + "reportSha256": "8a95caf9a51d29ffe66f3eae5538111c588cad67cff21d3b94fae32f7fc5a5b1", + "matrixCases": 27, + "scope": "Four actual published packages and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, compiler/default/import= contracts. Candidate keeps required Parameters and assignable void Result while offering opt-in AsyncResult. Published 1.0.0 missing runtime is an explicit negative control; historical NodeNext ESM diagnostics are retained. This is not native Document PiP or complete distribution/device acceptance." + }, + { + "command": [ + "test:iframe-types-package" + ], + "packages": [ + "artplayer-tool-iframe" + ], + "report": "refactor/.cache/iframe-package-types-uFIFIy/report.json", + "reportSha256": "78b5eb30a3796fa2fd99a8aba2c8fd878091f526bbe5dd99a4e8ea81fa0f0b46", + "matrixCases": 16, + "scope": "Actual npm archive, frozen Git workspace pack and candidate Yarn tarball installed outside workspace; byte verification, offline/frozen lock and strict positive/negative consumers. Old npm CJS namespace and distinct helper are preserved observations, not claims the candidate provides old package-name/helper aliases. Native player and distribution migration remain 05/06." + }, + { + "command": [ + "test:jassub-types-package" + ], + "packages": [ + "artplayer-plugin-jassub" + ], + "report": "refactor/.cache/jassub-package-types-wjwSXn/report.json", + "reportSha256": "5c4d2303e4814b713e88e6b04e32a1230fba101a7c511c77f7f00919d6ac3f96", + "matrixCases": 15, + "scope": "Actual npm 1.0.0/1.1.0 and Yarn-packed candidate/core installed outside workspace; offline installation and forced frozen reinstall with unchanged lock, strictly verified installed member hashes and non-link checks; root declaration text unchanged after CRLF normalization with both raw hashes recorded, exact old diagnostics, conditional accurate runtime declarations and Node lazy factory/legacy identity. Registrars are never invoked in Node; this is not browser Worker/WASM/font rendering or publication acceptance." + }, + { + "command": [ + "test:mediabunny-types-package" + ], + "packages": [ + "artplayer-proxy-mediabunny" + ], + "report": "refactor/.cache/mb-package-types-5kkstS/report.json", + "reportSha256": "ffc4cf21d3924abb8cd50e3851993ae7eec2d58ef3199ac4b99112b301baa5e0", + "matrixCases": 17, + "scope": "Two frozen published packages and packed candidate with packed candidate core, installed outside workspace; offline/frozen reinstall, exact bytes, default/legacy CJS/ESM runtime and compiler matrices. Exact optional whole-factory assignments checked against actual historical declaration bytes. New shim types are opt-in, without SDK declaration leakage. Historical NodeNext ESM failures remain explicit negative controls. Not native decoding, old-core runtime combinations or full distribution/license acceptance." + }, + { + "command": [ + "test:multiple-subtitles-types-package" + ], + "packages": [ + "artplayer-plugin-multiple-subtitles" + ], + "report": "refactor/.cache/multiple-subtitles-package-types-RvtowJ/report.json", + "reportSha256": "49b134ee83b4cc8cf175e1da44dea66faa0057e528ce5fd0fbc57e92bf727687", + "matrixCases": 22, + "scope": "Three actual published packages in five compiler modes and packed candidate in seven modes, installed outside the workspace with packed core. Offline frozen reinstalls and byte identity verified. Latest npm 1.2.0 factory shape, synchronous result and NodeNext namespace retained under approved ADR-025; its eight old direct-consumer diagnostics are preserved. Earlier 1.0.0/1.1.0 export-assignment conflicts have documented runtime-entry migration. Runtime asynchronous result and CommonJS namespace types pass independently. Full device and distribution acceptance remains separate." + }, + { + "command": [ + "test:thumbnail-types-package" + ], + "packages": [ + "artplayer-tool-thumbnail" + ], + "report": "refactor/.cache/thumbnail-package-types-5Keo7q/report.json", + "reportSha256": "e3d82ff8aee8d8a64a1641e14ef9fc7aa73912ef61b666a451b4749e8c56aeed", + "matrixCases": 12, + "scope": "Frozen Git input fixture and candidate Yarn pack installed outside workspace. The frozen fixture is not an original npm archive; no recovered 3.5.31 complete archive is claimed. Candidate root/legacy runtime, offline frozen install, all files and strict consumers verified." + }, + { + "command": [ + "test:vast-types-package" + ], + "packages": [ + "artplayer-plugin-vast" + ], + "report": "refactor/.cache/vast-package-types-jEi1dC/report.json", + "reportSha256": "7b79b9c0b84ed553659eb48f7c1b504bc012efee42c6109090019d2cd7c4a35f", + "matrixCases": 14, + "scope": "Actual published and Yarn-packed candidate installed outside workspace; offline forced frozen reinstall, member hashes and declaration path isolation. No real IMA, live ad SDK registration, device or complete task06 distribution claim." + }, + { + "command": [ + "test:vtt-thumbnail-types-package" + ], + "packages": [ + "artplayer-plugin-vtt-thumbnail" + ], + "report": "refactor/.cache/vtt-thumbnail-package-types-S9W8jG/report.json", + "reportSha256": "77e03e48a8c21a2975753e2174b0d23d59e0098eb117967bd693ebd6d6441e3a", + "matrixCases": 32, + "scope": "Five actual published packages in five compiler modes and packed candidate in seven modes, installed outside the workspace with packed core. Offline/frozen reinstall and byte identity are verified. Root preserves latest 1.1.0 factory and NodeNext ESM namespace, including plain module replacements. Historical raw direct-import diagnostics remain recorded. Direct/default import=require forms retain opposing 1.0.x and 1.1.0 evidence; the candidate follows 1.1.0 under approved type-compatibility-policy.md and documents earlier migration. Runtime describes Promise results, writable self identity and all named types with NodeNext and Node10 including no-interop consumers. Device and full distribution acceptance remain separate." + } + ], + "validation": { + "orchestratorTests": 4, + "ciTests": 80, + "strictDocsToolsTypes": "pass", + "fullLint": "pass", + "strictToolchain": "pass", + "distributionContentDiff": [], + "remoteCI": "not run", + "browserDeviceAcceptance": "not run" + }, + "logs": [ + { + "file": "refactor/.cache/ci-types-ecosystem.log", + "sha256": "5dfdfd46210e14054a605027842773e3d3238eeaf3e810c11393dca4942a2dd3" + }, + { + "file": "refactor/.cache/ci-types-ecosystem-tests-final.log", + "sha256": "4b25accf1f99e46242a8ed3e470c95282211f4c54283f6d6b56c47cb7dcf2054" + }, + { + "file": "refactor/.cache/ci-types-ecosystem-ci-tests.log", + "sha256": "a5b12b71a9c97aea1a829fa815701b10df6c9a7d0447e75609f43dbc1d93d0ae" + }, + { + "file": "refactor/.cache/ci-types-ecosystem-lint.log", + "sha256": "674c9b4d9bff48ba4fea0a6f8318705d67140250ab0f6a2541475ca54d2bc1e3" + } + ], + "inputs": { + "scripts/consumers/ecosystem.ts": "3c57cd39f62fdf1e5adedceef8f8bd02ddd346435f2a666d6929be7afafdd37f", + "test/ecosystem-types.test.js": "28015fa57553fbf2f38b2388e86bdfe404aae731723f4be7c5f1792b85f4cc31", + ".github/workflows/nodejs.yml": "2968371759010d5702f2376e3af3d5ff2fbba221c60fe6750f0bd80043db4004", + "refactor/scripts/ci-workflow.mjs": "5446c09ed2dec24a33d7802463e22fea1f08148fb5c5ff14998af91497b0acd1", + "refactor/scripts/ci-workflow.test.mjs": "fb17a605c09d70cd3eabaa14061982340340205b2be9c89debcfc433d86dd3cd", + "refactor/scripts/impact-model.mjs": "19804ba83b2e02556cbb66c1e90d4a71e6604d01adadf4b886f7968c2915e4fe", + "refactor/impact-policy.json": "058078d60305a8609ae2b8ebbda3b2dc6cfec93342a7d1aa810b8a2d0be03110", + "scripts/package-check.mjs": "cf19bbfd6010965e40495a9943f0d6fe9e024d110e22234490f16e7e3d461ac4", + "refactor/scripts/ads-package-types.mjs": "846af87f5f7f0c84fcbccecd4b3dfd6e2c1946ce47dfe7122760fe5f25cf5b6a", + "refactor/scripts/ambilight-package-types.mjs": "cddd0c877f19a8c4a7ca92f0599788840ee45803ec8c94748ff2f928fa10a8d0", + "refactor/scripts/asr-package-types.mjs": "fd450cf4ed91adf162e88832aa3ad29baf6167dbca38e3a8542152908df537e0", + "refactor/scripts/auto-thumbnail-package-types.mjs": "ffea1e0599ecfc85b52a8c4e18afcd642455d636ce371456c6fe21c5dc198bf9", + "refactor/scripts/canvas-package-types.mjs": "e5c5c6e574357261ac8aa029e348397ed43f5761782ad221e9293b5f540848f1", + "refactor/scripts/chromecast-package-types.mjs": "1c5666d0e71e694a7845ab3229f1c7f5112f5098885da99586c808d13f659126", + "refactor/scripts/danmuku-package-types.mjs": "859e96e989896f09a6bc225f3cbc4d40540808369e13a99d71976d3b87e1012c", + "refactor/scripts/danmuku-mask-package-types.mjs": "9acdecc6dfc91624c24b8e9e4a91f036267e4536eae180d65c3d208cc17f154c", + "refactor/scripts/dash-package-types.mjs": "3c9663979ff640b0d2cdc7b68f67ad76636473722057bca59cfcf4511aec741a", + "refactor/scripts/dpip-package-types.mjs": "85ef013a770e609399d84fbaf8ec559fa1771d6c66d2f99514060d9fa2425fe2", + "refactor/scripts/iframe-package-types.mjs": "8dd0006f605752ffdbf51c58bf5ee4cca503dc6c3c1793a8b03dc14fc62d27ea", + "refactor/scripts/jassub-package-types.mjs": "58c7520cf7e05d26d6eabb5354fd3e103588b4e213e50c6405ff2e8b2d1446cd", + "refactor/scripts/mb-package-types.mjs": "2f8a5ca12ada786155621315af39c346db782f08852ab3dd4736a1bf6c002da0", + "refactor/scripts/multiple-subtitles-package-types.mjs": "8f8fdca94bd566d2f505950a646b0c25bb8eca49d3c839ca81a3712858c456b1", + "refactor/scripts/thumbnail-package-types.mjs": "27318713c7ba77b0a373c29a60f102fbb43629f787d508ee83d7f0720fbe8fe3", + "refactor/scripts/vast-package-types.mjs": "4d33091b82536cb4198efdee906fcc04afb7c52045057ab5917d9d4e2a20eaeb", + "refactor/scripts/vtt-thumbnail-package-types.mjs": "f6bc446fd8e496d7ab7f0be5c6b63d88a0e458938879aed0c1e6ae4d629b22ea" + }, + "scope": "All 21 library installed type checkers executed after fresh builds, including exact expected historical diagnostics. No claim of all runtime, browser, SDK/device or release acceptance." +} diff --git a/refactor/changes/2026-09-15-CI-TYPES-02-ecosystem.md b/refactor/changes/2026-09-15-CI-TYPES-02-ecosystem.md new file mode 100644 index 000000000..58da1d956 --- /dev/null +++ b/refactor/changes/2026-09-15-CI-TYPES-02-ecosystem.md @@ -0,0 +1,52 @@ +# CI-TYPES-02:全生态安装类型门槛 + +基线 `00c131bff130fad4ff03f90d7f02c124bc177cb5`。已有17个独立包类型命令 +没有作为全生态必需步骤进入CI;CI-TYPES-01只补齐了共享Audio/HLS类型消费。 +本任务建立显式21库包清单,将完整已存在的类型契约真正串入流水线。 + +## 结构与失败语义 + +`scripts/consumers/ecosystem.ts` 管理包/命令清单、子进程和逐步报告。 +21库包恰好覆盖一次;VitePress站点由既有site/editor检查承担,不冒充可安装库。 +共享命令覆盖核心、Chapter、Audio、HLS,其余17包运行原独立测试,保留历史 +诊断、版本对比、类型提取、条件导出和准确runtime入口,不用空导入替换。 + +`yarn test:ecosystem-types` 先运行build:types、build all、build:i18n。 +准备失败立即停止,避免测试旧dist;准备成功后串行运行18组,单包失败记录 +实际退出码并继续收集其他包证据,总命令最终非零退出。子进程不开shell, +NODE_PATH清空,stdout/stderr写独立日志,每组结束写报告和日志SHA-256。 +目录位于refactor/.cache/ecosystem-types/run-*,各包原报告保持独立。 + +正常构建允许生成分发文件,但不得夹带无关变更。本轮重建后Git没有分发内容 +差异,源码、公开声明、版本、依赖及锁文件保持原状。没有添加新依赖。 +工具模块已纳入原严格TS/lint,调度器测试加入test:node。 + +## CI与维护 + +browser-consumers在原Node20/22/标准版、React/Vue、Iframe及性能检查之后 +执行新步骤,保留三OS矩阵和原60分钟作业期限。此顺序避免共享四包类型检查 +更新latest.json影响前面已选定的浏览器map。后续全installed浏览器检查必须 +重新准备完整--browser清单。原有步骤、缓存边界、只读权限及总CI result不变。 + +CI校验器和影响模型将该命令作为第九项必需调用,拒绝移除、条件跳过和丢失 +原始类型证据;上传总报告及专用包JSON/log。维护说明位于消费者模块README、 +test/package/README.md和ci-setup.md。本地配置并不证明远端运行成功或耗时 +满足预算,CI-01/CI-04继续保留实际Actions验收。 + +## 验证范围 + +最终命令、每包退出结果、原始报告及代码指纹见 +[机器证据](../baselines/ecosystem-types-validation.json)。 +调度器受控子进程测试验证缺包/重复/缺命令、非零退出、双输出流、启动失败, +以及构建失败不进入消费、首包失败仍跑完其余包并使总命令失败。 +这些故障测试只证明调度语义;真实包兼容证据来自另外执行的完整安装命令。 + +类型命令成功允许已明确记录并精确断言的历史诊断,不等于历史声明错误消失。 +Thumbnail仍待默认运行时行为决策,Auto Thumbnail首帧及其他SDK/设备/像素 +问题继续开放。本次没有修复或降低这些门槛,没有浏览器播放、远端CI、部署 +或发布操作,不把21包类型检查替代全生态运行时组合与三轮发布复盘。 + +## 回退 + +回退本独立提交移除汇总命令、新CI门槛、对应验证/报告与维护说明;17个 +独立包检查和CI-TYPES-01的四包共享检查继续可用。消费者API无需迁移。 diff --git a/refactor/ci-setup.md b/refactor/ci-setup.md index 40aa99ca4..df97e22b7 100644 --- a/refactor/ci-setup.md +++ b/refactor/ci-setup.md @@ -1,5 +1,12 @@ # PR 检查和 Pages 操作说明 +CI-TYPES-02 增加 `yarn test:ecosystem-types`:四包共享消费者加十七个独立 +类型检查命令覆盖全部二十一库包,先重建声明/分发/i18n,再逐包隔离安装验证。 +browser-consumers 在恢复标准 Node 并完成原消费步骤后执行;类型失败使作业失败, +上传总报告和各包原始 JSON/log。维护入口见 +[消费者模块](../scripts/consumers/README.md)。这些类型结果不批准 Thumbnail +尚未决策的默认运行时行为,也不代替真实播放、设备或远端流水线验收。 + 最新统一安装清单含二十包。VAST 的完整 glomex 加载边界进入普通 installed 范围;真实 IMA 仍由独立 test:vast-native 按显式安装 map 验证,本地 39/39 通过不表示远端 CI 已覆盖真实广告。见[记录](changes/2026-09-15-CI-01-vast-installed.md)。 diff --git a/refactor/impact-analysis.md b/refactor/impact-analysis.md index 7b7c07fc9..7b6f69da2 100644 --- a/refactor/impact-analysis.md +++ b/refactor/impact-analysis.md @@ -40,9 +40,9 @@ GitHub PR读取事件里的base SHA,push读取before SHA;初次push的全零 ## CI门槛与能力边界 -八项必需调用不能从策略中漏掉:`ci:check`、`ci:build`、消费者 `test:package`、 +九项必需调用不能从策略中漏掉:`ci:check`、`ci:build`、消费者 `test:package`、 播放作业 `test:package --browser`、`test:browser:source`、`test:browser:installed`、 -`test:coverage`、`test:performance`。两个浏览器范围只传当前矩阵的 `--project`, +`test:coverage`、`test:performance`、`test:ecosystem-types`。两个浏览器范围只传当前矩阵的 `--project`, 并使用固定 `!cancelled()` 条件在普通失败后继续采证;其余门槛保持无条件步骤。 固定 yaml 2.8.2 解析实际 workflow,检查对应 job 必需执行、没有 continue-on-error、 没有路径过滤,且 checkout 有完整历史。命令被移走、改成 echo 或更换执行条件会 @@ -60,6 +60,9 @@ GitHub PR读取事件里的base SHA,push读取before SHA;初次push的全零 Node 消费者列表,也不等于二十包全部类型/设备/SDK 组合通过。Audio/HLS 的额外 五模式安装类型验证由 CI-TYPES-01 接入,报告单列 additionalTypeScope;其余包 的专用类型检查仍保留,不能因此关闭十九包完整安装消费的缺口。 + CI-TYPES-02 将四包共享检查和十七个专用类型命令汇总为必需的 + `test:ecosystem-types`,显式覆盖二十一库包;准备失败拒绝消费旧产物, + 任一包失败使总命令失败。类型结果单独记录,不扩张上述通用 Node 验收范围。 - 覆盖率范围从实际coverage-policy读取;浏览器/性能命令运行已有用例,不代表所有 包、真实设备、远程SDK已验收。 - ENG-COVERAGE-01继续把契约、支持版本、测试ID、候选/报告及任务逐项连接; diff --git a/refactor/impact-policy.json b/refactor/impact-policy.json index 552ebe196..6cf033451 100644 --- a/refactor/impact-policy.json +++ b/refactor/impact-policy.json @@ -321,6 +321,11 @@ "job": "coverage", "command": "yarn test:coverage", "scope": "Current coverage-policy packages and critical module thresholds only" + }, + { + "job": "browser-consumers", + "command": "yarn test:ecosystem-types", + "scope": "Rebuild and execute explicit installed type contracts for all 21 libraries; preserve historical diagnostics and separate runtime/device gates" } ], "installedConsumerPackages": [ diff --git a/refactor/plan.md b/refactor/plan.md index 136b0d622..ec57effb8 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -2,9 +2,9 @@ > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 -基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 278 项,范围 22 个包及工作区/示例。 +基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 279 项,范围 22 个包及工作区/示例。 -状态:todo 43 / doing 21 / blocked 0 / done 214 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 43 / doing 21 / blocked 0 / done 215 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -96,10 +96,11 @@ | CI-01 | workspace
增强兼容矩阵、并发缓存与 CI 报告 | DOC-10, ENG-08, ENG-09, ENG-10 | OS/Node/TS/浏览器与影响范围矩阵、缓存、超时、汇总检查和 artifact 报告 | 固定安装、失败/取消不误报、核心影响全生态;检查只读,失败证据可追溯 | H | doing | | CI-02 | workspace
分离并改进 GitHub Pages 部署 | DOC-10, ENG-02, SITE-03 | Pages artifact 部署配置、旧路径/域名核对、预检和迁移恢复指南 | 部署只取受信任已验证产物;本地实现可验收,远端 source/环境和实际部署状态单独登记 | H | done | | CI-03 | workspace
建立 npm 分包候选与发布工作流 | DOC-10, CI-01, REL-08, REL-04, CI-NPM-01 | 候选准备、精确 artifact 发布配置、OIDC 评估、版本/tag/registry 预检和部分失败恢复 | 不自动发布;明确逐包信任前置和 dry run 限制,不能重建未验证内容或重发冲突版本;按版本清单校验各包下一 major 和预发布/正式 tag,保留旧核心支持范围 | H | todo | -| CI-04 | workspace
验收 GitHub 流水线与远端发布准入 | CI-01, CI-02, CI-03, SITE-06, CI-BROWSER-01, CI-TYPES-01 | 静态/干净环境检查、真实 PR 正反例、候选 dry run、required checks/Pages/npm 必需配置状态及运维指南 | 必要 Actions 证据和远端配置核对齐全;缺失保持未完成,真实 publish/deploy 仍在授权发布步骤执行 | H | todo | +| CI-04 | workspace
验收 GitHub 流水线与远端发布准入 | CI-01, CI-02, CI-03, SITE-06, CI-BROWSER-01, CI-TYPES-01, CI-TYPES-02 | 静态/干净环境检查、真实 PR 正反例、候选 dry run、required checks/Pages/npm 必需配置状态及运维指南 | 必要 Actions 证据和远端配置核对齐全;缺失保持未完成,真实 publish/deploy 仍在授权发布步骤执行 | H | todo | | CI-BROWSER-01 | workspace
分离源码与已安装产物浏览器验证范围 | ENG-05, ENG-07 | 完整源码入口、明确已安装包子集、分开的报告目录与失败传播 | 混用输入旧红新绿;源码默认保留所有spec,已安装入口严格校验四包来源;两类报告都保留,源码失败不能误报全绿;不代表完整远端或全包验收 | M | done | | CI-NPM-01 | workspace
从已验收候选准备不可重建的npm交付包 | DOC-10, REL-08, REL-04 | 复用严格准入台账、复制精确tarball、绑定源码/工具链/证据/摘要的本地准备命令及反向测试;供CI-03后续受信任artifact工作流使用 | 缺候选或任一准入缺口即拒绝;不构建、不安装、不联网或发布;阻止路径越界、脏源码、复制期间漂移及半成品冒充完成,声明远端信任/OIDC/registry预检仍未实现 | H | done | | CI-TYPES-01 | workspace, artplayer-plugin-audio-track, artplayer-plugin-hls-control
接入 Audio/HLS 安装后严格类型消费者 | ENG-07, PKG-AUDIO-04, PKG-HLS-04 | 共享隔离编译器模块、真实 tarball 的五模式正反例和声明路径证据 | 旧/新编译器所有正例通过,每条无效调用实际报错;仓库外声明解析、缺声明/any/逃逸负例有效;保留独立运行时和浏览器验收 | M | done | +| CI-TYPES-02 | workspace
将全部库包的独立安装类型验证接入CI | CI-TYPES-01, ENG-07 | 21库包完整检查清单、实际构建和隔离安装测试调度、失败证据汇总及必需CI门槛 | 不得漏包或用通用导入替代历史类型契约;准备失败不消费旧产物;记录每包实际结果并传播失败,保留运行时/设备/未决兼容门槛 | M | done | ## 2.1 早期试点 @@ -732,3 +733,4 @@ - PKG-AUTO-THUMB-13: [记录](changes/2026-09-15-PKG-AUTO-THUMB-13-registration.md) [记录](baselines/auto-thumbnail-registration-validation.json) - SITE-ROUTES-01: [记录](changes/2026-09-15-SITE-ROUTES-01-html-inventory.md) [记录](baselines/site-routes-validation.json) - CI-TYPES-01: [记录](changes/2026-09-15-CI-TYPES-01-installed-consumers.md) [记录](baselines/installed-plugin-types-validation.json) +- CI-TYPES-02: [记录](changes/2026-09-15-CI-TYPES-02-ecosystem.md) [记录](baselines/ecosystem-types-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index f0469b142..98458f9d6 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,20 @@ # 进度与证据 +## CI-TYPES-02 全生态安装类型门槛 + +新增test:ecosystem-types,明确21库包无遗漏/重复。先重建声明、全部分发和i18n, +再串行执行四包共享消费者及17个专用类型检查;真实18组全部通过,579.13秒。 +历史诊断、精确runtime入口和各包版本对比按原契约保留,没有放宽测试。 + +CI消费者三OS增加必需步骤和原始报告上传;影响模型与CI校验保护第九项门槛。 +4项调度故障/清单测试、80项CI测试、完整lint、严格TS和工具链通过;重建无 +分发内容差异。见[记录](changes/2026-09-15-CI-TYPES-02-ecosystem.md)与 +[逐包证据](baselines/ecosystem-types-validation.json)。 + +Auto Thumbnail首帧仍有独立原生WebKit失败证据,没有添加任意等待或声称修复; +Thumbnail默认行为待决策及其他运行时/设备门槛保持。此次没有运行远端CI或 +完整浏览器矩阵,没有发布。215 done、21 doing、43 todo,共279。 + ## CI-TYPES-01 安装后的 Audio/HLS 类型验证 共享严格编译器模块已接入真实 tarball 消费:Audio 的12条和HLS的8条负例, diff --git a/refactor/scripts/ci-workflow.mjs b/refactor/scripts/ci-workflow.mjs index 0c52141c2..b97e4feeb 100644 --- a/refactor/scripts/ci-workflow.mjs +++ b/refactor/scripts/ci-workflow.mjs @@ -99,6 +99,7 @@ export function validateCIWorkflow(source) { ['test:vue-consumer', 'vue-consumer', 'vue-consumer-*/'], ['test:iframe-history', 'iframe-history', 'iframe-history/'], ['test:performance', 'performance', 'performance/'], + ['test:ecosystem-types', 'ecosystem-types', 'ecosystem-types/'], ]) { const index = consumers.steps.findIndex(step => step.run === `yarn ${command} 2>&1 | tee refactor/.cache/ci/${log}.log`) assert(index > consumerIndex && !Object.hasOwn(consumers.steps[index], 'if'), 'Run complete consumers after restoring canonical Node') @@ -106,6 +107,8 @@ export function validateCIWorkflow(source) { assert(!browser.steps.some(step => step.run?.includes(`yarn ${command}`)), 'Do not repeat all-engine consumers in each playback leg') consumerIndex = index } + for (const suffix of ['*.json', '*.log']) + assert(consumerUpload.with.path.split('\n').includes(`refactor/.cache/*-package-types-*/${suffix}`), 'Retain specialized installed type evidence') const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --browser ')) const sourceEngineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:source --project=${{ matrix.browser }} 2>&1 | tee refactor/.cache/ci/browser-source.log') const engineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:installed --project=${{ matrix.browser }} 2>&1 | tee refactor/.cache/ci/browser-installed.log') diff --git a/refactor/scripts/ci-workflow.test.mjs b/refactor/scripts/ci-workflow.test.mjs index d1fa62b74..809aad48a 100644 --- a/refactor/scripts/ci-workflow.test.mjs +++ b/refactor/scripts/ci-workflow.test.mjs @@ -67,6 +67,12 @@ for (const [name, mutate] of [ }], ['browser runtime not restored', w => w.jobs['browser-consumers'].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-consumers'].steps.find(s => s.run?.startsWith('yarn test:react-consumer')).if = 'false'], + ['skipped ecosystem types', w => w.jobs['browser-consumers'].steps.find(s => s.run?.startsWith('yarn test:ecosystem-types')).if = 'false'], + ['removed ecosystem types', w => w.jobs['browser-consumers'].steps = w.jobs['browser-consumers'].steps.filter(s => !s.run?.startsWith('yarn test:ecosystem-types'))], + ['missing specialized type evidence', (w) => { + const upload = w.jobs['browser-consumers'].steps.find(s => s.uses?.startsWith('actions/upload-artifact@')) + upload.with.path = upload.with.path.replace('refactor/.cache/*-package-types-*/*.json', '') + }], ['removed React consumer', w => w.jobs['browser-consumers'].steps = w.jobs['browser-consumers'].steps.filter(s => !s.run?.startsWith('yarn test:react-consumer'))], ['missing React evidence', w => w.jobs['browser-consumers'].steps.find(s => s.uses?.startsWith('actions/upload-artifact@')).with.path = 'refactor/.cache/ci/'], ['skipped Vue consumer', w => w.jobs['browser-consumers'].steps.find(s => s.run?.startsWith('yarn test:vue-consumer')).if = 'false'], diff --git a/refactor/scripts/impact-model.mjs b/refactor/scripts/impact-model.mjs index 3b318fcb0..54271da56 100644 --- a/refactor/scripts/impact-model.mjs +++ b/refactor/scripts/impact-model.mjs @@ -112,6 +112,7 @@ export function readImpactModel(directory) { 'checks yarn ci:check', 'checks yarn ci:build', 'browser-consumers yarn test:package', + 'browser-consumers yarn test:ecosystem-types', 'browser-smoke yarn test:package --browser', 'browser-smoke yarn test:browser:source --project=${{ matrix.browser }}', 'browser-smoke yarn test:browser:installed --project=${{ matrix.browser }}', diff --git a/refactor/tasks.json b/refactor/tasks.json index d6ff708ed..6c729c28d 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -844,7 +844,8 @@ "CI-03", "SITE-06", "CI-BROWSER-01", - "CI-TYPES-01" + "CI-TYPES-01", + "CI-TYPES-02" ], "status": "todo", "risk": "H", @@ -5995,6 +5996,26 @@ "changes/2026-09-15-CI-TYPES-01-installed-consumers.md", "baselines/installed-plugin-types-validation.json" ] + }, + { + "id": "CI-TYPES-02", + "phase": "2.2 GitHub CI/CD", + "title": "将全部库包的独立安装类型验证接入CI", + "scope": [ + "workspace" + ], + "dependsOn": [ + "CI-TYPES-01", + "ENG-07" + ], + "status": "done", + "risk": "M", + "deliverable": "21库包完整检查清单、实际构建和隔离安装测试调度、失败证据汇总及必需CI门槛", + "acceptance": "不得漏包或用通用导入替代历史类型契约;准备失败不消费旧产物;记录每包实际结果并传播失败,保留运行时/设备/未决兼容门槛", + "evidence": [ + "changes/2026-09-15-CI-TYPES-02-ecosystem.md", + "baselines/ecosystem-types-validation.json" + ] } ] } diff --git a/scripts/consumers/README.md b/scripts/consumers/README.md index 6243f31d8..e4dcd28af 100644 --- a/scripts/consumers/README.md +++ b/scripts/consumers/README.md @@ -33,3 +33,41 @@ Maintain the module with `yarn typecheck:docs-tools`, `yarn lint`, and `node --test test/installed-types.test.js` (also included in `yarn test:node`). The tests compile a real isolated package and reject a missing declaration, `any` widening, and a package link outside the consumer. + +## Complete ecosystem gate + +`yarn test:ecosystem-types` rebuilds public declarations, all package bundles and +core languages, then runs the four-package shared consumer plus all 17 specialized +package type commands sequentially. `ecosystem.ts` validates that this explicit +roster covers every library exactly once; the VitePress site retains its separate +site/editor checks. A new library or missing command fails instead of silently +reducing scope. Existing historical diagnostics, conditional exports and accurate +runtime entry fixtures remain owned by their specialized checkers. + +Preparation failure stops before any installed validation, avoiding stale bundle +acceptance. A package failure records its actual exit code and continues through +the remaining packages, then fails the aggregate command. Each child writes stdout +and stderr to a dedicated file; after each child, the report records duration, +exit code or launch error, log fingerprint and package ownership. Results live in +`refactor/.cache/ecosystem-types/run-*/`. Specialized checkers keep their own detailed +reports and fingerprints under `refactor/.cache/*-package-types-*/`. + +This command intentionally regenerates tracked distribution files using the +normal build commands. Review any real output drift before committing; formatting +normalization alone is not a runtime change. Do not run another source edit/build +concurrently. Checks run one at a time to avoid competing installs and excessive +compiler memory. The CI consumer job runs this gate after its existing runtime, +framework, history and performance checks on each OS, with the canonical Node +restored. CI retains the aggregate directory and detailed JSON/log artifacts on +failure. The existing job timeout remains in force. + +The shared four-package check updates `refactor/.cache/packages/latest.json`. +That is why CI runs this gate after the existing runtime/browser consumers, which +already selected their own map. Before a later full installed browser run, prepare +its twenty-package map again with `yarn test:package --browser`; never pass the +four-package type preparation off as the complete browser artifact roster. + +Successful completion means every selected checker met its documented type +contract, including exact expected historical failures. It does not approve the +pending Thumbnail runtime compatibility choice or replace media/device/SDK tests, +release reviews, or actual remote CI evidence. diff --git a/scripts/consumers/ecosystem.ts b/scripts/consumers/ecosystem.ts new file mode 100644 index 000000000..fc3738742 --- /dev/null +++ b/scripts/consumers/ecosystem.ts @@ -0,0 +1,95 @@ +import assert from 'node:assert/strict' +import { spawn } from 'node:child_process' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { fileURLToPath } from 'node:url' + +export const ecosystemChecks = [ + { script: 'test:package', args: ['--include=artplayer-plugin-audio-track,artplayer-plugin-hls-control'], packages: ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-audio-track', 'artplayer-plugin-hls-control'] }, + ...[ + ['ads', 'artplayer-plugin-ads'], + ['ambilight', 'artplayer-plugin-ambilight'], + ['asr', 'artplayer-plugin-asr'], + ['auto-thumbnail', 'artplayer-plugin-auto-thumbnail'], + ['canvas', 'artplayer-proxy-canvas'], + ['chromecast', 'artplayer-plugin-chromecast'], + ['danmuku', 'artplayer-plugin-danmuku'], + ['danmuku-mask', 'artplayer-plugin-danmuku-mask'], + ['dash', 'artplayer-plugin-dash-control'], + ['dpip', 'artplayer-plugin-document-pip'], + ['iframe', 'artplayer-tool-iframe'], + ['jassub', 'artplayer-plugin-jassub'], + ['mediabunny', 'artplayer-proxy-mediabunny'], + ['multiple-subtitles', 'artplayer-plugin-multiple-subtitles'], + ['thumbnail', 'artplayer-tool-thumbnail'], + ['vast', 'artplayer-plugin-vast'], + ['vtt-thumbnail', 'artplayer-plugin-vtt-thumbnail'], + ].map(([name, pkg]) => ({ script: `test:${name}-types-package`, args: [], packages: [pkg!] })), +] + +export function verifyEcosystemScope(directory: string, checks = ecosystemChecks) { + const names = fs.readdirSync(path.join(directory, 'packages')).filter(name => name !== 'artplayer-vitepress' && fs.existsSync(path.join(directory, 'packages', name, 'package.json'))).sort() + const covered = checks.flatMap(check => check.packages).sort() + assert.deepEqual(covered, names, 'Every library package needs exactly one installed type check') + const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'package.json'), 'utf8')) as { scripts: Record } + for (const check of checks) + assert(manifest.scripts[check.script], `Missing consumer command: ${check.script}`) + return names +} + +export async function runConsumerCommand(executable: string, args: string[], cwd: string, log: string) { + const started = Date.now() + const file = fs.openSync(log, 'w') + try { + const result = await new Promise<{ code: number | null, signal: NodeJS.Signals | null, error?: string }>((resolve) => { + const child = spawn(executable, args, { cwd, windowsHide: true, stdio: ['ignore', file, file], env: { ...process.env, NODE_PATH: '' } }) + child.once('error', error => resolve({ code: null, signal: null, error: error.message })) + child.once('close', (code, signal) => resolve({ code, signal })) + }) + return { ...result, elapsedMs: Date.now() - started, log, sha256: createHash('sha256').update(fs.readFileSync(log)).digest('hex') } + } + finally { + fs.closeSync(file) + } +} + +export async function checkEcosystem(directory = fileURLToPath(new URL('../../', import.meta.url))) { + assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use the pinned Yarn') + const yarn = process.env.npm_execpath + assert(yarn && fs.existsSync(yarn), 'Missing Yarn executable') + const packages = verifyEcosystemScope(directory) + const parent = path.join(directory, 'refactor/.cache/ecosystem-types') + fs.mkdirSync(parent, { recursive: true }) + const output = fs.mkdtempSync(path.join(parent, 'run-')) + const results: { command: string[], packages: string[], outcome: Awaited> }[] = [] + const save = () => fs.writeFileSync(path.join(output, 'report.json'), `${JSON.stringify({ packages, node: process.version, results, scope: 'Installed type contracts, including explicitly recorded historical diagnostics. Not runtime/device or release acceptance.' }, null, 2)}\n`) + console.log(`Ecosystem installed types: ${output}`) + for (const args of [['build:types'], ['build', 'all'], ['build:i18n']]) { + const outcome = await runConsumerCommand(process.execPath, [yarn, ...args], directory, path.join(output, `prepare-${results.length}.log`)) + results.push({ command: args, packages: [], outcome }) + save() + assert.equal(outcome.code, 0, `Cannot test stale packages after failed preparation; see ${outcome.log}`) + } + let failed = false + for (const [index, check] of ecosystemChecks.entries()) { + const args = [check.script, ...check.args] + console.log(`[${index + 1}/${ecosystemChecks.length}] ${args.join(' ')}`) + const outcome = await runConsumerCommand(process.execPath, [yarn, ...args], directory, path.join(output, `${check.script.replaceAll(':', '-')}.log`)) + results.push({ command: args, packages: check.packages, outcome }) + save() + if (outcome.code !== 0) + failed = true + console.log(`${check.script}: ${outcome.code === 0 ? 'passed' : 'FAILED'} (${Math.round(outcome.elapsedMs / 1000)}s); ${outcome.log}`) + } + assert(!failed, `Installed ecosystem type checks failed; all available results retained in ${output}`) + return { output, results } +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + checkEcosystem().catch((error: unknown) => { + console.error(error) + process.exitCode = 1 + }) +} diff --git a/test/ecosystem-types.test.js b/test/ecosystem-types.test.js new file mode 100644 index 000000000..e721cc46f --- /dev/null +++ b/test/ecosystem-types.test.js @@ -0,0 +1,74 @@ +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 -- Tests repository consumer orchestration with real child processes. +import { test } from 'node:test' +import { ecosystemChecks, runConsumerCommand, verifyEcosystemScope } from '../scripts/consumers/ecosystem.ts' +import { consumerDirectory, removeConsumer, workspace } from '../scripts/package-consumer.mjs' + +test('Installed type roster covers every library once and rejects omissions, duplicates and missing scripts', () => { + assert.equal(verifyEcosystemScope(workspace).length, 21) + assert.throws(() => verifyEcosystemScope(workspace, ecosystemChecks.slice(1)), /Every library package/) + assert.throws(() => verifyEcosystemScope(workspace, [...ecosystemChecks, ecosystemChecks[0]]), /Every library package/) + const missing = ecosystemChecks.map(check => ({ ...check })) + missing[0].script = 'test:missing-consumer' + assert.throws(() => verifyEcosystemScope(workspace, missing), /Missing consumer command/) +}) + +test('Consumer child failures retain both output streams and actual exit status', async () => { + const dir = consumerDirectory() + try { + const log = path.join(dir, 'failed.log') + const failed = await runConsumerCommand(process.execPath, ['-e', 'console.log("before failure"); console.error("type mismatch"); process.exitCode=23'], dir, log) + assert.equal(failed.code, 23) + assert.equal(failed.signal, null) + assert.match(fs.readFileSync(log, 'utf8'), /before failure[\s\S]*type mismatch/) + const passed = await runConsumerCommand(process.execPath, ['-e', 'console.log("next package")'], dir, path.join(dir, 'next.log')) + assert.equal(passed.code, 0) + assert.match(fs.readFileSync(passed.log, 'utf8'), /next package/) + const missing = await runConsumerCommand(path.join(dir, 'missing-executable'), [], dir, path.join(dir, 'missing.log')) + assert.equal(missing.code, null) + assert.match(missing.error, /ENOENT/) + } + finally { removeConsumer(dir) } +}) + +for (const failure of ['build', 'test:package']) { + test(`Ecosystem command propagates ${failure} failure with the correct remaining scope`, async () => { + const dir = consumerDirectory() + try { + const scripts = Object.fromEntries(ecosystemChecks.map(check => [check.script, 'controlled child'])) + fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ scripts })) + for (const name of ecosystemChecks.flatMap(check => check.packages)) { + const folder = path.join(dir, 'packages', name) + fs.mkdirSync(folder, { recursive: true }) + fs.writeFileSync(path.join(folder, 'package.json'), JSON.stringify({ name })) + } + const yarn = path.join(dir, 'controlled-yarn.cjs') + fs.writeFileSync(yarn, `console.log(process.argv.slice(2)); if (process.argv[2] === ${JSON.stringify(failure)}) process.exitCode = 29`) + const launcher = path.join(dir, 'launch.mjs') + fs.writeFileSync(launcher, `import { checkEcosystem } from ${JSON.stringify(new URL('../scripts/consumers/ecosystem.ts', import.meta.url).href)}; +process.env.npm_config_user_agent = 'yarn/1.22.22'; +process.env.npm_execpath = ${JSON.stringify(yarn)}; +try { await checkEcosystem(${JSON.stringify(dir)}) } catch (error) { console.error(error); process.exitCode = 1 } +`) + const result = await runConsumerCommand(process.execPath, [launcher], dir, path.join(dir, 'aggregate.log')) + assert.equal(result.code, 1) + const parent = path.join(dir, 'refactor/.cache/ecosystem-types') + const [run] = fs.readdirSync(parent) + const report = JSON.parse(fs.readFileSync(path.join(parent, run, 'report.json'), 'utf8')) + if (failure === 'build') { + assert.deepEqual(report.results.map(item => item.command), [['build:types'], ['build', 'all']]) + assert.deepEqual(report.results.map(item => item.outcome.code), [0, 29]) + } + else { + assert.equal(report.results.length, ecosystemChecks.length + 3) + assert.deepEqual(report.results.slice(3).map(item => item.command[0]), ecosystemChecks.map(check => check.script)) + assert.equal(report.results.filter(item => item.outcome.code === 29).length, 1) + assert(report.results.slice(4).every(item => item.outcome.code === 0)) + } + } + finally { removeConsumer(dir) } + }) +} diff --git a/test/package/README.md b/test/package/README.md index e97ec9b63..b8492d4cc 100644 --- a/test/package/README.md +++ b/test/package/README.md @@ -1,5 +1,12 @@ # Installed package checks +Run `yarn test:ecosystem-types` for the complete library type roster: the shared +core/chapter/Audio/HLS consumer plus seventeen specialized package checks. +It rebuilds packages first, preserves each historical declaration contract, and +retains all package failures before returning a failing aggregate status. See +[orchestrator ownership and reports](../../scripts/consumers/README.md). +Type success does not approve unresolved runtime behavior or device gates. + `historicalDistributionFiles()` in `scripts/package-check.mjs` distinguishes a same-name npm release from the Iframe package rename. Iframe verifies the real plugin archive plus the frozen tool workspace, then requires all four original