test(site): [SITE-05] enforce built links and validate online editors

This commit is contained in:
Harvey Zhao committed 2026-09-16 09:55:00 +08:00
1 parent 6b580d1667
commit fb78328c43
17 files changed
+2122 -15

No files matched your search

+7 -5
View File
@@ -45,10 +45,10 @@
"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/ecosystem-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-links.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:versions && 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",
"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:versions && 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:site-links && 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 check:site-links && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail-registration.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js test/asr-media-error.test.js test/thumbnail-cleanup-errors.test.js test/dash-cleanup-errors.test.js test/hls-example.test.js test/dash-example.test.js test/thumbnail-policy.test.js",
@@ -128,7 +128,7 @@
"check:site-assets": "node scripts/build-site-assets.mjs --check",
"typecheck:site-assets": "node node_modules/typescript/bin/tsc -p packages/artplayer-vitepress/browser/tsconfig.json --noEmit",
"check:llm": "node scripts/build-llm.js --check",
"test:site-build": "node --test test/site-build.test.js test/build-docs.test.js test/site-markdown.test.js",
"test:site-build": "node --test test/site-build.test.js test/build-docs.test.js test/site-markdown.test.js test/site-links.test.js",
"test:site-editor": "node --test test/site-editor.test.js",
"typecheck:scaffold": "node node_modules/typescript/bin/tsc -p scripts/tsconfig.scaffold.json --noEmit",
"test:scaffold": "node --test test/plugin-scaffold.test.js",
@@ -181,7 +181,8 @@
"test:release-bundle": "node --test refactor/scripts/release-bundle.test.mjs refactor/scripts/release-verify.test.mjs",
"test:ecosystem-types": "node scripts/consumers/ecosystem.ts",
"check:versions": "node refactor/scripts/version-plan.mjs --prepared",
"release:verify-bundle": "yarn check:toolchain --strict && node scripts/verify-release.mjs"
"release:verify-bundle": "yarn check:toolchain --strict && node scripts/verify-release.mjs",
"check:site-links": "node scripts/check-site-links.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -205,6 +206,7 @@
"eslint": "9.39.2",
"eslint-plugin-format": "2.0.1",
"glob": "13.0.6",
"htmlparser2": "10.1.0",
"lerna": "8.2.4",
"less": "4.5.1",
"linkedom": "0.18.13",
+6 -2
View File
@@ -75,8 +75,12 @@ Use `yarn test:site-build` for the pinned-component compilation and drift guard,
before `yarn test:browser:source document-search.spec.js document-site.spec.js`.
Browser checks cover both language URLs, pointer navigation, keyboard selection,
Escape, focus, no results, and retained document identity (no form reload).
They isolate external scripts and do not validate AdSense, physical devices,
all search-index anchors or the complete online editor; those remain SITE-05 work.
They isolate external scripts and do not validate AdSense or physical devices.
`yarn check:site-links` separately checks every built page's local href/src and
every shipped search-index target/anchor. The site-editor, site-loading and
editor declaration suites exercise the actual Monaco UI and worker. Remote
GitHub edit links for new guides require those source paths to reach `master`;
an unpublished local guide is not proof that its external edit link is available.
Tools have their own navigation group. Iframe explains the required child inject
step and serialized commit body, without treating the protocol as a sandbox or
+5 -1
View File
@@ -21292,6 +21292,10 @@
"file": "scripts/site-build/i18n.ts",
"sha256Lf": "394b8f19d795980f49cf1557b79b4708f3419689d0fa53be348b83b655d9a984"
},
{
"file": "scripts/site-build/links.ts",
"sha256Lf": "004eac68a543ef3f936b3e32bda2d7226b319e82df2ce9180b0c00eb69c576bf"
},
{
"file": "packages/artplayer-vitepress/build/markdown.ts",
"sha256Lf": "6d639e25bab97c973940661e2aac6f1034ab53ab1c016310a571ab52392819da"
@@ -21356,7 +21360,7 @@
"build:i18n": "node ./scripts/build-i18n.js",
"build:test": "node ./scripts/build-test.js",
"build:all": "yarn ci:build && yarn lint",
"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",
"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 check:site-links && yarn test:imports",
"build:types": "node scripts/build-types.mjs --write"
},
"siteManifest": {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,56 @@
# SITE-05 全站本地链接与编辑器验证
起点:`6b580d166747d2a1d460666d4259238beb851f4c`。本批加入持续可运行的链接门槛,
并验证原在线编辑器和声明;SITE-05 保持 doing,未进入正式复盘。
## 实现和依赖
新增 `scripts/site-build/links.ts` 与薄 CLI `scripts/check-site-links.mjs`。
`yarn check:site-links` 检查 docs/document 全部生成 HTML 的 href/src、本地文件、
目录 index、中文/转义锚点和历史 name 锚点,并导入实际构建的 search-data 模块
检查所有结果 URL;不是重新生成一份索引再声称发布索引正确。索引缺失/重复/结构
错误或空白会直接失败。HTML 脚本文本与 inert template 内容不作为可点击链接,
不执行页面代码;HTML base 暂不支持时明确拒绝,不静默选错相对地址。外部 URL
只列出,CI 不将整个第三方网络可用性作为确定性构建的条件。
初始 Linkedom TS 版本与 DOM lib 冲突,未加 skipLibCheck 或类型忽略。改用已有
传递依赖 htmlparser2 10.1.0 的有类型解析器,并显式声明为根 devDependency。
Yarn 只增加精确选择器,原版本/integrity 不变;没有新增传递包或播放器运行依赖。
根冻结安装锁不变,严格检查为41个工具/1473个选择器。实现地图和命令写入
scripts/site-build/README.md、站点 README、工具链、CI 与浏览器维护文档。
`ci:check` 增加提交产物检查,`ci:build` 在文档构建之后检查,失败退出非零。
三组回归覆盖实体编码、相对/目录路径、缺失资源/锚点、无效URL及异常索引。
已接入 test:site-build/test:node;未触发远端 Actions。
## 本地验证
Node24.21.0 / Yarn1.22.22:
- 67个生成页面、5766次本地引用、942条真实构建搜索结果,缺失文件/锚点均为0。
报告列出82个外部URL;不是82个已验证外部锚点。
- test:site-build 11/11,1334.8651ms;test:ci 80/80,5761.8837ms;均0失败/跳过。
- typecheck:docs-tools、修改文件只读ESLint、严格工具链、site inventory和CI配置通过。
- check:editor-types校验24份输出及当前/兼容编译器;check:site-assets校验5份产物/66路由。
- source浏览器命令包含site-editor、site-loading、editor-declarations、editor-types、
document-search及document-site。Playwright按正则匹配,实际还选中Ads/Audio/DASH/
HLS/Thumbnail的编辑器类型文件;报告记录全部72项,三引擎各24项,0失败/跳过/重试。
覆盖真实Monaco编译/运行、Ctrl-S、重复Run、语法错误保留旧播放器、CSS/脚本顺序、
拒绝storage的降级、持久设置、声明加载失败清理、迟到示例不覆盖新Run、语言路由、
嵌入代码URL/依赖转发及搜索交互。部分SDK用例有受控替代,不代表真实SDK或设备验收。
浏览器和静态结果见 [site05-links-editor.json](../baselines/site05-links-editor.json)。
媒体库源码未变,不将本批浏览器检查替代安装包、真机或EX-03全部示例矩阵。
## 外部链接缺口
首次Node直连81个去掉fragment后的外部地址:2个200、79个连接/超时错误。
检测系统代理后,用curl HEAD跟随重定向复测81个,每个一次、4并发、每请求15秒上限:
42个200、39个404,无传输失败。HTTP成功只证明本次可达,不验证远端fragment或SDK功能。
39个404全部是新增指南的GitHub master编辑源文件链接,本地源码存在,默认分支尚未
包含它们。保留准确目标,不跳转到其他页面冒充编辑当前文件,也不擅自推送/合并。
SITE-05继续doing,远端源码可用后重验;SITE-06最终交付仍依赖SITE-05和字体来源SITE-07。
共享输入变化后的21库候选刷新仍由REL-02处理;旧候选证据不代表当前共享指纹。
回退本提交移除新检查、devDependency/脚本和记录;播放器API、生成文档URL不变。
没有推送、部署、发布或启动用户保留的正式复盘。
+4
View File
@@ -1,5 +1,9 @@
# PR 检查和 Pages 操作说明
SITE-05 增加 `yarn check:site-links`:`ci:check` 校验已提交文档,`ci:build` 在重建
文档后检查生成 HTML 的 href/src、本地文件/锚点及实际搜索索引。任一缺失使入口失败。
外部 URL 只列入报告,在线编辑器和真实播放由浏览器矩阵另验;本地配置不代表远端运行。
同一20包安装映射的Windows WebKit26.6完整运行现已结束:bb2c12df3执行77文件/845项,
694通过、13失败、138能力跳过、0重试,1214.860秒。失败包括8项Audio、1项Chapter、
1项候选弹幕CPU间隔漏发和3项旧JASSUB;候选JASSUB三核心通过。见
+1 -1
View File
@@ -23,7 +23,7 @@ chapter 试点可使用 `http://localhost:8082/?libs=./uncompiled/artplayer-plug
2. 每次记录核心、插件、语言包和外部 SDK 的实际 URL、版本及产物内容标识。旧/新组合必须有意指定,不能把陈旧构建或误混版本作为候选验收证据。
3. 独立用例使用隔离测试上下文或测试专属 origin,并明确 prod/ts/code/log 状态。不要清除用户日常 Chrome 的全部存储。单独设置连续 Run 用例,检查多实例全部销毁、监听器/DOM/资源清理及重复加载依赖的影响。
4. common.js 在移动设备判断成立时转到 mobile.html;报告记录最终 URL,移动页通过不能计作桌面 Monaco 编辑器通过。
5. 选中 TS 模式不等于严格类型检查通过;当前编辑器直接 eval 内容,不能假设可以运行任意 TS 语法。类型验收仍由固定版本 tsc 和真实消费者完成。
5. TS 模式已由 Monaco worker 先检查语法并生成 JavaScript,再交给执行器;编译失败保留现有播放器。语义诊断不会自动阻止 Run,因此能够运行不等于严格类型检查通过。类型验收继续使用固定版本 tsc、Monaco 语义诊断和真实消费者。
6. 优先使用许可明确的本地样本。外部 CDN、广告、流媒体和模型请求逐项登记;网络失败与播放器回归区分。受控测试可隔离无关外部请求并记录,实际 SDK 集成保留独立验收,不能一概屏蔽后声称完整通过。
## 从演示补成可靠回归
+1 -1
View File
@@ -688,7 +688,7 @@
- SITE-HLS-01: [记录](changes/2026-09-15-SITE-HLS-01-guides.md) [记录](baselines/hls-docs-validation.json)
- SITE-DASH-01: [记录](changes/2026-09-15-SITE-DASH-01-guides.md) [记录](baselines/dash-docs-validation.json)
- SITE-04: [记录](site-content-review.md) [记录](changes/2026-09-16-SITE-04-chapter-ambilight-guides.md) [记录](baselines/site04-chapter-ambilight-guides.json) [记录](changes/2026-09-16-SITE-04-dpip-asr-guides.md) [记录](baselines/site04-dpip-asr-guides.json) [记录](changes/2026-09-16-SITE-04-auto-multi-guides.md) [记录](baselines/site04-auto-multi-guides.json) [记录](changes/2026-09-16-SITE-04-ads-vast-guides.md) [记录](baselines/site04-ads-vast-guides.json) [记录](changes/2026-09-16-SITE-04-jassub-mask-cast-guides.md) [记录](baselines/site04-jassub-mask-cast-guides.json) [记录](changes/2026-09-16-SITE-04-tools-guides.md) [记录](baselines/site04-tools-guides.json) [记录](changes/2026-09-16-SITE-04-proxies-guides.md) [记录](baselines/site04-proxies-guides.json) [记录](changes/2026-09-16-SITE-04-existing-guides.md) [记录](baselines/site04-existing-guides.json) [记录](core-content-review.json) [记录](baselines/site04-core-services.json) [记录](changes/2026-09-16-SITE-04-core-services.md) [记录](baselines/site04-template-icons.json) [记录](changes/2026-09-16-SITE-04-template-icons.md) [记录](baselines/site04-components.json) [记录](changes/2026-09-16-SITE-04-components.md) [记录](baselines/site04-setting.json) [记录](changes/2026-09-16-SITE-04-setting.md) [记录](baselines/site04-plugin-registry.json) [记录](changes/2026-09-16-SITE-04-plugin-registry.md) [记录](baselines/site04-subtitle.json) [记录](changes/2026-09-16-SITE-04-subtitle.md) [记录](baselines/site04-utils.json) [记录](changes/2026-09-16-SITE-04-utils.md) [记录](baselines/site04-globals.json) [记录](changes/2026-09-16-SITE-04-globals.md) [记录](baselines/site04-events.json) [记录](changes/2026-09-16-SITE-04-events.md) [记录](baselines/site04-options.json) [记录](changes/2026-09-16-SITE-04-options.md) [记录](baselines/site04-css.json) [记录](changes/2026-09-16-SITE-04-css.md) [记录](baselines/site04-lifecycle.json) [记录](changes/2026-09-16-SITE-04-lifecycle.md) [记录](baselines/site04-playback.json) [记录](changes/2026-09-16-SITE-04-playback.md) [记录](baselines/site04-display.json) [记录](changes/2026-09-16-SITE-04-complete.md)
- SITE-05: [记录](changes/2026-09-16-SITE-05-search.md) [记录](baselines/site05-search.json)
- SITE-05: [记录](changes/2026-09-16-SITE-05-search.md) [记录](baselines/site05-search.json) [记录](changes/2026-09-16-SITE-05-links-editor.md) [记录](baselines/site05-links-editor.json)
- SITE-07: [记录](site-inventory.md) [记录](baselines/site-provenance.json) [记录](changes/2026-09-14-SITE-07-vendor-notices.md) [记录](baselines/site-notices-checkpoint.json) [记录](baselines/site-codicons-provenance.json) [记录](baselines/site-codicons-validation.json) [记录](changes/2026-09-15-SITE-07-codicons.md) [记录](baselines/vconsole-notices-provenance.json) [记录](baselines/vconsole-notices-validation.json) [记录](changes/2026-09-15-SITE-07-vconsole-notices.md) [记录](console-modernization.md) [记录](baselines/site-console-inventory.json) [记录](baselines/site-console-validation.json) [记录](changes/2026-09-15-SITE-07-console-baseline.md) [记录](baselines/console-feed-provenance.json) [记录](changes/2026-09-15-SITE-07-console-feed-source.md) [记录](baselines/console-commonjs-provenance.json) [记录](changes/2026-09-15-SITE-07-console-commonjs.md) [记录](baselines/console-esm-provenance.json) [记录](changes/2026-09-15-SITE-07-console-esm.md) [记录](baselines/console-embedded-notices.json) [记录](baselines/console-notices-validation.json) [记录](changes/2026-09-15-SITE-07-console-notices.md) [记录](baselines/console-embedded-sources.json) [记录](baselines/console-embedded-validation.json) [记录](changes/2026-09-15-SITE-07-console-embedded-sources.md) [记录](baselines/console-derived-attribution.json) [记录](baselines/console-derived-validation.json) [记录](changes/2026-09-15-SITE-07-console-derived-attribution.md) [记录](baselines/console-stackoverflow-provenance.json) [记录](baselines/console-stackoverflow-validation.json) [记录](changes/2026-09-15-SITE-07-console-stackoverflow.md) [记录](baselines/console-shallowequal-validation.json) [记录](changes/2026-09-15-SITE-07-console-shallowequal.md) [记录](console-notice-review.md) [记录](baselines/console-notice-review-validation.json) [记录](changes/2026-09-15-SITE-07-console-notice-review.md) [记录](baselines/monaco-typescript-provenance.json) [记录](baselines/monaco-typescript-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-typescript.md) [记录](baselines/monaco-languages-provenance.json) [记录](baselines/monaco-languages-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-languages.md) [记录](baselines/monaco-language-notices.json) [记录](baselines/monaco-language-notices-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-language-notices.md) [记录](baselines/monaco-modes-provenance.json) [记录](baselines/monaco-modes-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-modes.md) [记录](baselines/monaco-basic-provenance.json) [记录](baselines/monaco-basic-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-basic.md) [记录](baselines/monaco-core-origins-provenance.json) [记录](baselines/monaco-core-origins-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-origins.md) [记录](baselines/monaco-core-build-provenance.json) [记录](baselines/monaco-core-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-core-build.md) [记录](baselines/monaco-contributions-provenance.json) [记录](baselines/monaco-contributions-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-contributions.md) [记录](baselines/monaco-css-build-provenance.json) [记录](baselines/monaco-css-build-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-css.md) [记录](baselines/monaco-node-path-provenance.json) [记录](baselines/monaco-node-path-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-node-path.md) [记录](changes/2026-09-15-SITE-07-monaco-dom.md) [记录](baselines/monaco-dom-origins-validation.json) [记录](changes/2026-09-15-SITE-07-monaco-unicode.md) [记录](baselines/monaco-unicode-provenance.json) [记录](baselines/monaco-unicode-validation.json) [记录](changes/2026-09-15-SITE-07-font-notices.md) [记录](baselines/site-font-notices-provenance.json) [记录](baselines/site-font-notices-validation.json)
- EX-01: [记录](changes/2026-09-14-EX-01-react-consumer.md) [记录](baselines/react-consumer-validation.json) [记录](scripts/react-consumer.mjs)
- EX-02: [记录](changes/2026-09-14-EX-02-vue-consumer.md) [记录](baselines/vue-consumer-validation.json) [记录](scripts/vue-consumer.mjs)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## SITE-05 本地链接、编辑器与远端缺口
新增check:site-links并接入CI:67生成页面/5766本地引用/942搜索结果全部有效。
真实编辑器与文档三浏览器72项、构建11项、CI回归80项均通过,声明/工具链/类型检查
通过,见[记录](changes/2026-09-16-SITE-05-links-editor.md)。外部链接经系统代理HEAD
复测81个:42个200、39个404;404全部是新增指南尚未进入GitHub master的编辑源文件。
不擅自推送/合并,SITE-05保持doing,225 done /22 doing /38 todo。后续远端源码可用
时复验链接;SITE-06还保留字体许可门槛。共享候选输入需REL-02刷新,正式复盘仍等待用户。
## SITE-05 搜索交互修复
修复 Enter 默认表单提交造成的整页重载、有内容时 Escape 不关闭、查询变化后选择
+3 -1
View File
@@ -4624,7 +4624,9 @@
"acceptance": "文档构建、链接、嵌入路径与声明注入通过;真实完整 demo 保留 EX-03 独立门槛;核对 ENG-PM-01 登记的搜索 peer 范围和真实搜索行为",
"evidence": [
"changes/2026-09-16-SITE-05-search.md",
"baselines/site05-search.json"
"baselines/site05-search.json",
"changes/2026-09-16-SITE-05-links-editor.md",
"baselines/site05-links-editor.json"
]
},
{
+7 -2
View File
@@ -6,7 +6,7 @@
1. 切换到 .node-version 指定的 Node,安装 Yarn 1.22.22;例如 `npm install --global yarn@1.22.22`,然后确认 `node --version` 和 `yarn --version`。npm 仅可用于安装 Yarn 工具及消费者兼容检查,不用于维护本仓库依赖锁。
2. 干净 checkout 执行 `yarn install --frozen-lockfile --non-interactive`,禁止 CI 自动更新锁或忽略安装脚本/engines。参见 [Yarn Classic install](https://classic.yarnpkg.com/en/docs/cli/install/)。
3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、20 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。
3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、根固定开发工具和 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。
私有根包最低 Node 为 ^20.19.0 || >=22.12.0,与原本使用的 Vite 7 一致。标准工具链验证
@@ -19,6 +19,11 @@ CORE-25 默认选项缺陷已修复并通过相同三 Node 与严格打包检查
## 锁文件和依赖维护
- 根构建工具固定精确版本并放在 devDependencies;发布包的 dependencies/peer 范围保持原样。
- SITE-05 将已有传递依赖 `htmlparser2@10.1.0` 显式声明为根 devDependency,供
`yarn check:site-links` 解析生成 HTML 使用;不进入播放器包。原版本和完整性不变,
根 yarn.lock 只合并新增的精确选择器。链接检查不执行 HTML 的脚本,不请求外部 URL。
文档站 FlexSearch peer 已对齐 0.7.43,见[搜索验证](changes/2026-09-16-SITE-05-search.md)。
Yarn workspace add 后须再从根目录安装并核对 resolutions,防止工作区子命令遗漏根覆盖。
- PKG-JASSUB-09 新增根 devDependency `pngjs@7.0.0`,在 Node 中解码 Playwright 截图,
用实际合成画面与转移画布 readback 对照;不进入发布包。仅新增其精确 Yarn 锁条目,
并验证固定 Node/Yarn 的 frozen 安装及严格工具链检查。上游 API 见
@@ -39,7 +44,7 @@ Monaco 浏览器资产匹配的精确 API 类型。只使用 type import,不
或加入播放器产物;root yarn.lock 仅增加该版本的一个条目。新增
`yarn test:site-editor` 验证编辑器状态/读取失败;浏览器检查仍使用原 assets/js/vs。
ENG-01 的 [npm 验证报告](baselines/toolchain-validation.json) 是切换前历史证据,不代表 Yarn 已验证。[Yarn 验证报告](baselines/yarn-validation.json) 记录最终干净冻结安装、20 项 Node 测试、21 库包 63 产物及文档站构建全部通过;63 产物 SHA-256 与 ENG-01 完全相同。保留的旧锁条目版本变化为零,9 个 workspace runtime 依赖解析与 npm 基线一致。搜索 peer 警告由 SITE-05 接续验证。
ENG-01 的 [npm 验证报告](baselines/toolchain-validation.json) 是切换前历史证据,不代表 Yarn 已验证。[Yarn 验证报告](baselines/yarn-validation.json) 记录最终干净冻结安装、20 项 Node 测试、21 库包 63 产物及文档站构建全部通过;63 产物 SHA-256 与 ENG-01 完全相同。保留的旧锁条目版本变化为零,9 个 workspace runtime 依赖解析与 npm 基线一致。搜索 peer 后续已由 SITE-05 对齐版本并完成三引擎交互验证;未启用的托管提供方警告保留说明。
构建通过不代表运行时、类型、真实浏览器或 npm 发布验收已完成。Chrome 不可用时按 release-reviews.md 使用内置浏览器并注明实际环境。
+13
View File
@@ -0,0 +1,13 @@
import process from 'node:process'
import { checkSiteLinks } from './site-build/links.ts'
try {
const report = await checkSiteLinks(`${process.cwd()}/docs`)
console.log(JSON.stringify(report, null, 2))
if (report.issues.length)
process.exitCode = 1
}
catch (error) {
console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
}
+20 -1
View File
@@ -88,4 +88,23 @@ native radio selection and visible code blocks after clicking the built code-gro
labels. SITE-03 still
owns desktop editor UI migration, SITE-04 semantic/bilingual documentation and
SITE-05 full site/search/link acceptance. No npm/Pages publication or remote CI
run follows from a successful local build. No dependencies were added.
run follows from a successful local build.
## Generated documentation links
`yarn check:site-links` reads every generated HTML page under `docs/document/`
and the actual built `virtual_search-data.*.js` module. `links.ts` uses the pinned
root development dependency `htmlparser2@10.1.0` to collect href/src references,
IDs and legacy named anchors without executing page scripts. It resolves local
files, directory indexes, encoded fragments and references back to the site root.
Script contents and inert template contents are not treated as rendered links.
An HTML base element requires explicit checker support rather than silently
using the wrong base. Missing/ambiguous/malformed search data and missing HTML
output fail the command. A missing target or anchor also exits nonzero.
The checker runs in `ci:check` against committed output and after the documentation
build in `ci:build`. Its JSON lists external URLs without requesting them. External
availability, CSS/JavaScript-discovered URLs, interactive Run Code forwarding,
playback and online editor behavior need separate checks. `test/site-links.test.js`
exercises valid HTML/entities, missing assets and anchors, invalid URLs and broken
search modules; it is included in `test:site-build` and `test:node`.
+156
View File
@@ -0,0 +1,156 @@
import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
import fs from 'node:fs/promises'
import path from 'node:path'
import { pathToFileURL } from 'node:url'
import { Parser } from 'htmlparser2'
export interface LinkIssue {
source: string
href: string
reason: string
}
async function files(directory: string): Promise<string[]> {
const entries = await fs.readdir(directory, { withFileTypes: true })
const result: string[] = []
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
assert(!entry.isSymbolicLink(), `Unexpected symlink: ${entry.name}`)
const file = path.join(directory, entry.name)
if (entry.isDirectory())
result.push(...await files(file))
else result.push(file)
}
return result
}
function record(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === 'object' && !Array.isArray(value))
}
function readHtml(source: string) {
const anchors = new Set<string>()
const references: string[] = []
let templateDepth = 0
new Parser({
onopentag(name, attributes) {
const inert = templateDepth > 0
if (name === 'template')
templateDepth++
if (inert)
return
assert(name !== 'base' || !attributes.href, 'An HTML base URL requires explicit link-checker support')
if (attributes.id)
anchors.add(attributes.id)
if (name === 'a' && attributes.name)
anchors.add(attributes.name)
for (const attribute of ['href', 'src']) {
if (attributes[attribute])
references.push(attributes[attribute])
}
},
onclosetag(name) {
if (name === 'template')
templateDepth--
},
}).end(source)
return { anchors, references }
}
// Check the shipped index, not a separately regenerated approximation of its links.
export async function readSearchLinks(documentRoot: string) {
const chunks = path.join(documentRoot, 'assets/chunks')
const matches = (await fs.readdir(chunks)).filter(name => /^virtual_search-data\.[\w-]+\.js$/.test(name))
assert.equal(matches.length, 1, 'Expected exactly one built search data module')
const name = matches[0]
assert(name)
const file = path.join(chunks, name)
const sha256 = createHash('sha256').update(await fs.readFile(file)).digest('hex')
const module: unknown = await import(`${pathToFileURL(file).href}?sha256=${sha256}`)
assert(record(module) && record(module.default) && record(module.default.PREVIEW_LOOKUP), 'Invalid built search lookup')
const links = Object.entries(module.default.PREVIEW_LOOKUP).map(([id, value]) => {
assert(record(value) && typeof value.l === 'string' && value.l.length, `Missing search URL: ${id}`)
return { id, href: value.l }
})
assert(links.length > 0, 'Empty search lookup')
return { file, sha256, links }
}
export async function checkSiteLinks(siteRoot: string) {
const root = await fs.realpath(siteRoot)
const documentRoot = path.join(root, 'document')
const htmlFiles = (await files(documentRoot)).filter(file => file.endsWith('.html'))
assert(htmlFiles.length > 0, 'No generated documentation pages')
const search = await readSearchLinks(documentRoot)
const documents = new Map<string, ReturnType<typeof readHtml>>()
const external = new Set<string>()
const issues: LinkIssue[] = []
let localReferences = 0
async function document(file: string) {
let value = documents.get(file)
if (!value) {
value = readHtml(await fs.readFile(file, 'utf8'))
documents.set(file, value)
}
return value
}
async function check(href: string, source: string, base = source) {
try {
const url = new URL(href, new URL(base, 'https://artplayer.org'))
if (!['http:', 'https:'].includes(url.protocol))
return
if (url.hostname !== 'artplayer.org') {
external.add(url.href)
return
}
localReferences++
let file = path.resolve(root, `.${decodeURIComponent(url.pathname)}`)
assert(file === root || file.startsWith(`${root}${path.sep}`), 'Target outside site root')
let stat
try {
stat = await fs.stat(file)
}
catch {
throw new Error('Missing local file')
}
if (stat.isDirectory()) {
file = path.join(file, 'index.html')
assert((await fs.stat(file)).isFile(), 'Missing directory index')
}
assert((await fs.realpath(file)).startsWith(`${root}${path.sep}`), 'Target resolves outside site root')
if (url.hash && file.endsWith('.html')) {
const id = decodeURIComponent(url.hash.slice(1))
const target = await document(file)
if (id && id.toLowerCase() !== 'top') {
assert(target.anchors.has(id), `Missing anchor: ${id}`)
}
}
}
catch (error) {
issues.push({ source, href, reason: error instanceof Error ? error.message : String(error) })
}
}
for (const file of htmlFiles) {
const source = `/${path.relative(root, file).replaceAll('\\', '/')}`
const html = await document(file)
for (const href of html.references)
await check(href, source)
}
const pageIssues = issues.length
for (const link of search.links)
await check(link.href, `search:${link.id}`, '/document/')
return {
pages: htmlFiles.length,
localReferences,
searchEntries: search.links.length,
searchModule: { path: path.relative(root, search.file).replaceAll('\\', '/'), sha256: search.sha256 },
externalUrls: [...external].sort(),
pageIssues,
searchIssues: issues.length - pageIssues,
issues,
}
}
+1
View File
@@ -29,6 +29,7 @@
"build-site-notices.mjs",
"build-i18n.js",
"build-docs.js",
"check-site-links.mjs",
"../packages/artplayer-vitepress/build/**/*.ts",
"build-vconsole.mjs",
"browser-validation/**/*.ts",
+54
View File
@@ -0,0 +1,54 @@
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Exercise the generated site's real filesystem and HTML links.
import test from 'node:test'
import { checkSiteLinks } from '../scripts/site-build/links.ts'
async function fixture(t, lookup = { a: { l: 'target.html#中文' } }) {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'artplayer-site-links-'))
t.after(async () => {
assert.equal(path.dirname(path.resolve(root)), path.resolve(os.tmpdir()))
assert(path.basename(root).startsWith('artplayer-site-links-'))
await fs.rm(root, { recursive: true, force: true })
})
await fs.mkdir(path.join(root, 'document/assets/chunks'), { recursive: true })
await fs.writeFile(path.join(root, 'package.json'), '{"type":"module"}')
await fs.writeFile(path.join(root, 'document/assets/chunks/virtual_search-data.test.js'), `export default ${JSON.stringify({ PREVIEW_LOOKUP: lookup })}`)
await fs.writeFile(path.join(root, 'document/index.html'), '<a href="target.html#%E4%B8%AD%E6%96%87">target</a><a href="https://example.com/unchecked">external</a><a href="mailto:test@example.com">email</a>')
await fs.writeFile(path.join(root, 'document/target.html'), '<h1 id="中文">Heading</h1><a name="legacy"></a><a href="#legacy">legacy</a><a href="/document/">home</a><img src="/image.svg?v=1"><script src="/script.js"></script><h2 id="a&amp;b">Entity</h2><a href="#a%26b">entity</a><script>const html = \'<a href="/not-a-link">\';</script><template><a href="/inert">inert</a></template>')
await fs.writeFile(path.join(root, 'image.svg'), '<svg/>')
await fs.writeFile(path.join(root, 'script.js'), '')
return root
}
test('built pages and the shipped search index resolve encoded anchors, assets and directory URLs', async (t) => {
const report = await checkSiteLinks(await fixture(t))
assert.equal(report.pages, 2)
assert.equal(report.searchEntries, 1)
assert.equal(report.localReferences, 7)
assert.deepEqual(report.externalUrls, ['https://example.com/unchecked'])
assert.deepEqual(report.issues, [])
})
test('broken assets, ordinary anchors and actual search-index anchors fail independently', async (t) => {
const root = await fixture(t, { a: { l: 'target.html#absent' }, b: { l: 'missing.html' } })
await fs.appendFile(path.join(root, 'document/index.html'), '<a href="target.html#missing">bad</a><img src="/missing.svg"><a href="/%ZZ">invalid</a>')
const report = await checkSiteLinks(root)
assert.equal(report.pageIssues, 3)
assert.equal(report.searchIssues, 2)
assert(report.issues.some(issue => issue.source === 'search:a' && issue.reason.includes('Missing anchor')))
assert(report.issues.some(issue => issue.source === 'search:b' && issue.reason === 'Missing local file'))
})
test('missing, ambiguous or malformed search modules cannot silently pass', async (t) => {
const root = await fixture(t, { a: {} })
await assert.rejects(checkSiteLinks(root), /Missing search URL/)
const chunks = path.join(root, 'document/assets/chunks')
await fs.copyFile(path.join(chunks, 'virtual_search-data.test.js'), path.join(chunks, 'virtual_search-data.extra.js'))
await assert.rejects(checkSiteLinks(root), /exactly one/)
await fs.unlink(path.join(chunks, 'virtual_search-data.extra.js'))
await fs.unlink(path.join(chunks, 'virtual_search-data.test.js'))
await assert.rejects(checkSiteLinks(root), /exactly one/)
})
+1 -1
View File
@@ -4754,7 +4754,7 @@ html-void-elements@^3.0.0:
resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz"
integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
htmlparser2@^10.1.0:
htmlparser2@10.1.0, htmlparser2@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.1.0.tgz#fe3f2e12c73b6e462d4e10395db9c1119e4d6ae4"
integrity sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==