ci: [CI-01] verify identical tarballs on three Node runtimes

This commit is contained in:
Harvey Zhao committed 2026-09-13 13:21:17 +08:00
1 parent 585e11c53c
commit 38355ae4cf
21 files changed
+839 -27

No files matched your search

+24
View File
@@ -107,6 +107,30 @@ jobs:
run: | run: |
yarn test:package 2>&1 | tee refactor/.cache/ci/package.log yarn test:package 2>&1 | tee refactor/.cache/ci/package.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`);' 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: Select Node 20 consumer runtime
id: consumer-node-20
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20.19.0
package-manager-cache: false
- name: Verify Node 20 installed consumers
run: node scripts/package-runtime.mjs --expected-node 20.19.0 2>&1 | tee refactor/.cache/ci/consumer-node-20.log
- name: Select Node 22 consumer runtime
id: consumer-node-22
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.12.0
package-manager-cache: false
- name: Verify Node 22 installed consumers
run: node scripts/package-runtime.mjs --expected-node 22.12.0 2>&1 | tee refactor/.cache/ci/consumer-node-22.log
- name: Restore canonical runtime for browser tools
id: restore-canonical-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
package-manager-cache: false
- name: Verify canonical installed consumers
run: node scripts/package-runtime.mjs --canonical 2>&1 | tee refactor/.cache/ci/consumer-node-canonical.log
- name: Run all three engines - name: Run all three engines
run: yarn test:browser 2>&1 | tee refactor/.cache/ci/browser.log run: yarn test:browser 2>&1 | tee refactor/.cache/ci/browser.log
- name: Verify iframe cached history and interrupted navigation - name: Verify iframe cached history and interrupted navigation
+3 -2
View File
@@ -45,7 +45,7 @@
"check:toolchain": "node scripts/check-toolchain.mjs", "check:toolchain": "node scripts/check-toolchain.mjs",
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix", "lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
"check:plan": "node refactor/scripts/plan.mjs --check", "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: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:baseline": "node --test refactor/scripts/*.test.mjs", "test:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test", "ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn lint && yarn check:types && yarn typecheck && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports", "ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
@@ -92,7 +92,8 @@
"test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs", "test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs",
"test:jassub": "node --test test/jassub.test.js refactor/scripts/jassub-contract.test.mjs", "test:jassub": "node --test test/jassub.test.js refactor/scripts/jassub-contract.test.mjs",
"check:ci": "node refactor/scripts/ci-workflow.mjs", "check:ci": "node refactor/scripts/ci-workflow.mjs",
"test:ci": "node --test test/ci-summary.test.js refactor/scripts/ci-workflow.test.mjs refactor/scripts/impact.test.mjs" "test:ci": "node --test test/ci-summary.test.js refactor/scripts/ci-workflow.test.mjs refactor/scripts/impact.test.mjs test/package-runtime.test.js",
"test:package:runtime": "node scripts/package-runtime.mjs --canonical"
}, },
"browserslist": "last 1 Chrome version", "browserslist": "last 1 Chrome version",
"devDependencies": { "devDependencies": {
@@ -0,0 +1,441 @@
{
"schemaVersion": 1,
"task": "CI-01",
"date": "2026-09-13",
"baseCommit": "585e11c53c03b4b12933b21dcfe8c4a117299b2b",
"candidateScope": "Accompanying CI-01 working tree; production package code unchanged",
"nodeBinaries": [
{
"node": "20.19.0",
"platform": "win-x64",
"sha256": "6e3a39787e667d50487f7335c85636c2823a53e636d73c2c841d45da4e57906c",
"source": "https://nodejs.org/download/release/v20.19.0/SHASUMS256.txt"
},
{
"node": "22.12.0",
"platform": "win-x64",
"sha256": "b3b117a08ee61efee09e6fd523ab33c0c018da1b570bde08e4fd914dc1170ed6",
"source": "https://nodejs.org/download/release/v22.12.0/SHASUMS256.txt"
}
],
"build": {
"node": "24.21.0",
"yarn": "1.22.22",
"legacyTypeModes": 5,
"preciseTypeModes": 8,
"knownTypeBlockers": 0,
"knownRuntimeBlockers": 1
},
"runs": [
{
"node": "20.19.0",
"buildNode": "24.21.0",
"passed": true,
"candidateChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.runtime-cjs-identity",
"DIST.runtime-esm-identity",
"DIST.runtime-legacy-identity",
"DIST.runtime-shared-statics",
"DIST.runtime-types-only",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"publishedChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"unresolved": {
"name": "ReferenceError",
"message": "navigator is not defined",
"historical": false,
"resolved": false
},
"installedPackages": [
{
"name": "artplayer",
"version": "5.4.1",
"sha256": "6298aa247ca273fa5fb80297fc49d4434609e611e24a7f24cb37752c6768c214"
},
{
"name": "artplayer-plugin-chapter",
"version": "1.1.0",
"sha256": "53eb3c1bbcc046a28ae499cfb19731eb66a16453bf44eae73e03693bbb510793"
}
],
"report": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-20.19.0.json",
"sha256": "19f69281782a34a7829b47bdfbebeaa22a2bcbaef9e1b982bdd615247b136286"
},
"installLog": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-20.19.0-install.log",
"sha256": "048cba88c2a0a9c14e1e195ac88af9c4c359b6058672d542c49cdb2c6e4f1e6d"
}
},
{
"node": "22.12.0",
"buildNode": "24.21.0",
"passed": true,
"candidateChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.runtime-cjs-identity",
"DIST.runtime-esm-identity",
"DIST.runtime-legacy-identity",
"DIST.runtime-shared-statics",
"DIST.runtime-types-only",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"publishedChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"unresolved": {
"name": "ReferenceError",
"message": "navigator is not defined",
"historical": false,
"resolved": false
},
"installedPackages": [
{
"name": "artplayer",
"version": "5.4.1",
"sha256": "6298aa247ca273fa5fb80297fc49d4434609e611e24a7f24cb37752c6768c214"
},
{
"name": "artplayer-plugin-chapter",
"version": "1.1.0",
"sha256": "53eb3c1bbcc046a28ae499cfb19731eb66a16453bf44eae73e03693bbb510793"
}
],
"report": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-22.12.0.json",
"sha256": "6014c69d26444d0c1884aa2be5de801351d4b4f120682ecaef1d5d440ff67084"
},
"installLog": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-22.12.0-install.log",
"sha256": "0ad1239965a5caa605c43986d81a6efa3778fdc4d5752534edd8b21f1059fe62"
}
},
{
"node": "24.21.0",
"buildNode": "24.21.0",
"passed": true,
"candidateChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.runtime-cjs-identity",
"DIST.runtime-esm-identity",
"DIST.runtime-legacy-identity",
"DIST.runtime-shared-statics",
"DIST.runtime-types-only",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"publishedChecks": [
"DIST.cjs",
"DIST.cjs-legacy",
"DIST.esm",
"DIST.esm-default-only",
"DIST.esm-legacy",
"DIST.global-core",
"DIST.amd-core",
"DIST.global-core-legacy",
"DIST.amd-core-legacy",
"DIST.global-chapter",
"DIST.amd-chapter",
"DIST.global-chapter-legacy",
"DIST.amd-chapter-legacy",
"SSR.import-template",
"SSR.constructor-browser-only",
"SSR.custom-ua-umd-legacy",
"SSR.custom-ua-esm",
"DIST.i18n-cjs-esm-global",
"DIST.exports-boundary",
"DIST.direct-esm-file",
"EVENT.lazy-registry",
"EVENT.symbol-number-channels",
"EVENT.context-removal",
"EVENT.once-throw-consumed",
"EVENT.chain-context-arguments",
"EVENT.once-reentry",
"EVENT.dispatch-snapshot",
"EVENT.off-original-once",
"EVENT.throw-propagation",
"SSR.defaults-missing-navigator-known-failure",
"API.defaults-controlled-language"
],
"unresolved": {
"name": "ReferenceError",
"message": "navigator is not defined",
"historical": false,
"resolved": false
},
"installedPackages": [
{
"name": "artplayer",
"version": "5.4.1",
"sha256": "6298aa247ca273fa5fb80297fc49d4434609e611e24a7f24cb37752c6768c214"
},
{
"name": "artplayer-plugin-chapter",
"version": "1.1.0",
"sha256": "53eb3c1bbcc046a28ae499cfb19731eb66a16453bf44eae73e03693bbb510793"
}
],
"report": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-24.21.0.json",
"sha256": "8ca41aa19f023561500a9956d8c34a873cf438e062214dfe76310218ccf7b3fd"
},
"installLog": {
"file": "refactor/.cache/packages/run-oT7iFE/runtime-node-24.21.0-install.log",
"sha256": "973f6f01784e113ec2a4da62c6df6dcb32c99b548f23549cc3bc0eec5280f462"
}
}
],
"firstFailure": {
"error": "ReferenceError: navigator is not defined",
"result": "reproduced in published and candidate; CORE-25 open",
"log": {
"file": "refactor/.cache/ci01-node20-runtime.log",
"sha256": "2b2074af5e5afaab44530a047656a53d5736b19fb2dfe40dd54af724570ae60f"
}
},
"strictRelease": {
"command": "yarn test:package:release",
"expectedExitCode": 1,
"actualExitCode": 1,
"reason": "Known runtime blockers remain",
"log": {
"file": "refactor/.cache/ci01-node-release-negative.log",
"sha256": "6ff96a023493ea284411b725734b2cedf7eac3e4b0602ba458dcb14f09515319"
}
},
"localCI": {
"state": "passed",
"command": "yarn ci:check",
"total": 2429,
"unit": 2031,
"engineering": 27,
"baseline": 371,
"seconds": 240.99,
"targeted": 44,
"metadata": 9,
"actionlint": "1.7.12 passed",
"explicitLint": "passed"
},
"remoteActions": null,
"releaseReady": false,
"limitations": [
"Only core/chapter actual package fixture",
"Three Windows Node runtimes; hosted OS runs not verified",
"Historical and candidate missing-navigator failure is an explicit unresolved observation",
"Tooling minimum clean installs and older consumer Node scope remain unverified"
],
"logs": [
{
"file": "refactor/.cache/ci01-node-ci.log",
"sha256": "b753192c80948138e6ef3c063cf68e9a293496c0601340d24c4122d009975071"
},
{
"file": "refactor/.cache/ci01-node-test-ci.log",
"sha256": "c0f509f45e12f0fcf71939c520be4770673cb504e7c44622060ce111efd43279"
},
{
"file": "refactor/.cache/ci01-node-metadata.log",
"sha256": "7ed48f9eab2ac84ff388255dc87c7e28bcaec5125a5fee1651b9326946901e15"
},
{
"file": "refactor/.cache/ci01-node-actionlint.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"file": "refactor/.cache/ci01-node-lint-final.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
],
"inputs": [
{
"file": "scripts/package-runtime.mjs",
"sha256": "ecbe917118e67e4c53965002912bf4bded821f6bd6d00db1f3f1a56e62cab732"
},
{
"file": "scripts/package-consumer.mjs",
"sha256": "3ab9a6ea73981f777ff7db8a7cb0e54c61b5be43c1bcbc05013423afd1645a25"
},
{
"file": "scripts/package-check.mjs",
"sha256": "8b4a65f7f65083104cb0dba6325e622f221574c213f59656367c3c71b610571b"
},
{
"file": "test/package/runtime.cjs",
"sha256": "cf70882e29817dacfde9323b54890c3d0ae23eb2536c98ce90d573963de9721f"
},
{
"file": "test/package-runtime.test.js",
"sha256": "633e15b760c2ea8b9a0cb3eee49fde353f77a8dda7655595f9bded13e48dc4cb"
},
{
"file": "test/package-check.test.js",
"sha256": "e4f41015c81331ef085a158ec0330e82786402aa4f7e0308b7e3b1a95f2a6ff5"
},
{
"file": "refactor/scripts/ci-workflow.mjs",
"sha256": "588ad0e032c3e9a16620202a21bd293edfb042954411f58d7352d6fee38a631c"
},
{
"file": "refactor/scripts/ci-workflow.test.mjs",
"sha256": "6cac9f185739e9ea7753f7220ddcfa4b708a9699c029b998e2e257f6a04dcdb0"
},
{
"file": ".github/workflows/nodejs.yml",
"sha256": "f3f8476f4a35a80845350f83beaba15fdb70690273f9b56e4614b5e048d002bd"
},
{
"file": "package.json",
"sha256": "4b68d39f4e8fb3c1634ab11bde8686c48aa277e2bf2775a4bf3472f2d1030aca"
}
],
"inputByteScope": "Local working-tree bytes; Git checkout line endings may differ"
}
@@ -0,0 +1,64 @@
# CI-01 同一 tarball 的 Node 消费者矩阵
基于 585e11c53c03b4b12933b21dcfe8c4a117299b2b,标准构建 Node 为 24.21.0。
新增 Node 20.19.0、22.12.0、24.21.0 的实际安装运行,CI-01 保持 doing。
这些是私有根包 engines 的两个分支边界与标准环境,不是发布包声明的最低 Node;
目前各发布包没有 engines.node,不能据此抬高消费者门槛或宣称更早 Node 不受支持。
## 实现与维护
package-check 仍在标准 Node 完成构建、Yarn pack、隔离安装、五个旧类型模式和八个
精确类型模式。报告增加真实源码 SHA、Yarn 版本/执行路径与发布基线成员指纹,保留
已校验的 published-artifacts。候选输出保持原路径、内容与 browser-artifacts 格式。
scripts/package-runtime.mjs 只依赖 Node 内置模块,接管原 package-consumer 的运行时
夹具装配;原导出继续转发,类型编译器留在 package-consumer。新增入口在选择的精确
Node 中,读取同一 package-check 报告、校验 HEAD 和 tarball 摘要,使用记录的 Yarn
1.22.22 在仓库外临时目录 offline/frozen 安装同一 tarball,核对锁不变、逐文件字节、
非 workspace 链接,再执行 require/ESM、legacy/runtime/global/AMD、SSR、语言和
Emitter 契约。真实 child Node 版本写入结果;不会悄悄回退到构建 Node。
发布基线也在该 Node 执行。默认值和 API 形状在同一环境比较;历史行为与候选分别报告。
每次消费生成 runtime-node-<version>.json、安装日志,失败时记录错误及 stdout/stderr,
finally 删除经过根路径校验的临时目录。配置/来源预检的早期失败由 workflow tee 日志
保存;尚未创建运行报告时不会伪造成功报告。旧输出无新增来源字段需重新 test:package。
三个系统的 browser-smoke 在打包后依次切到固定 Node 20/22、运行消费者,最后恢复
.node-version 并重装消费;浏览器工具继续使用标准 Node。工作流校验保护版本、执行
顺序、无条件步骤和恢复动作。每个 Node 复用同一构建,不重新生成候选。
新入口 yarn test:package:runtime 在标准环境运行;其他版本显式执行
node scripts/package-runtime.mjs --expected-node <exact version>。需要先运行 yarn test:package。
新测试加入 test:node 与 test:ci;现有构建/类型/包消费入口保留。没有新增 npm 依赖或锁变化。
## 实际发现:CORE-25
Node 20.19.0 的首轮在实际发布 5.4.0 的静态 option getter 触发
ReferenceError: navigator is not defined。当前候选默认值中的 navigator?.language
同样不能保护未声明的全局变量。较新 Node 提供 navigator,原来的测试因此漏检。
现在夹具在每个 Node 明确删除 navigator,精确记录旧/候选都抛出这个错误;然后在
相同受控 en-US navigator 下比较默认值,并恢复原全局描述符。这仅是区分两个测试
场景,不是修复生产代码,也不代表真实浏览器语言验收。新增 CORE-DEFAULTS-SSR-01
风险和 CORE-25 修复任务,REVIEW-01 依赖该任务。候选 knownRuntimeBlockers=1,
严格 test:package:release 因该问题失败;常规兼容测试中的历史失败观察不等于发布就绪。
## 验证与后续
具体版本、二进制/产物摘要、三个运行结果、初始失败、严格发布反例和最终本地 CI
见 [证据](../baselines/node-consumer-validation.json)。新自动化反例覆盖文件篡改、
路径穿越、workspace 链接、错误 Node、过期源码、遗漏消费者与错误恢复运行时。
本批没有播放器生产代码或公开类型改动,不需要生成或提交库产物;真实打包在忽略
缓存的独立快照中执行。没有新浏览器播放或远端 Actions 执行。每个系统的实际运行、
更早消费者范围、最低工具链干净安装、全插件安装矩阵及类型/设备全组合仍需补证。
下一步独立修复 CORE-25,并重跑这三个 Node 和浏览器语言默认值;CI-01 不标完成。
回退本批恢复原消费者装配和 workflow,保留 CORE-25 的缺陷记录与待办。
Node 本地二进制仅下载到忽略缓存,执行前按官方发布摘要核验:
[20.19.0](https://nodejs.org/download/release/v20.19.0/SHASUMS256.txt)、
[22.12.0](https://nodejs.org/download/release/v22.12.0/SHASUMS256.txt)。旧版本用于隔离兼容测试,
没有替换标准 Node 或更改包管理器。提交主题包含 [CI-01];没有推送、部署或发布。
最终本地验证:完整 ci:check 2429 项(2031 单元、27 工程、371 基线),
定向 test:ci 44 项、元数据 9 项、actionlint 1.7.12 与定向只读 ESLint 全部通过。
严格发布反例按预期 exit 1;并未清除 CORE-25。
+9 -4
View File
@@ -19,7 +19,8 @@
| `yarn check:contracts --report` | 校验12类契约/22包归属、版本及报告对应;--write更新静态表,详见 [维护说明](contract-coverage.md) | | `yarn check:contracts --report` | 校验12类契约/22包归属、版本及报告对应;--write更新静态表,详见 [维护说明](contract-coverage.md) |
| `yarn ci:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、类型、Node 和基线测试;允许写忽略缓存,不修改源码 | | `yarn ci:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、类型、Node 和基线测试;允许写忽略缓存,不修改源码 |
| `yarn check:ci` | 只读校验实际工作流的完整系统矩阵、安装、缓存、报告和最终检查;已接入 ci:check | | `yarn check:ci` | 只读校验实际工作流的完整系统矩阵、安装、缓存、报告和最终检查;已接入 ci:check |
| `yarn test:ci` | CI 汇总真实退出码、工作流破坏反例与全包影响分析,共 37 项 | | `yarn test:ci` | CI 汇总退出码、工作流/影响分析反例与隔离运行时校验,共 44 项 |
| `yarn test:package:runtime` | 标准 Node 重装同一已检查 tarball;须先运行 test:package,其他 Node 使用显式 --expected-node |
| `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 | | `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
| `yarn check:impact --report` | 读取实际依赖/验证关系和Git变更,核对workflow必需命令,写CI影响报告;已接入ci:check,见[影响映射](impact-analysis.md) | | `yarn check:impact --report` | 读取实际依赖/验证关系和Git变更,核对workflow必需命令,写CI影响报告;已接入ci:check,见[影响映射](impact-analysis.md) |
| `yarn build:all` | 保留旧入口,执行 ci:build 后只读 lint | | `yarn build:all` | 保留旧入口,执行 ci:build 后只读 lint |
@@ -38,10 +39,12 @@ Actions 固定完整 SHA,Node 来自 .node-version,Yarn 固定 1.22.22;安
| --- | --- | --- | --- | | --- | --- | --- | --- |
| checks | Linux、Windows | ci:check、ci:build;Linux 额外执行 actionlint | 45 分钟 | | checks | Linux、Windows | ci:check、ci:build;Linux 额外执行 actionlint | 45 分钟 |
| coverage | Linux、Windows | 既有源码映射与生命周期覆盖检查 | 15 分钟 | | coverage | Linux、Windows | 既有源码映射与生命周期覆盖检查 | 15 分钟 |
| browser-smoke | Linux、Windows、macOS | 实际 core/chapter 安装产物、Chromium/Firefox/WebKit、iframe history、性能 | 60 分钟 | | browser-smoke | Linux、Windows、macOS | 同一 core/chapter tarball 的 Node 20/22/24 消费,三浏览器、iframe history、性能 | 60 分钟 |
| CI result | Linux | 汇总以上三个作业组,所有结果必须为 success | 5 分钟 | | CI result | Linux | 汇总以上三个作业组,所有结果必须为 success | 5 分钟 |
Node 均使用 .node-version 的 24.21.0。TS 5.9.3、4.3.5 和迁移运行时兼容 5.1.6 构建与浏览器工具使用 .node-version 的 24.21.0。安装运行时消费分别使用固定 20.19.0、
22.12.0 和标准 Node,不重新构建候选。它们是根工具 engines 的边界测试点,发布包没有
声明 Node 下限;更早 Node 和最低工具链干净安装仍需验证。TS 5.9.3、4.3.5 和迁移运行时兼容 5.1.6
继续由既有类型脚本按各自适用范围运行,不代表最低 Node 或所有包/TS 组合已验收。 继续由既有类型脚本按各自适用范围运行,不代表最低 Node 或所有包/TS 组合已验收。
矩阵不设置 fail-fast,单个系统失败后仍尽量收集其他系统证据;显式 bash 保留 tee 矩阵不设置 fail-fast,单个系统失败后仍尽量收集其他系统证据;显式 bash 保留 tee
上游命令的失败退出码。影响报告继续扩大核心/共享变更到全生态,当前不缩减必需作业。 上游命令的失败退出码。影响报告继续扩大核心/共享变更到全生态,当前不缩减必需作业。
@@ -75,7 +78,9 @@ summary.md 和 GitHub Job Summary,不依赖先前下载的 artifact 来判断
必须针对实际故障,不以清缓存代替修复锁或构建问题。 必须针对实际故障,不以清缓存代替修复锁或构建问题。
本地验证与指纹见 [CI-01 记录](changes/2026-09-13-CI-01-matrix-summary.md)。 本地验证与指纹见 [CI-01 记录](changes/2026-09-13-CI-01-matrix-summary.md)。
最低 Node 消费/工具环境、全生态安装矩阵和有证据的影响调度仍待 CI-01;CI-04 负责 三个 Node 的 Windows 安装证据及新发现见 [消费者记录](changes/2026-09-13-CI-01-node-consumers.md)。
CORE-25 的无 navigator 默认选项缺陷尚未修复,严格 test:package:release 会因此失败。
更早 Node 消费/最低工具环境、全生态安装矩阵和有证据的影响调度仍待 CI-01;CI-04 负责
各系统远端运行、冷热缓存、失败/取消演练及 required check 设置。没有新增远端通过证据。 各系统远端运行、冷热缓存、失败/取消演练及 required check 设置。没有新增远端通过证据。
## Pages 隔离与启用条件 ## Pages 隔离与启用条件
+6
View File
@@ -52,6 +52,12 @@ ENG-PERF-01/02 的后续核心性能工作归 MOD-03,并在 REVIEW-01 重新
## 仍需全项目完成的范围 ## 仍需全项目完成的范围
CI-01 后续 Node 20 安装消费发现 CORE-25:静态 option 在没有 navigator 时抛出
ReferenceError,发布 5.4.0 和当前候选均复现。较新 Node 的全局 navigator 掩盖了它。
已新增独立修复任务与 REVIEW-01 前置,并使严格 package release 检查阻断。
本核心阶段的历史完成记录不代表这个后续发现已修复,见
[Node 消费者记录](changes/2026-09-13-CI-01-node-consumers.md)。
- 各 PKG-*-01/05/06:完整发布基线、支持范围旧核心/新插件、新核心/旧插件、SDK/组合/分发; - 各 PKG-*-01/05/06:完整发布基线、支持范围旧核心/新插件、新核心/旧插件、SDK/组合/分发;
chapter 的本轮四组合不能代替所有包,也不能代替它自己的最终进度/质量/全屏组合任务。 chapter 的本轮四组合不能代替所有包,也不能代替它自己的最终进度/质量/全屏组合任务。
- EX-01/02/03 与 SITE:React/Vue、全部 demo/在线编辑器及远端资源;核心 Monaco 场景不是全站验收。 - EX-01/02/03 与 SITE:React/Vue、全部 demo/在线编辑器及远端资源;核心 Monaco 场景不是全站验收。
+7 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。 > 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 223 项,范围 22 个包及工作区/示例。 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 224 项,范围 22 个包及工作区/示例。
状态:todo 86 / doing 16 / blocked 0 / done 121 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 状态:todo 87 / doing 16 / blocked 0 / done 121 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -14,7 +14,7 @@
| 包 | 基线版本 | 任务 | | 包 | 基线版本 | 任务 |
| --- | --- | --- | | --- | --- | --- |
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24 | | artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24, CORE-25 |
| artplayer-plugin-ads | 2.1.0 | PKG-ADS-01, PKG-ADS-02, PKG-ADS-03, PKG-ADS-04, PKG-ADS-05, PKG-ADS-06 | | artplayer-plugin-ads | 2.1.0 | PKG-ADS-01, PKG-ADS-02, PKG-ADS-03, PKG-ADS-04, PKG-ADS-05, PKG-ADS-06 |
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 | | artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 |
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06 | | artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06 |
@@ -132,6 +132,7 @@
| CORE-23 | artplayer<br>检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | done | | CORE-23 | artplayer<br>检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | done |
| CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10, ENG-11 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | done | | CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10, ENG-11 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | done |
| CORE-24 | artplayer<br>修复连续切源的播放意图继承 | CORE-22, PKG-AUDIO-04 | 连续切源保留原播放意图,同时尊重显式暂停与最新来源,回归旧/新 audio | 真实暂停状态的受控测试、重入/取消/失败/销毁、三引擎和 main/legacy 产物验证通过;不恢复过期来源的公开副作用 | H | done | | CORE-24 | artplayer<br>修复连续切源的播放意图继承 | CORE-22, PKG-AUDIO-04 | 连续切源保留原播放意图,同时尊重显式暂停与最新来源,回归旧/新 audio | 真实暂停状态的受控测试、重入/取消/失败/销毁、三引擎和 main/legacy 产物验证通过;不恢复过期来源的公开副作用 | H | done |
| CORE-25 | artplayer<br>修复无 navigator 环境读取默认选项 | CORE-22 | 安全读取默认语言并保留浏览器已有默认值;实际 Node 与发布产物正反例 | 记录旧发布版 ReferenceError;候选在无 navigator 时可读默认选项,浏览器语言和公开类型不回归;不以补全局对象隐藏错误 | M | todo |
## 5 包迁移:artplayer-plugin-chapter ## 5 包迁移:artplayer-plugin-chapter
@@ -405,7 +406,7 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| REVIEW-01 | workspace<br>第一轮全项目架构与兼容性复盘 | REL-02, CORE-22, SITE-04, DOC-08, MOD-01, MOD-02, MOD-03 | 22 包结构、类型、旧 API/路径、测试盲区和维护文档的逐包报告 | 本轮阻断项由独立修复任务关闭并复测;环境缺口转交下一轮,不宣称已发布就绪 | H | todo | | REVIEW-01 | workspace<br>第一轮全项目架构与兼容性复盘 | REL-02, CORE-22, SITE-04, DOC-08, MOD-01, MOD-02, MOD-03, CORE-25 | 22 包结构、类型、旧 API/路径、测试盲区和维护文档的逐包报告 | 本轮阻断项由独立修复任务关闭并复测;环境缺口转交下一轮,不宣称已发布就绪 | H | todo |
| REVIEW-02 | workspace<br>第二轮真实浏览器与生态集成复盘 | REVIEW-01, REL-03 | Chrome 交互、自动浏览器、新旧组合、真机/SDK/性能/资源的全范围报告 | 所需环境证据齐全,前轮修复再次核对;本轮阻断项关闭,无关 mock 不替代真实验收 | H | todo | | REVIEW-02 | workspace<br>第二轮真实浏览器与生态集成复盘 | REVIEW-01, REL-03 | Chrome 交互、自动浏览器、新旧组合、真机/SDK/性能/资源的全范围报告 | 所需环境证据齐全,前轮修复再次核对;本轮阻断项关闭,无关 mock 不替代真实验收 | H | todo |
| REVIEW-03 | workspace<br>第三轮 npm 候选内容与发布准备复盘 | REVIEW-02, REL-04, CI-04 | 实际候选 integrity、干净安装构建/消费者、入口/许可/版本/tag/回退及前轮证据汇总 | 最终候选完整检查通过,发布阻断项为零;准备可审阅的批次报告,不自动执行 publish;逐包核对 major 目标,不能用大版本豁免旧 API 兼容 | H | todo | | REVIEW-03 | workspace<br>第三轮 npm 候选内容与发布准备复盘 | REVIEW-02, REL-04, CI-04 | 实际候选 integrity、干净安装构建/消费者、入口/许可/版本/tag/回退及前轮证据汇总 | 最终候选完整检查通过,发布阻断项为零;准备可审阅的批次报告,不自动执行 publish;逐包核对 major 目标,不能用大版本豁免旧 API 兼容 | H | todo |
@@ -454,7 +455,7 @@
- ENG-09: [记录](changes/2026-09-12-ENG-09-integration.md) [记录](baselines/engineering-integration.json) - ENG-09: [记录](changes/2026-09-12-ENG-09-integration.md) [记录](baselines/engineering-integration.json)
- ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md) - ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md)
- ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md) - ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md)
- CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) - CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) [记录](changes/2026-09-13-CI-01-node-consumers.md) [记录](baselines/node-consumer-validation.json)
- PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json) - PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json)
- CORE-01: [记录](changes/2026-09-10-CORE-01-typed-utils.md) [记录](baselines/core-utils-validation.json) - CORE-01: [记录](changes/2026-09-10-CORE-01-typed-utils.md) [记录](baselines/core-utils-validation.json)
- CORE-02: [记录](changes/2026-09-11-CORE-02-typed-emitter.md) [记录](baselines/emitter-validation.json) - CORE-02: [记录](changes/2026-09-11-CORE-02-typed-emitter.md) [记录](baselines/emitter-validation.json)
@@ -555,3 +556,4 @@
- PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json) - PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json)
- PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-public-types.md) [记录](baselines/thumbnail-public-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-emitter.md) [记录](baselines/thumbnail-emitter-validation.json) - PKG-TOOL-THUMB-04: [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-runtime-types.md) [记录](baselines/thumbnail-runtime-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-public-types.md) [记录](baselines/thumbnail-public-types-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-04-emitter.md) [记录](baselines/thumbnail-emitter-validation.json)
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) - PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
- CORE-25: [记录](changes/2026-09-13-CI-01-node-consumers.md)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据 # 进度与证据
## CI-01 Node 消费者矩阵检查点(doing)
标准 Node 24.21.0 构建同一 core/chapter tarball,20.19.0/22.12.0/24.21.0 实际
offline/frozen 安装逐成员验证,每个运行 36 项候选与 31 项发布观察;五个旧类型和
八个精确类型模式通过。三个系统 workflow 已接入精确运行时切换,浏览器前恢复标准 Node。
首次 Node 20 发现无 navigator 读取静态 option 抛错,旧与候选均复现。新增 CORE-25
与 CORE-DEFAULTS-SSR-01,并加入 REVIEW-01 前置;严格 package:release 已验证因此失败。
兼容检查成功包括明确的未修复缺陷观察,不能作为发布成功;下一步优先独立修复 CORE-25。
完整本地 CI 2429 项通过(2031 单元、27 工程、371 基线),定向 44 项、元数据 9 项,
actionlint/定向 lint 通过。无生产源码/公开类型/依赖/版本改动,无新浏览器或远端运行。
见[变更](changes/2026-09-13-CI-01-node-consumers.md)和[证据](baselines/node-consumer-validation.json)。
224 项:121 done、16 doing、87 todo;201 风险。CI-01 的更早 Node/最低工具环境、
全生态安装/影响调度及 CI-04 远端仍待验收。独立本地检查点,不推送、不发布。
## CI-01 系统矩阵、缓存与结果汇总检查点(doing) ## CI-01 系统矩阵、缓存与结果汇总检查点(doing)
checks 扩为 Linux/Windows,浏览器扩为 Linux/Windows/macOS;coverage 保留双系统。 checks 扩为 Linux/Windows,浏览器扩为 Linux/Windows/macOS;coverage 保留双系统。
+1
View File
@@ -204,3 +204,4 @@
| MULTI-SUB-ENTITY-01 | open / 已复现 | Vendored cue entity decoding retains semicolons after lt, gt and amp entities | PKG-MULTI-SUB-05 | | MULTI-SUB-ENTITY-01 | open / 已复现 | Vendored cue entity decoding retains semicolons after lt, gt and amp entities | PKG-MULTI-SUB-05 |
| JASSUB-TYPE-01 | open / 已复现 | JASSUB declared required URLs, Promise methods and resize parameter order differ from actual historical behavior | PKG-JASSUB-04 | | JASSUB-TYPE-01 | open / 已复现 | JASSUB declared required URLs, Promise methods and resize parameter order differ from actual historical behavior | PKG-JASSUB-04 |
| JASSUB-EXPORT-01 | open / 已复现 | JASSUB historical CommonJS object.default changes to a direct factory in 1.1.0 | PKG-JASSUB-04, PKG-JASSUB-06 | | JASSUB-EXPORT-01 | open / 已复现 | JASSUB historical CommonJS object.default changes to a direct factory in 1.1.0 | PKG-JASSUB-04, PKG-JASSUB-06 |
| CORE-DEFAULTS-SSR-01 | open / 已复现 | Reading static defaults throws when navigator is absent | CORE-25 |
+18
View File
@@ -4496,6 +4496,24 @@
], ],
"compatibleResolution": "Preserve actual historical JS default/direct forms and validate their declared module views independently.", "compatibleResolution": "Preserve actual historical JS default/direct forms and validate their declared module views independently.",
"closureCriteria": "Installed CJS/ESM/global/legacy consumers and public types preserve the supported historical forms." "closureCriteria": "Installed CJS/ESM/global/legacy consumers and public types preserve the supported historical forms."
},
{
"id": "CORE-DEFAULTS-SSR-01",
"title": "Reading static defaults throws when navigator is absent",
"confirmation": "reproduced",
"status": "open",
"owners": [
"CORE-25"
],
"evidence": [
"test/package/runtime.cjs",
"packages/artplayer/src/option/defaults.ts",
"refactor/changes/2026-09-13-CI-01-node-consumers.md",
"refactor/baselines/node-consumer-validation.json"
],
"compatibleResolution": "Read navigator through a guarded global lookup while preserving browser language defaults and public declaration shape.",
"closureCriteria": "Published failure remains explicit; source and actual installed candidate defaults work without navigator on selected Node versions and browser language behavior stays unchanged.",
"workspaceState": "Node 20.19.0 actual installed probe fails with ReferenceError: navigator is not defined. Published 5.4.0 and candidate 5.4.1 both reproduce under explicitly absent navigator on every selected runtime. CI-01 records this known failure, then compares default values under the same controlled en-US navigator; this is not a production fix."
} }
] ]
} }
+16
View File
@@ -68,6 +68,22 @@ export function validateCIWorkflow(source) {
} }
const browser = workflow.jobs['browser-smoke'] const browser = workflow.jobs['browser-smoke']
assert(browser.steps.some(step => step.run?.startsWith('yarn test:browser:install --with-deps') && !Object.hasOwn(step, 'if')), 'Browser dependencies must install even on cache hits') assert(browser.steps.some(step => step.run?.startsWith('yarn test:browser:install --with-deps') && !Object.hasOwn(step, 'if')), 'Browser dependencies must install even on cache hits')
let consumerIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package'))
for (const [id, version, command] of [
['consumer-node-20', '20.19.0', 'node scripts/package-runtime.mjs --expected-node 20.19.0 2>&1 | tee refactor/.cache/ci/consumer-node-20.log'],
['consumer-node-22', '22.12.0', 'node scripts/package-runtime.mjs --expected-node 22.12.0 2>&1 | tee refactor/.cache/ci/consumer-node-22.log'],
['restore-canonical-node', null, 'node scripts/package-runtime.mjs --canonical 2>&1 | tee refactor/.cache/ci/consumer-node-canonical.log'],
]) {
const index = browser.steps.findIndex(step => step.id === id)
assert(index > consumerIndex, 'Build once before each ordered consumer runtime switch')
const step = browser.steps[index]
assert(step.uses?.startsWith('actions/setup-node@') && !Object.hasOwn(step, 'if'), 'Consumer Node setup cannot be skipped')
assert.deepEqual(step.with, version ? { 'node-version': version, 'package-manager-cache': false } : { 'node-version-file': '.node-version', 'package-manager-cache': false }, 'Use exact consumer versions and restore the canonical browser runtime')
const probe = browser.steps[index + 1]
assert(probe?.run === command && !Object.hasOwn(probe, 'if'), 'Run the installed consumer on the selected Node')
consumerIndex = index + 1
}
assert(browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser ')) > consumerIndex, 'Browser tools must run after canonical Node restoration')
const pages = workflow.jobs.checks.steps.find(step => step.uses?.startsWith('actions/upload-pages-artifact@')) 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') assert.equal(pages?.if, 'inputs.pages-artifact && github.ref == \'refs/heads/master\' && matrix.os == \'ubuntu-latest\'', 'Only one trusted matrix leg can prepare Pages')
return { jobs: requiredJobs, systems, summary: summary.name } return { jobs: requiredJobs, systems, summary: summary.name }
+3
View File
@@ -31,6 +31,9 @@ for (const [name, mutate] of [
['conditional frozen install', w => w.jobs.checks.steps.find(s => s.run?.includes('yarn install --frozen-lockfile')).if = 'false'], ['conditional frozen install', w => w.jobs.checks.steps.find(s => s.run?.includes('yarn install --frozen-lockfile')).if = 'false'],
['commented frozen install', w => w.jobs.checks.steps.find(s => s.run?.includes('yarn install --frozen-lockfile')).run = '# yarn install --frozen-lockfile --non-interactive'], ['commented frozen install', w => w.jobs.checks.steps.find(s => s.run?.includes('yarn install --frozen-lockfile')).run = '# yarn install --frozen-lockfile --non-interactive'],
['conditional Node setup', w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/setup-node@')).if = 'false'], ['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'],
['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 }],
['Pages uploaded by both matrix legs', w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/upload-pages-artifact@')).if = 'inputs.pages-artifact && github.ref == \'refs/heads/master\''], ['Pages uploaded by both matrix legs', w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/upload-pages-artifact@')).if = 'inputs.pages-artifact && github.ref == \'refs/heads/master\''],
]) { ]) {
test(`CI rejects ${name}`, () => { test(`CI rejects ${name}`, () => {
+23 -2
View File
@@ -756,7 +756,9 @@
"evidence": [ "evidence": [
"ci-setup.md", "ci-setup.md",
"changes/2026-09-13-CI-01-matrix-summary.md", "changes/2026-09-13-CI-01-matrix-summary.md",
"baselines/ci-matrix-validation.json" "baselines/ci-matrix-validation.json",
"changes/2026-09-13-CI-01-node-consumers.md",
"baselines/node-consumer-validation.json"
] ]
}, },
{ {
@@ -4362,7 +4364,8 @@
"DOC-08", "DOC-08",
"MOD-01", "MOD-01",
"MOD-02", "MOD-02",
"MOD-03" "MOD-03",
"CORE-25"
], ],
"status": "todo", "status": "todo",
"risk": "H", "risk": "H",
@@ -4478,6 +4481,24 @@
"factory-compatibility-decision.md", "factory-compatibility-decision.md",
"changes/2026-09-12-PKG-FACTORY-01-decision.md" "changes/2026-09-12-PKG-FACTORY-01-decision.md"
] ]
},
{
"id": "CORE-25",
"phase": "3-4 核心迁移",
"title": "修复无 navigator 环境读取默认选项",
"scope": [
"artplayer"
],
"dependsOn": [
"CORE-22"
],
"status": "todo",
"risk": "M",
"deliverable": "安全读取默认语言并保留浏览器已有默认值;实际 Node 与发布产物正反例",
"acceptance": "记录旧发布版 ReferenceError;候选在无 navigator 时可读默认选项,浏览器语言和公开类型不回归;不以补全局对象隐藏错误",
"evidence": [
"changes/2026-09-13-CI-01-node-consumers.md"
]
} }
] ]
} }
+5 -1
View File
@@ -9,7 +9,11 @@
3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、20 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。 3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、20 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。
4. 执行 `yarn test:playback`、`yarn test:dash-control`、`yarn build all` 和 `yarn workspace artplayer-vitepress build`。ENG-02 已拆分只读 lint 与 lint:fix;PR/主线入口和独立 Pages 流程见 ci-setup.md。 4. 执行 `yarn test:playback`、`yarn test:dash-control`、`yarn build all` 和 `yarn workspace artplayer-vitepress build`。ENG-02 已拆分只读 lint 与 lint:fix;PR/主线入口和独立 Pages 流程见 ci-setup.md。
私有根包最低 Node 为 ^20.19.0 || >=22.12.0,与原本使用的 Vite 7 一致。本轮验证 Node 24.21.0,其他版本矩阵由 CI-01 接续,不能宣称所有最低环境已经通过。 私有根包最低 Node 为 ^20.19.0 || >=22.12.0,与原本使用的 Vite 7 一致。标准工具链验证
使用 Node 24.21.0;CI-01 已在 Windows 的 20.19.0/22.12.0/24.21.0 上重装消费相同
core/chapter tarball,见 [Node 记录](changes/2026-09-13-CI-01-node-consumers.md)。
这不是在最低 Node 上完成仓库工具链的干净安装/构建测试,也不是发布包的最低 Node 声明。
CORE-25 默认选项缺陷仍在严格发布检查中阻断;完整工具与消费范围由 CI-01 继续验证。
## 锁文件和依赖维护 ## 锁文件和依赖维护
+7 -2
View File
@@ -88,6 +88,7 @@ export async function checkPackages({ release = false } = {}) {
const installed = consumerDirectory() const installed = consumerDirectory()
try { try {
const oldRuntime = runtimeConsumer(baseline.dir, { baseline: true }) const oldRuntime = runtimeConsumer(baseline.dir, { baseline: true })
fs.cpSync(path.join(baseline.dir, 'node_modules'), path.join(output, 'published-artifacts'), { recursive: true })
const packages = [] const packages = []
for (const name of names) { for (const name of names) {
const archive = path.join(output, `${name}.tgz`) const archive = path.join(output, `${name}.tgz`)
@@ -119,13 +120,17 @@ export async function checkPackages({ release = false } = {}) {
assert.deepEqual(runtime.observations.api, oldRuntime.observations.api, 'Published API shape/defaults changed') assert.deepEqual(runtime.observations.api, oldRuntime.observations.api, 'Published API shape/defaults changed')
const types = typeConsumers(installed) const types = typeConsumers(installed)
const preciseTypes = typeConsumers(installed, { precise: true }) const preciseTypes = typeConsumers(installed, { precise: true })
const report = { task: 'ENG-07', capturedAt: new Date().toISOString(), node: process.versions.node, packages, runtime, publishedRuntime: oldRuntime, types, preciseTypes, knownTypeBlockers: [...types, ...preciseTypes].reduce((sum, result) => sum + result.diagnostics.length, 0) } 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) }
writeJson(path.join(output, 'report.json'), report) writeJson(path.join(output, 'report.json'), report)
writeJson(path.join(output, 'browser-artifacts.json'), artifacts) writeJson(path.join(output, 'browser-artifacts.json'), artifacts)
writeJson(path.join(parent, 'latest.json'), { output: path.relative(workspace, output).replaceAll('\\', '/') }) 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}`) 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 (release) if (release) {
assert.equal(report.knownTypeBlockers, 0, 'Known type blockers remain; this candidate is not release-ready') 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')
}
return report return report
} }
finally { finally {
+2 -7
View File
@@ -9,6 +9,8 @@ import ts from 'typescript'
import compat from 'typescript-compat' import compat from 'typescript-compat'
import runtimeCompat from 'typescript-runtime-compat' import runtimeCompat from 'typescript-runtime-compat'
export { runtimeConsumer } from './package-runtime.mjs'
export const workspace = fileURLToPath(new URL('../', import.meta.url)) export const workspace = fileURLToPath(new URL('../', import.meta.url))
export const names = ['artplayer', 'artplayer-plugin-chapter'] export const names = ['artplayer', 'artplayer-plugin-chapter']
export const readJson = file => JSON.parse(fs.readFileSync(file, 'utf8')) export const readJson = file => JSON.parse(fs.readFileSync(file, 'utf8'))
@@ -27,13 +29,6 @@ export function removeConsumer(dir) {
fs.rmSync(dir, { recursive: true, force: true }) fs.rmSync(dir, { recursive: true, force: true })
} }
export function runtimeConsumer(dir, { baseline = false } = {}) {
fs.copyFileSync(path.join(workspace, 'test/package/runtime.cjs'), path.join(dir, 'runtime.cjs'))
fs.copyFileSync(path.join(workspace, 'test/contracts/emitter.js'), path.join(dir, 'emitter.mjs'))
writeJson(path.join(dir, 'expected.json'), { version: readJson(path.join(dir, 'node_modules/artplayer/package.json')).version, baseline })
return JSON.parse(run(['runtime.cjs'], dir))
}
export function typeConsumers(dir, { precise = false } = {}) { export function typeConsumers(dir, { precise = false } = {}) {
const results = [] const results = []
const matrix = precise const matrix = precise
+98
View File
@@ -0,0 +1,98 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const workspace = fileURLToPath(new URL('../', import.meta.url))
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'))
const write = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
const hash = file => createHash('sha256').update(fs.readFileSync(file)).digest('hex')
const run = (args, cwd) => execFileSync(process.execPath, args, { cwd, encoding: 'utf8', timeout: 120000, maxBuffer: 8 * 1024 * 1024, windowsHide: true, env: { ...process.env, NODE_PATH: '' } })
export function runtimeConsumer(dir, { baseline = false } = {}) {
fs.copyFileSync(path.join(workspace, 'test/package/runtime.cjs'), path.join(dir, 'runtime.cjs'))
fs.copyFileSync(path.join(workspace, 'test/contracts/emitter.js'), path.join(dir, 'emitter.mjs'))
write(path.join(dir, 'expected.json'), { version: read(path.join(dir, 'node_modules/artplayer/package.json')).version, baseline })
const result = JSON.parse(run(['runtime.cjs'], dir))
assert.equal(result.node, process.versions.node, 'Consumer must run on the selected Node')
return result
}
export function verifyInstalledFiles(directory, packages) {
assert.deepEqual(packages.map(pkg => pkg.name).sort(), ['artplayer', 'artplayer-plugin-chapter'], 'Runtime probe scope must match the actual package fixture')
for (const pkg of packages) {
const root = path.join(directory, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Consumer cannot resolve workspace links')
for (const [member, digest] of Object.entries(pkg.files)) {
assert(member.startsWith('package/') && !member.includes('\\') && !member.split('/').includes('..'), 'Unsafe package member')
assert.equal(hash(path.join(root, member.slice(8))), digest, `Installed package bytes differ: ${pkg.name}/${member}`)
}
assert.equal(read(path.join(root, 'package.json')).version, pkg.version)
}
}
export function checkPackageRuntime({ expectedNode, output } = {}) {
assert.equal(process.versions.node, expectedNode, 'Select the requested exact Node before running consumers')
const parent = fs.realpathSync(path.join(workspace, 'refactor/.cache/packages'))
const selected = output || read(path.join(parent, 'latest.json')).output
const directory = fs.realpathSync(path.resolve(workspace, selected))
assert.equal(path.dirname(directory), parent, 'Use a direct package-check run directory')
assert(path.basename(directory).startsWith('run-'))
const built = read(path.join(directory, 'report.json'))
assert.equal(built.source, execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8' }).trim(), 'Package build belongs to another commit')
assert.equal(built.toolchain?.yarn, '1.22.22', 'Use the recorded pinned package manager')
const yarn = built.toolchain.yarnPath
assert.equal(run([yarn, '--version'], workspace).trim(), '1.22.22')
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'artplayer-consumer-'))
const report = { task: 'CI-01', node: process.versions.node, source: built.source, buildNode: built.node, passed: false, packages: built.packages.map(pkg => ({ name: pkg.name, version: pkg.version, sha256: pkg.sha256 })) }
const prefix = path.join(directory, `runtime-node-${expectedNode}`)
try {
const baseline = path.join(temporary, 'published')
fs.mkdirSync(baseline)
fs.cpSync(path.join(directory, 'published-artifacts'), path.join(baseline, 'node_modules'), { recursive: true })
verifyInstalledFiles(baseline, built.publishedPackages)
report.publishedRuntime = runtimeConsumer(baseline, { baseline: true })
const dependencies = {}
for (const pkg of built.packages) {
assert.equal(path.basename(pkg.archive), pkg.archive, 'Archive must stay in the build directory')
const archive = path.join(directory, pkg.archive)
assert.equal(hash(archive), pkg.sha256, 'Candidate archive changed after package checks')
dependencies[pkg.name] = `file:${archive.replaceAll('\\', '/')}`
}
write(path.join(temporary, 'package.json'), { name: 'artplayer-isolated-consumer', private: true, dependencies })
const lock = fs.readFileSync(path.join(directory, 'consumer-yarn.lock'))
fs.writeFileSync(path.join(temporary, 'yarn.lock'), lock)
fs.writeFileSync(`${prefix}-install.log`, run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], temporary))
assert.deepEqual(fs.readFileSync(path.join(temporary, 'yarn.lock')), lock, 'Frozen consumer lock changed')
verifyInstalledFiles(temporary, built.packages)
report.runtime = runtimeConsumer(temporary)
assert.deepEqual(report.runtime.observations.api, report.publishedRuntime.observations.api, 'Published API shape/defaults changed on this Node')
assert.deepEqual(report.runtime.checks, built.runtime.checks, 'Runtime check coverage changed across Node versions')
report.knownRuntimeBlockers = report.runtime.observations.defaultsWithoutNavigator.resolved ? 0 : 1
report.passed = true
return report
}
catch (error) {
report.error = { name: error.name, message: error.message }
fs.writeFileSync(`${prefix}-failure.log`, `${error.stack}\n${error.stdout || ''}\n${error.stderr || ''}`)
throw error
}
finally {
write(`${prefix}.json`, report)
assert.equal(path.dirname(fs.realpathSync(temporary)), fs.realpathSync(os.tmpdir()))
assert(path.basename(temporary).startsWith('artplayer-consumer-'))
fs.rmSync(temporary, { recursive: true, force: true })
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const args = process.argv.slice(2)
assert((args.length === 1 && args[0] === '--canonical') || (args.length === 2 && args[0] === '--expected-node' && /^\d+\.\d+\.\d+$/.test(args[1])), 'Use --canonical or --expected-node <exact version>')
const expectedNode = args[0] === '--canonical' ? fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim() : args[1]
const result = checkPackageRuntime({ expectedNode })
console.log(`Installed runtime passed on Node ${result.node}: ${result.runtime.checks.length} candidate and ${result.publishedRuntime.checks.length} published checks`)
}
+1 -1
View File
@@ -22,7 +22,7 @@ test('Package checks reject missing files, missing wildcard exports and internal
test('Actual isolated runtime rejects removed default exports and required files', async () => { test('Actual isolated runtime rejects removed default exports and required files', async () => {
const { dir } = await publishedConsumer() const { dir } = await publishedConsumer()
try { try {
assert.equal(runtimeConsumer(dir, { baseline: true }).checks.length, 20 + Object.keys(emitterContracts).length) assert.equal(runtimeConsumer(dir, { baseline: true }).checks.length, 22 + Object.keys(emitterContracts).length)
assert.throws(() => runtimeConsumer(dir), /Command failed/) assert.throws(() => runtimeConsumer(dir), /Command failed/)
const esm = path.join(dir, 'node_modules/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.mjs') const esm = path.join(dir, 'node_modules/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.mjs')
const original = fs.readFileSync(esm) const original = fs.readFileSync(esm)
+62
View File
@@ -0,0 +1,62 @@
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 -- Verify isolated package runtime evidence and rejected inputs.
import test from 'node:test'
import { publishedConsumer } from '../scripts/package-check.mjs'
import { removeConsumer } from '../scripts/package-consumer.mjs'
import { checkPackageRuntime, runtimeConsumer, verifyInstalledFiles } from '../scripts/package-runtime.mjs'
test('Runtime evidence records the actual child Node version with published contracts', async () => {
const { dir, releases } = await publishedConsumer()
try {
verifyInstalledFiles(dir, releases)
const result = runtimeConsumer(dir, { baseline: true })
assert.equal(result.node, process.versions.node)
assert(result.checks.includes('DIST.esm'))
assert(result.checks.includes('SSR.constructor-browser-only'))
}
finally { removeConsumer(dir) }
})
test('Installed runtime verification rejects tampered bytes, traversal and workspace links', async () => {
const { dir, releases } = await publishedConsumer()
try {
const manifest = path.join(dir, 'node_modules/artplayer/package.json')
const original = fs.readFileSync(manifest)
fs.appendFileSync(manifest, '\n')
assert.throws(() => verifyInstalledFiles(dir, releases), /Installed package bytes differ/)
fs.writeFileSync(manifest, original)
const unsafe = structuredClone(releases)
unsafe[0].files['package/../escape'] = '0'.repeat(64)
assert.throws(() => verifyInstalledFiles(dir, unsafe), /Unsafe package member/)
const root = path.join(dir, 'node_modules/artplayer')
const target = path.join(dir, 'linked-artplayer')
assert(path.dirname(target) === dir && path.dirname(path.dirname(root)) === dir)
fs.renameSync(root, target)
fs.symlinkSync(target, root, process.platform === 'win32' ? 'junction' : 'dir')
assert.throws(() => verifyInstalledFiles(dir, releases), /workspace links/)
}
finally { removeConsumer(dir) }
})
test('Runtime selection cannot silently fall back to a different Node version', () => {
assert.throws(() => checkPackageRuntime({ expectedNode: '0.0.0' }), /requested exact Node/)
})
test('Runtime package selection rejects other directories and stale source commits before installing', () => {
const parent = path.resolve('refactor/.cache/packages')
fs.mkdirSync(parent, { recursive: true })
const directory = fs.mkdtempSync(path.join(parent, 'run-runtime-negative-'))
try {
fs.writeFileSync(path.join(directory, 'report.json'), JSON.stringify({ source: '0'.repeat(40) }))
assert.throws(() => checkPackageRuntime({ expectedNode: process.versions.node, output: directory }), /another commit/)
assert.throws(() => checkPackageRuntime({ expectedNode: process.versions.node, output: parent }), /direct package-check run/)
}
finally {
assert.equal(path.dirname(fs.realpathSync(directory)), fs.realpathSync(parent))
assert(path.basename(directory).startsWith('run-runtime-negative-'))
fs.rmSync(directory, { recursive: true, force: true })
}
})
+16 -1
View File
@@ -35,9 +35,24 @@ type failures; all five groups now require zero diagnostics. Exact known
diagnostics are referenced by `known-types.json` and `test/types/known-diagnostics.json`. diagnostics are referenced by `known-types.json` and `test/types/known-diagnostics.json`.
Unexpected errors and unexpectedly removed errors both require investigation. Unexpected errors and unexpectedly removed errors both require investigation.
Remove a known case when its owning task fixes it; keep frozen release evidence intact. Remove a known case when its owning task fixes it; keep frozen release evidence intact.
`yarn test:package:release` additionally rejects any remaining known type errors. `yarn test:package:release` additionally rejects remaining known type and runtime blockers.
Passing this command alone is not authorization or sufficient evidence to publish. Passing this command alone is not authorization or sufficient evidence to publish.
`scripts/package-runtime.mjs` uses only Node built-ins and can reinstall the exact checked
tarballs under a different Node. First run `yarn test:package` on the canonical toolchain,
then use the selected executable with `scripts/package-runtime.mjs --expected-node 20.19.0`
or `--expected-node 22.12.0`; `yarn test:package:runtime` verifies canonical Node.
Each run checks HEAD, archive digests, the frozen offline install, installed bytes and
the actual child version. Reports and failure logs are `runtime-node-<version>.*` in
the same package output directory. Old reports without source/toolchain fields must
be rebuilt. CI restores canonical Node before running browser tools.
CORE-25 remains an explicit defect: both published and candidate static defaults throw
without navigator. The fixture tests that exact failure on every runtime, then uses an
identical controlled language for default-value comparison and restores the global.
`knownRuntimeBlockers=1` prevents strict release success; ordinary observation tests
passing do not mean the defect is fixed. See [Node evidence](../../refactor/changes/2026-09-13-CI-01-node-consumers.md).
The optional core `artplayer/runtime` entry has eight additional strict consumer The optional core `artplayer/runtime` entry has eight additional strict consumer
groups: TS 5.1.6 and 5.9.3 each check Node10 CommonJS, NodeNext CJS/ESM and Bundler. groups: TS 5.1.6 and 5.9.3 each check Node10 CommonJS, NodeNext CJS/ESM and Bundler.
These fixtures cover accurate returns and getter/setter types, construction-stage These fixtures cover accurate returns and getter/setter types, construction-stage
+18 -2
View File
@@ -186,8 +186,24 @@ const vm = require('node:vm');
checks.push(id) checks.push(id)
} }
const shape = value => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(value)).map(([key, d]) => [key, { enumerable: d.enumerable, configurable: d.configurable, writable: d.writable, get: typeof d.get, set: typeof d.set, value: typeof d.value }])) const shape = value => Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(value)).map(([key, d]) => [key, { enumerable: d.enumerable, configurable: d.configurable, writable: d.writable, get: typeof d.get, set: typeof d.set, value: typeof d.value }]))
observations.api = { static: shape(core), prototype: shape(core.prototype), emitter: shape(core.Emitter.prototype), factory: shape(chapter), defaults: JSON.parse(JSON.stringify(core.option, (_, value) => typeof value === 'function' ? '$function' : value)) } const defaultNavigator = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
process.stdout.write(JSON.stringify({ checks, observations })) try {
assert(delete globalThis.navigator)
// CORE-25 tracks this reproduced published and candidate failure separately.
assert.throws(() => core.option, { name: 'ReferenceError', message: 'navigator is not defined' })
observations.defaultsWithoutNavigator = { name: 'ReferenceError', message: 'navigator is not defined', historical: !!expected.baseline, resolved: false }
check('SSR.defaults-missing-navigator-known-failure', true)
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: { language: 'en-US' } })
observations.api = { static: shape(core), prototype: shape(core.prototype), emitter: shape(core.Emitter.prototype), factory: shape(chapter), defaults: JSON.parse(JSON.stringify(core.option, (_, value) => typeof value === 'function' ? '$function' : value)) }
check('API.defaults-controlled-language', observations.api.defaults.lang === 'en-us')
}
finally {
if (defaultNavigator)
Object.defineProperty(globalThis, 'navigator', defaultNavigator)
else
delete globalThis.navigator
}
process.stdout.write(JSON.stringify({ node: process.versions.node, checks, observations }))
})().catch((error) => { })().catch((error) => {
console.error(error) console.error(error)
process.exitCode = 1 process.exitCode = 1