mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-18 19:01:20 -08:00
test(BASE-05): capture published distribution and type consumers
This commit is contained in:
1 parent
2b7054d8a5
commit
c7a375e146
21 files changed
+5079
-28
No files matched your search
@@ -40,3 +40,7 @@ BASE-03 已在内置浏览器实测播放推进、暂停/seek/切源、事件及
|
||||
## DOM 与官方示例路径
|
||||
|
||||
BASE-04 已在内置浏览器完成两轮 16 项 DOM/样式/实际输入断言。见 [覆盖/复现/历史问题](dom-coverage.md)、[冻结报告](dom.json) 和 [29 示例/36 HTML/22 包路径](demo-inventory.json)。轻量夹具通过不代表 Monaco 或所有官方示例已验证。
|
||||
|
||||
## 分发、类型与 SSR 消费
|
||||
|
||||
BASE-05 的 [覆盖/复跑/历史诊断](consumer-coverage.md) 包含 18 项 Node runtime、16 项严格 TS 场景和浏览器两轮各 5 项 SSR 检查。[消费者](consumers.json)、[SSR](ssr.json)、[22 包资源](distribution.json) 分别记录已发布内容与工作区观察。8 项类型历史失败保持显式,不能计作候选通过。
|
||||
@@ -0,0 +1,72 @@
|
||||
# 发布入口、资源与类型消费基线
|
||||
|
||||
BASE-05 验证固定 npm core 5.4.0 / chapter 1.1.0。隔离目录只写入已校验 tarball 的 47 个成员,不链接 workspace 包,也不修改发布 manifest。运行依赖已经包含在这些 bundle 中;本探针没有执行 npm/Yarn 安装,因此不能代替安装脚本、peerDependencies 或候选 npm pack 验收。
|
||||
|
||||
## 入口与 SSR
|
||||
|
||||
[consumers.json](consumers.json) 使用 Node 24.21.0,18 项 runtime 断言实际重跑通过。
|
||||
|
||||
| 契约 / 固定 ID | 已验证行为 | 后续责任 |
|
||||
| --- | --- | --- |
|
||||
| API-09 / DIST.cjs、cjs-legacy | require 核心/插件根及 legacy 子路径,核心版本与插件工厂可调用 | ENG-06/07;CORE-12;PKG-CHAPTER-04 |
|
||||
| API-09 / DIST.esm、esm-default-only、esm-legacy、direct-esm-file | 真实 Node import 的默认导出,核心/插件 root/legacy;绝对文件 URL 的核心 .mjs 可用 | ENG-06/07;CORE-12;PKG-CHAPTER-04 |
|
||||
| API-09 / DIST.global-core/core-legacy/chapter/chapter-legacy | VM 中执行无 module/define 的 UMD,保留 Artplayer / artplayerPluginChapter 名称 | ENG-06;浏览器 UMD 挂载另由 BASE-02/04/本任务 SSR 验证 |
|
||||
| API-09 / DIST.amd-core/core-legacy/chapter/chapter-legacy | 最小 define.amd loader 捕获工厂,调用一次;导出和对应全局变量为同一对象 | ENG-06;这是 AMD 分支验证,未运行完整 RequireJS 网站 |
|
||||
| API-09 / DIST.i18n-cjs-esm-global | 已发布 11 个语言的 CJS/ESM/global 内容一致,名称/keys/Play 值留证 | CORE-12;不等于所有语言 UI 操作通过 |
|
||||
| API-09 / DIST.exports-boundary | 三个未经 exports 暴露的深层包 specifier 返回 ERR_PACKAGE_PATH_NOT_EXPORTED | ENG-06/07:区分 package specifier 与 CDN/直接文件路径 |
|
||||
| API-01/09 / SSR.import-template | 无 DOM 的 import/require 可读取 Artplayer.html/STYLE,不创建实例 | CORE-12 |
|
||||
| API-01 / SSR.constructor-browser-only | CJS、ESM、legacy 在 useSSR=false/true 下构造均抛明确浏览器环境错误,共六次 | CORE-01/12:SSR import 不等于服务端可构造播放器 |
|
||||
| API-01/08 / SSR.browser-reuses-player/video、browser-ready/plugin/cleanup | 内置浏览器两轮各五项检查,预置模板节点身份与标记保留,真实 MP4 metadata/ready、chapter 注册、销毁清理 | CORE-12 / ENG-05;[ssr.json](ssr.json) |
|
||||
|
||||
DIST 表中的同前缀短名均使用 DIST.。语言实际为 ar/cs/es/fa/fr/id/pl/ru/tr/vi/zh-tw;不虚构 en/zh-cn 的外部文件,它们属于核心内置语言。Node 隔离消费者会检查 require.resolve 指向隔离发布内容;TypeScript 也拒绝解析到 workspace 声明。
|
||||
|
||||
AMD 同时设置全局是发布构建的真实行为,不能直接换成只 define 的默认 UMD 模板。useSSR 浏览器探针是将 Artplayer.html 预先写入容器再初始化,不是 React/Vue SSR 框架集成,也不是服务端构造。两轮 videoWidth=640、duration=90.046009,保留 serverMarker,errors/unhandled 及浏览器 error/warn 日志为空。
|
||||
|
||||
## TypeScript 矩阵
|
||||
|
||||
固定 TypeScript 5.9.3,strict=true、skipLibCheck=false、noEmit=true、ES2020+DOM、types=[]、esModuleInterop=true。每个场景在独立发布声明上创建 compiler program,记录全部诊断及声明解析路径;临时目录路径规范化为 `<consumer>`,不修改诊断类别。尚未确定最低受支持 TS,不能据此把消费者下限提高到 5.9.3。
|
||||
|
||||
固定场景 ID 为 `TYPE.<mode>/<fixture>`,例如 `TYPE.bundler-esm/public.ts`。Node10 是 TypeScript 的旧模块解析模式名,**不是 Node.js 10 运行环境**。
|
||||
|
||||
| fixture | node10-commonjs | nodenext-cjs | nodenext-esm | bundler-esm |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| public.ts:默认/命名类型、core legacy、ready this、插件、play、toggle、注册返回声明、两个错误参数检查 | 通过 | 通过 | 历史默认导出互操作错误及级联诊断 | 通过 |
|
||||
| language.ts:fr 子路径及 i18n 配置 | 通过 | 通过 | core 不可构造 TS2351 | 通过 |
|
||||
| optional-chapter.ts:chapter() | TS2554 | TS2554 | TS2349 | TS2554 |
|
||||
| legacy-plugin.ts:chapter/legacy | TS2307 | 通过 | TS2349 | 通过 |
|
||||
|
||||
16 项中 8 项编译通过,8 项为明确捕获的历史失败。检查器通过表示结果与历史记录一致,不是将八项失败当作候选验收通过。NodeNext ESM 的失败不能用 skipLibCheck、any 或删除消费样例掩盖。public.ts 的 @ts-expect-error 仅用于 string URL 和 number chapter 边界反例;在编译通过模式中它们必须真正产生类型错误。
|
||||
|
||||
| 差异 ID | 证据与影响 | 责任 |
|
||||
| --- | --- | --- |
|
||||
| BASE-TYPE-01 | 发布 root ESM 的运行时默认类/函数正常,但其 .d.ts 在无 type:module 的包中被 NodeNext ESM 消费时呈现 namespace,不可构造/调用 | ENG-04、CORE-07、PKG-CHAPTER-04:协调 ESM/CJS 声明入口并保留旧解析模式 |
|
||||
| BASE-TYPE-02 | 发布 chapter 的 Option 参数声明为必填,chapter() 报 TS2554;BASE-02 的真实浏览器已用无参数工厂成功注册 | PKG-CHAPTER-02:补兼容可选参数与正反例 |
|
||||
| BASE-TYPE-03 | chapter/legacy 运行时 CJS/ESM 可用,Node10 解析下声明缺 typesVersions 回退,报 TS2307 | ENG-04、PKG-CHAPTER-04:补旧解析消费,不改变 legacy 文件路径 |
|
||||
| BASE-TYPE-04 | public.ts 依旧能把 plugins.add 结果赋给 Promise、把 toggle 赋给 void;BASE-02/03 运行时证据与此不同 | BASE-07 / CORE-07:分别记录旧声明接受范围与真实返回,不能直接收紧旧样例 |
|
||||
|
||||
完整诊断见 consumers.json;候选修复需新增成功断言并解释旧新差异,保留本历史记录。
|
||||
|
||||
## 全包分发与资源清单
|
||||
|
||||
[distribution.json](distribution.json) 保存 22 包的完整 manifest、118 个当前 dist/types 文件的路径/大小/哈希、声明入口到文件的映射、源码资源路径和构建输出规则。它是工作区观察,现有 dist 可能陈旧,不能宣称这些哈希全部来自本轮构建或 npm。核心/chapter 发布成员仍以 releases.json 为准,其余包由各自 01 契约任务取真实发布基线。
|
||||
|
||||
- 21 个库由根 build.js 生成 .js / .legacy.js / .mjs 并复制 docs/compiled;核心 i18n 由 build-i18n.js 独立生成。
|
||||
- artplayer-vitepress 当前没有 library main/module/exports,根 getProjects 排除它;实际 VitePress outDir 是 docs/document。这是站点分发。其 manifest 未设置 private:true,未来是否向 npm 分发须由 SITE/REL 明确,不能机械纳入库发布循环;用户要求的自身 next-major 版本仍保留。
|
||||
- **BASE-DIST-01**:artplayer-tool-thumbnail 的 module/exports.import 指向 .esm.js,实际观察/构建规则为 .mjs;types 指向不存在的 .d.ts,且无 main。交 PKG-TOOL-THUMB-01/04 核实旧 npm 与 CDN 文件,保留历史路径兼容方案。它与 BASE-DEMO-01 的旧 thumbnail 插件是两个不同包,不能混为一谈。
|
||||
- 源码资源表只列文件路径,不等于远端 SDK/worker/wasm 的完整闭包;外部模型/播放依赖由包契约和 EX-03 验证。npm 实际文件白名单、安装和打包由 ENG-07/REL 验收。
|
||||
|
||||
## 重跑与维护地图
|
||||
|
||||
使用固定 Node/Yarn 环境,先完成冻结安装;以下命令不需要新增依赖:
|
||||
|
||||
```sh
|
||||
node refactor/scripts/consumers.mjs --check
|
||||
node refactor/scripts/distribution.mjs --check
|
||||
node --test refactor/scripts/consumers.test.mjs
|
||||
```
|
||||
|
||||
consumers.mjs 校验固定归档、写入临时消费者、启动真实 Node 子进程并调用固定 TS compiler,结束时只清理已验证处于 .cache/consumers 下的本次目录。runtime.cjs 是分发/SSR Node 用例;public/language/optional-chapter/legacy-plugin.ts 各自代表类型场景。consumers.test.mjs 自动接入 test:baseline,包含来源、缺案例、静默跳过声明、原可用消费失败及媒体证据负例。distribution.mjs --check 只校验冻结清单,不重新构建或验证所有当前文件。
|
||||
|
||||
浏览器运行 `node refactor/scripts/browser-server.mjs`,打开 `http://127.0.0.1:8083/fixtures/ssr.html`,点击 Run SSR hydration baseline。须显示 5 checks / errors 0,再运行 `node refactor/scripts/ssr.mjs --compare refactor/.cache/reports/ssr.json`。ssr.mjs --check 只检查保存证据,不能替代浏览器重跑。
|
||||
|
||||
--capture 仅用于第一次采集且拒绝覆盖已有记录。发布基线变化必须独立记录;ENG-04/06/07 将这些样例扩为候选测试,不能改旧归档/声明制造通过。尚未完成全生态、所有历史 TS/Node、打包安装、框架 SSR、真机或发布验收。
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "BASE-05",
|
||||
"sourceCommit": "2b7054d8",
|
||||
"browserSurface": "Codex in-app browser",
|
||||
"report": {
|
||||
"kind": "ssr",
|
||||
"environment": {
|
||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36",
|
||||
"language": "zh-CN"
|
||||
},
|
||||
"scripts": [
|
||||
"/releases/artplayer/dist/artplayer.js",
|
||||
"/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js",
|
||||
"/fixtures/ssr.js"
|
||||
],
|
||||
"checks": [
|
||||
{
|
||||
"id": "SSR.browser-reuses-player",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "SSR.browser-reuses-video",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "SSR.browser-ready",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "SSR.browser-plugin",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"id": "SSR.browser-cleanup",
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"unhandled": [],
|
||||
"observations": {
|
||||
"videoWidth": 640,
|
||||
"duration": 90.046009,
|
||||
"serverMarker": "preserved"
|
||||
},
|
||||
"capture": {
|
||||
"capturedAt": "2026-09-10T12:25:30.884Z",
|
||||
"releases": [
|
||||
{
|
||||
"name": "artplayer",
|
||||
"version": "5.4.0",
|
||||
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
|
||||
"member": "package/dist/artplayer.js",
|
||||
"sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f"
|
||||
},
|
||||
{
|
||||
"name": "artplayer-plugin-chapter",
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
|
||||
"member": "package/dist/artplayer-plugin-chapter.js",
|
||||
"sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0"
|
||||
}
|
||||
],
|
||||
"fixtureHashAlgorithm": "sha256-lf",
|
||||
"fixtures": {
|
||||
"ssr.html": "57f8ce4d0227b83d01a575551d2d9bc3e0d0042af6038680833b2609849e8ea0",
|
||||
"ssr.js": "6396ad144a29b576f76974dac640ab47ddf28e6a27e71c56570c61d517d54242"
|
||||
},
|
||||
"media": {
|
||||
"video.mp4": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980"
|
||||
}
|
||||
}
|
||||
},
|
||||
"verification": {
|
||||
"repeatCapturedAt": "2026-09-10T12:27:45.290Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# BASE-05:分发、类型与 SSR 消费
|
||||
|
||||
日期 2026-09-10;分支 codex/compatible-modernization;起点 2b7054d8。
|
||||
|
||||
## 实现与证据
|
||||
|
||||
固定 core 5.4.0 / chapter 1.1.0 的 47 个发布成员经完整性检查后复制到独立消费者,无 workspace 链接,不修改包 manifest。真实 Node 24.21.0 验证 root/legacy CJS 与 ESM、全局/最小 AMD loader、11 语言、exports 边界及 SSR import/模板/非浏览器构造错误,18 项断言通过。AMD 同时写入全局的历史副作用已保留。
|
||||
|
||||
TypeScript 5.9.3 以 strict=true / skipLibCheck=false 跑 16 个消费场景,8 项编译通过、8 项捕获历史诊断,按模式和场景分别记录。NodeNext ESM 默认导出互操作、chapter 可选参数及旧解析 legacy 声明问题已分配 ENG-04/CORE-07/包任务;toggle/plugins.add 旧声明返回差异交 BASE-07。测试通过代表如实复现历史,不能当作全部类型已经正确。
|
||||
|
||||
新增浏览器 SSR 夹具和报告白名单,两次最终重载各 5 项断言通过,原有 player/video 节点及自定义标记保留,真实 metadata/ready、插件注册和清理通过。无意外错误/拒绝,浏览器 error/warn 为空。预置模板复用不是服务端构造或 React/Vue SSR 集成。
|
||||
|
||||
清点全部 22 包 manifest、118 个观察到的 dist/types 文件、入口匹配、源码资源和构建规则。thumbnail tool 的 .esm.js/缺失声明与文档站实际站点分发已明确;清单没有把陈旧 dist 等同真实 npm 内容。没有更改生产源码、声明、依赖、锁文件或生成分发。
|
||||
|
||||
详见 [契约/固定 ID/复现/责任](../baselines/consumer-coverage.md)、[Node 与 TS 报告](../baselines/consumers.json)、[浏览器 SSR](../baselines/ssr.json)、[全包资源](../baselines/distribution.json)。消费者、清单和浏览器源验证分开,避免一个大脚本混淆已验证范围。
|
||||
|
||||
## 验证与接续
|
||||
|
||||
新增 3 项 Node 测试,包含真正隔离重跑和来源、缺案例、skipLibCheck、旧合法类型失败、媒体证据等负例。完整 ci:check、计划/链接、脚本语法和 diff 检查通过;没有为本任务重建无变化的生产包。冻结内容与再次运行的 runtime/类型结果一致。
|
||||
|
||||
本任务独立本地提交,主题含 BASE-05;不推送、不发布。撤销本提交可恢复旧基线服务/测试入口,发布历史记录保留于 Git。下一任务 BASE-06:固定媒体下的性能/资源基线,随后继续类型和试点实施基础。
|
||||
@@ -0,0 +1,4 @@
|
||||
import Artplayer from 'artplayer'
|
||||
import french from 'artplayer/i18n/fr'
|
||||
const art = new Artplayer({ container: '#player', url: 'video.mp4', lang: 'fr', i18n: { fr: french } })
|
||||
void art
|
||||
@@ -0,0 +1,3 @@
|
||||
import chapter from 'artplayer-plugin-chapter/legacy'
|
||||
const factory = chapter({ chapters: [] })
|
||||
void factory
|
||||
@@ -0,0 +1,2 @@
|
||||
import chapter from 'artplayer-plugin-chapter'
|
||||
chapter()
|
||||
@@ -0,0 +1,30 @@
|
||||
import Artplayer, { type Option, type Setting } from 'artplayer'
|
||||
import Legacy from 'artplayer/legacy'
|
||||
import chapter from 'artplayer-plugin-chapter'
|
||||
|
||||
const settings: Setting[] = [{ name: 'quality', html: 'Quality', selector: [{ html: 'Auto', default: true }] }]
|
||||
const option: Option = {
|
||||
container: '#player', url: 'video.mp4', useSSR: true, settings,
|
||||
plugins: [chapter({ chapters: [{ start: 0, end: 10, title: 'First' }] })],
|
||||
}
|
||||
const art = new Artplayer(option, function (value) {
|
||||
const self: Artplayer = this
|
||||
const player: Artplayer = value
|
||||
self.pause()
|
||||
player.currentTime = 5
|
||||
})
|
||||
const old = new Legacy(option)
|
||||
const play: Promise<void> = art.play()
|
||||
const toggle: void = art.toggle()
|
||||
const html: string = Artplayer.html
|
||||
const factory = chapter({})
|
||||
factory(art).update({ chapters: [] })
|
||||
art.on('custom', (...values: unknown[]) => values.length).off('custom', () => {})
|
||||
const registration: Promise<Artplayer['plugins']> = art.plugins.add(factory)
|
||||
void [old, play, toggle, html, registration]
|
||||
|
||||
// Keep rejection checks independent of historical declaration mismatches.
|
||||
// @ts-expect-error url accepts a string
|
||||
new Artplayer({ container: '#player', url: 42 })
|
||||
// @ts-expect-error chapter boundaries must be numbers
|
||||
chapter({ chapters: [{ start: 'zero', end: 1, title: 'Invalid' }] })
|
||||
@@ -0,0 +1,86 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const vm = require('node:vm')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
|
||||
;(async () => {
|
||||
const checks = []
|
||||
const observations = {}
|
||||
const check = (id, value) => { assert(value, id); checks.push(id) }
|
||||
const root = __dirname
|
||||
const load = relative => fs.readFileSync(path.join(root, 'node_modules', relative), 'utf8')
|
||||
const core = require('artplayer')
|
||||
const chapter = require('artplayer-plugin-chapter')
|
||||
check('DIST.cjs', typeof core === 'function' && core.version === '5.4.0' && typeof chapter({}) === 'function')
|
||||
check('DIST.cjs-legacy', require('artplayer/legacy').version === core.version && typeof require('artplayer-plugin-chapter/legacy')({}) === 'function')
|
||||
const esm = await import('artplayer')
|
||||
const pluginEsm = await import('artplayer-plugin-chapter')
|
||||
check('DIST.esm', esm.default.version === core.version && typeof pluginEsm.default({}) === 'function')
|
||||
check('DIST.esm-default-only', Object.keys(esm).join() === 'default' && Object.keys(pluginEsm).join() === 'default')
|
||||
const legacy = await import('artplayer/legacy')
|
||||
const pluginLegacy = await import('artplayer-plugin-chapter/legacy')
|
||||
check('DIST.esm-legacy', legacy.default.version === core.version && typeof pluginLegacy.default({}) === 'function')
|
||||
for (const [name, filename, globalName] of [
|
||||
['core', 'artplayer/dist/artplayer.js', 'Artplayer'],
|
||||
['core-legacy', 'artplayer/dist/artplayer.legacy.js', 'Artplayer'],
|
||||
['chapter', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js', 'artplayerPluginChapter'],
|
||||
['chapter-legacy', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.legacy.js', 'artplayerPluginChapter'],
|
||||
]) {
|
||||
const code = load(filename)
|
||||
const global = vm.createContext({ console })
|
||||
vm.runInContext(code, global, { timeout: 2000 })
|
||||
check(`DIST.global-${name}`, typeof global[globalName] === 'function')
|
||||
let value
|
||||
let calls = 0
|
||||
const define = (factory) => { calls++; value = factory() }
|
||||
define.amd = {}
|
||||
const amd = vm.createContext({ console, define })
|
||||
vm.runInContext(code, amd, { timeout: 2000 })
|
||||
check(`DIST.amd-${name}`, calls === 1 && typeof value === 'function' && amd[globalName] === value)
|
||||
}
|
||||
check('SSR.import-template', core.html.includes('art-video-player') && esm.default.html === core.html && typeof core.STYLE === 'string' && core.instances.length === 0)
|
||||
observations.constructorErrors = []
|
||||
for (const Class of [core, esm.default, legacy.default]) {
|
||||
for (const useSSR of [false, true]) {
|
||||
let error
|
||||
try { new Class({ container: '#server', url: 'video.mp4', useSSR }) }
|
||||
catch (caught) { error = caught }
|
||||
assert.equal(error?.message, 'Artplayer can only be used in the browser environment')
|
||||
observations.constructorErrors.push({ useSSR, name: error.name, message: error.message })
|
||||
}
|
||||
}
|
||||
check('SSR.constructor-browser-only', observations.constructorErrors.length === 6)
|
||||
observations.languages = []
|
||||
const languages = fs.readdirSync(path.join(root, 'node_modules/artplayer/dist/i18n')).filter(file => file.endsWith('.mjs')).map(file => file.slice(0, -4)).sort()
|
||||
for (const name of languages) {
|
||||
const cjs = require(`artplayer/i18n/${name}`)
|
||||
const module = await import(`artplayer/i18n/${name}`)
|
||||
assert.deepEqual(module.default, cjs)
|
||||
const globalName = `artplayerI18n${name.replace(/(^|-)([a-z])/g, (_, _prefix, char) => char.toUpperCase())}`
|
||||
const context = vm.createContext({})
|
||||
vm.runInContext(load(`artplayer/dist/i18n/${name}.js`), context, { timeout: 2000 })
|
||||
assert.equal(JSON.stringify(context[globalName]), JSON.stringify(cjs))
|
||||
observations.languages.push({ name, globalName, keys: Object.keys(cjs).sort(), play: cjs.Play })
|
||||
}
|
||||
check('DIST.i18n-cjs-esm-global', languages.length > 0)
|
||||
observations.blockedDeepImports = []
|
||||
for (const specifier of ['artplayer/dist/artplayer.js', 'artplayer/types/artplayer.d.ts', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js']) {
|
||||
let code
|
||||
try { require.resolve(specifier) }
|
||||
catch (error) { code = error.code }
|
||||
assert.equal(code, 'ERR_PACKAGE_PATH_NOT_EXPORTED')
|
||||
observations.blockedDeepImports.push({ specifier, code })
|
||||
}
|
||||
check('DIST.exports-boundary', observations.blockedDeepImports.length === 3)
|
||||
observations.resolutions = {}
|
||||
for (const specifier of ['artplayer', 'artplayer/legacy', 'artplayer-plugin-chapter', 'artplayer-plugin-chapter/legacy']) {
|
||||
const resolved = require.resolve(specifier)
|
||||
assert(resolved.startsWith(path.join(root, 'node_modules') + path.sep), 'Escaped isolated packages')
|
||||
observations.resolutions[specifier] = path.relative(root, resolved).replaceAll('\\', '/')
|
||||
}
|
||||
// CDN-style absolute URLs bypass package exports; verify the actual files remain readable imports.
|
||||
const direct = await import(pathToFileURL(path.join(root, 'node_modules/artplayer/dist/artplayer.mjs')).href)
|
||||
check('DIST.direct-esm-file', direct.default === esm.default)
|
||||
process.stdout.write(JSON.stringify({ checks, observations }))
|
||||
})().catch((error) => { console.error(error); process.exitCode = 1 })
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<meta charset="utf-8">
|
||||
<title>ArtPlayer published SSR hydration baseline</title>
|
||||
<style>.player { width: 480px; height: 270px; } pre { white-space: pre-wrap; }</style>
|
||||
<h1>Browser reuse of server template</h1>
|
||||
<button id="run">Run SSR hydration baseline</button>
|
||||
<p id="status">Ready</p>
|
||||
<div class="player"></div>
|
||||
<pre id="result"></pre>
|
||||
<script src="/releases/artplayer/dist/artplayer.js"></script>
|
||||
<script src="/releases/artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js"></script>
|
||||
<script src="/fixtures/ssr.js"></script>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
/* global Artplayer, artplayerPluginChapter */
|
||||
document.querySelector('#run').onclick = async () => {
|
||||
const report = { kind: 'ssr', environment: { userAgent: navigator.userAgent, language: navigator.language }, scripts: [...document.scripts].map(script => new URL(script.src).pathname), checks: [], errors: [], unhandled: [] }
|
||||
const check = (id, passed) => { report.checks.push({ id, passed: Boolean(passed) }); if (!passed) throw new Error(id) }
|
||||
const onError = event => report.errors.push(event.message)
|
||||
const onUnhandled = event => report.unhandled.push(String(event.reason))
|
||||
addEventListener('error', onError)
|
||||
addEventListener('unhandledrejection', onUnhandled)
|
||||
let art
|
||||
try {
|
||||
const container = document.querySelector('.player')
|
||||
container.innerHTML = Artplayer.html
|
||||
const player = container.querySelector('.art-video-player')
|
||||
const video = container.querySelector('.art-video')
|
||||
player.dataset.serverMarker = 'preserved'
|
||||
art = new Artplayer({ container, url: '/assets/sample/video.mp4', useSSR: true, muted: true, plugins: [artplayerPluginChapter({})] })
|
||||
check('SSR.browser-reuses-player', art.template.$player === player && player.dataset.serverMarker === 'preserved')
|
||||
check('SSR.browser-reuses-video', art.video === video && container.querySelectorAll('.art-video-player').length === 1)
|
||||
await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('SSR ready timeout')), 10000)
|
||||
art.once('ready', () => { clearTimeout(timer); resolve() })
|
||||
})
|
||||
check('SSR.browser-ready', art.isReady && video.videoWidth > 0 && video.duration > 0)
|
||||
check('SSR.browser-plugin', art.plugins.artplayerPluginChapter.name === 'artplayerPluginChapter')
|
||||
report.observations = { videoWidth: video.videoWidth, duration: video.duration, serverMarker: player.dataset.serverMarker }
|
||||
art.destroy()
|
||||
check('SSR.browser-cleanup', container.children.length === 0 && Artplayer.instances.length === 0)
|
||||
}
|
||||
catch (error) { report.errors.push(error.message) }
|
||||
finally {
|
||||
if (art && Artplayer.instances.includes(art)) art.destroy()
|
||||
removeEventListener('error', onError)
|
||||
removeEventListener('unhandledrejection', onUnhandled)
|
||||
}
|
||||
document.querySelector('#result').textContent = JSON.stringify(report, null, 2)
|
||||
const response = await fetch('/reports/ssr', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report) })
|
||||
document.querySelector('#status').textContent = response.ok ? `Saved ${report.checks.length} checks; errors ${report.errors.length}` : 'Save failed'
|
||||
document.querySelector('#run').disabled = true
|
||||
}
|
||||
+14
-13
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 193 / doing 0 / blocked 0 / done 21 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 192 / doing 0 / blocked 0 / done 22 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
| BASE-02 | workspace<br>捕获公共 API 和属性描述符 | BASE-01, BASE-HARNESS-01 | 核心与试点的构造/默认值/静态和实例 API 快照,其余包由 01/02 步补齐;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 真实发布包反射与旧用户调用可重跑,未验证项明确 | H | done |
|
||||
| BASE-03 | workspace<br>捕获事件、异步和生命周期 | BASE-02 | ready/restart/destroy、Promise、切源/插件事件 trace;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 成功、失败、重入和多实例有断言,历史缺陷有标记;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | H | done |
|
||||
| BASE-04 | workspace<br>捕获 DOM、CSS 和官方 demo | BASE-02 | 核心模板/类名/变量、键盘/焦点/可访问名称基线及全 demo 路径映射;docs-browser-testing.md 中 HTML/编辑器的脚本来源与存储状态;登记契约/版本/固定测试 ID/命令/报告与负责任务 | 核心/试点用户样式和输入用法已记录,其他 demo 运行状态与未知项明确;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | done |
|
||||
| BASE-05 | workspace<br>捕获包入口、资源和类型消费 | BASE-01 | 全包 manifest/资源路径清单;核心/试点的 UMD/AMD/ESM/CJS/legacy/i18n 和类型消费者基线;登记契约/版本/固定测试 ID/命令/报告与负责任务;区分库/npm 与站点实际分发类别,SSR import/模板/非浏览器构造错误分别捕获 | 核心/试点隔离消费可运行,其他包由各自契约任务验证,thumbnail tool 差异已登记 | H | todo |
|
||||
| BASE-05 | workspace<br>捕获包入口、资源和类型消费 | BASE-01 | 全包 manifest/资源路径清单;核心/试点的 UMD/AMD/ESM/CJS/legacy/i18n 和类型消费者基线;登记契约/版本/固定测试 ID/命令/报告与负责任务;区分库/npm 与站点实际分发类别,SSR import/模板/非浏览器构造错误分别捕获 | 核心/试点隔离消费可运行,其他包由各自契约任务验证,thumbnail tool 差异已登记 | H | done |
|
||||
| BASE-06 | workspace<br>记录性能与资源基线 | BASE-03, BASE-04 | 固定媒体/设备测量、包体积、反复装卸资源报告 | 多次采样可重跑,指标和审查阈值确定,不承诺未经测量的收益;使用已发布包和最小可重跑探针启动,不等待后续 ENG 服务 | M | todo |
|
||||
| BASE-07 | workspace<br>建立差异和风险台账 | BASE-03, BASE-05 | 销毁/切源/类型差异、vendored 许可和外部 SDK 清单 | 已确认与待复现分开,每项有负责任务及兼容处理路线 | H | todo |
|
||||
| BASE-07 | workspace<br>建立差异和风险台账 | BASE-03, BASE-05 | 销毁/切源/类型差异、vendored 许可和外部 SDK 清单 | 已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据 | H | todo |
|
||||
| BASE-08 | workspace<br>建立消费者与真实环境验证矩阵 | BASE-01, BASE-05 | 每包版本/浏览器/codec/SDK/设备/样本、证据和负责验证任务的矩阵 | 格式兼容与运行能力分开,未知/缺环境不算通过,明确哪些检查阻止哪些批次发布 | H | todo |
|
||||
|
||||
## 2 工程保障
|
||||
@@ -77,9 +77,9 @@
|
||||
| ENG-PM-01 | workspace<br>按用户选择切换固定 Yarn 包管理器 | ENG-01 | Yarn 固定版本、唯一 yarn.lock、工具检查与安装维护文档 | 干净冻结安装、Node 测试和全部构建通过;记录解析差异和依赖用途,独立提交 | M | done |
|
||||
| ENG-02 | workspace<br>拆分只读检查并建立 PR CI | ENG-01, ENG-PM-01 | lint/lint:fix、PR 与主线检查、独立部署任务;遵循 github-ci-cd.md,PR/重构分支触发、最小权限及 workflow 静态检查 | 仓库内检查可执行且不改源码、不发布;required checks 的外部设置状态列入发布台账,不阻塞本地框架建设 | M | done |
|
||||
| ENG-03 | workspace<br>建立公共行为与单元测试入口 | ENG-02, BASE-03 | 保留现有 node:test,测试目录/夹具/统一入口 | 已有 19 项回归保留,旧版与候选可用同一夹具运行 | M | todo |
|
||||
| ENG-04 | workspace<br>建立类型测试基础 | ENG-02, BASE-05 | 根与分包 tsconfig、显式 TS 依赖、正反例测试 | 核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型 | M | todo |
|
||||
| ENG-04 | workspace<br>建立类型测试基础 | ENG-02, BASE-05 | 根与分包 tsconfig、显式 TS 依赖、正反例测试 | 核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型;复用 BASE-05 的四种消费模式,不以 skipLibCheck 掩盖 BASE-TYPE-01/03 | M | todo |
|
||||
| ENG-05 | workspace<br>建立真实浏览器测试服务 | ENG-03, BASE-04, BASE-08 | Playwright projects、本地 Range/失败媒体服务;复用 docs 页面/样本的状态隔离、错误采集与候选资源映射 | Chromium/Firefox/WebKit 的基础播放 smoke 和报告可执行;以媒体状态断言判定通过,区分轻量用例与真实编辑器交互 | M | todo |
|
||||
| ENG-06 | workspace<br>支持按包非交互与 JS/TS 构建 | ENG-02, BASE-05 | build/dev 入口解析、指定包参数、原交互保留 | 三种产物、Less/SVG/worker 和本地 8082 demo 正常 | H | todo |
|
||||
| ENG-06 | workspace<br>支持按包非交互与 JS/TS 构建 | ENG-02, BASE-05 | build/dev 入口解析、指定包参数、原交互保留 | 三种产物、Less/SVG/worker 和本地 8082 demo 正常;保持 BASE-05 的 AMD 同时写入全局行为及 i18n/legacy 入口 | H | todo |
|
||||
| ENG-07 | workspace<br>建立 tarball 消费与产物检查 | ENG-04, ENG-06 | 隔离 npm 消费 fixtures、API/声明/入口差分 | 不借 workspace 源码通过,能识别缺文件与默认导出变化 | H | todo |
|
||||
| ENG-08 | workspace<br>增加覆盖率、资源与性能报告 | ENG-03, ENG-05, BASE-06 | 覆盖率基线、资源清理断言、性能报告与阈值 | 关键生命周期分支有门槛,报告不靠无意义断言堆数量 | M | todo |
|
||||
| ENG-09 | workspace<br>建立全包依赖影响和文档检查 | ENG-07, DOC-04 | 共享核心/构建影响映射、文档及每任务完成提交的 Git 审计接入;契约-支持版本-测试 ID-命令-候选/报告-任务的覆盖索引 | 核心变化触发必需生态检查;原有 DOC-01 至 04 基线例外明确,后续 done 任务不能缺失独立 commit;计划/已执行/缺证据分开,识别缺少验证归属的公开契约 | M | todo |
|
||||
@@ -110,12 +110,12 @@
|
||||
| CORE-04 | artplayer<br>修复初始化与销毁生命周期 | CORE-03, ENG-05, BASE-07 | 构造失败回收、重复/重入 destroy、instances 修复 | 事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归 | H | todo |
|
||||
| CORE-05 | artplayer<br>区分输入配置和内部配置 | CORE-01, ENG-04 | Option/ResolvedOption、默认值与校验整理 | 配置合并/错误时机不变,JS 输入仍运行时校验 | M | todo |
|
||||
| CORE-06 | artplayer<br>建立内部媒体与宿主类型 | CORE-05 | 原生 video、canvas shim、UI host 类型 | 不更改公开 art.video 用法,不以 HTMLVideoElement 断言掩盖 shim | M | todo |
|
||||
| CORE-07 | artplayer<br>逐项协调公开声明差异 | CORE-02, CORE-06, BASE-07 | plugins/toggle/cue/setting/static 的旧新类型对照 | 每项有兼容重载/扩展或待决结论,不能删除旧类型样例 | H | todo |
|
||||
| CORE-07 | artplayer<br>逐项协调公开声明差异 | CORE-02, CORE-06, BASE-07 | plugins/toggle/cue/setting/static 的旧新类型对照 | 每项有兼容重载/扩展或待决结论,不能删除旧类型样例;处理 BASE-TYPE-01/04,分别保留旧合法声明消费并验证真实返回 | H | todo |
|
||||
| CORE-08 | artplayer<br>整理插件管理器与扩展类型 | CORE-03, CORE-07 | 注册流程、同步/异步结果、插件/事件扩展接口 | 旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归 | H | todo |
|
||||
| CORE-09 | artplayer<br>整理 URL 与切源操作 | CORE-04, CORE-06, CORE-08 | url/switch/customType 的操作身份及取消方案 | 并发切源、错误、同 URL、销毁和 Promise 结算有契约;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-01/02/06 并增加候选正向回归 | H | todo |
|
||||
| CORE-10 | artplayer<br>迁移播放与时间/状态属性 | CORE-09 | play/pause/toggle/seek/volume/rate/duration 等模块 | 旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过 | H | todo |
|
||||
| CORE-11 | artplayer<br>整理媒体事件、ready 与重连 | CORE-10 | 媒体转发、重连、UI 响应职责分离 | readiness 次数/顺序、过期重连、原错误参数有回归 | H | todo |
|
||||
| CORE-12 | artplayer<br>迁移模板及公开资源入口 | CORE-06, CORE-04, BASE-04 | template/icons/i18n/style、SSR 与 proxy 挂载 | DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器 | H | todo |
|
||||
| CORE-12 | artplayer<br>迁移模板及公开资源入口 | CORE-06, CORE-04, BASE-04 | template/icons/i18n/style、SSR 与 proxy 挂载 | DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器;复跑 BASE-05 的 SSR import/模板、浏览器 useSSR 节点复用和非浏览器构造错误 | H | todo |
|
||||
| CORE-13 | artplayer<br>迁移组件注册与控件 | CORE-12, CORE-08 | Component/control/layer/contextmenu 的资源与 TS 边界 | add/update/remove、name、selector、mounted/beforeUnmount 通过;处理 BASE-DOM-02 窄容器控件裁切,保留 controls.update 的旧 undefined 返回值 | H | todo |
|
||||
| CORE-14 | artplayer<br>拆分设置树、渲染和布局 | CORE-13 | setting model/selection/render/layout 的分批实现 | 不强行统一控件返回值,嵌套选择、默认高亮、更新清理通过;结合 BASE-DOM-02 验证窄容器设置定位 | H | todo |
|
||||
| CORE-15 | artplayer<br>迁移字幕获取、解析和渲染 | CORE-11, CORE-13 | subtitle 请求/track/DOM/URL 生命周期与类型 | 过期结果、偏移、cue 数组、原生全屏及错误路径通过 | H | todo |
|
||||
@@ -133,9 +133,9 @@
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-CHAPTER-01 | artplayer-plugin-chapter<br>核对包契约与历史用法 | BASE-05 | chapters 时间区间、update、name 和进度 DOM 清单 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | M | todo |
|
||||
| PKG-CHAPTER-02 | artplayer-plugin-chapter<br>建立特有行为与错误测试 | PKG-CHAPTER-01, ENG-03, ENG-05 | 区间重叠/空列表/边界 seek、hover 标题和 update 用例 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | M | todo |
|
||||
| PKG-CHAPTER-02 | artplayer-plugin-chapter<br>建立特有行为与错误测试 | PKG-CHAPTER-01, ENG-03, ENG-05 | 区间重叠/空列表/边界 seek、hover 标题和 update 用例 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例 | M | todo |
|
||||
| PKG-CHAPTER-03 | artplayer-plugin-chapter<br>整理内部职责与资源 | PKG-CHAPTER-02, ENG-07 | 在已有核心上整理时间区间计算、进度 DOM、事件/布局,合理简单代码可保持 | 不依赖新核心方法或大范围设置重构;旧 API/事件和资源通过,必要适配限包内 | M | todo |
|
||||
| PKG-CHAPTER-04 | artplayer-plugin-chapter<br>迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 基于既有公开核心类型的 Chapters/Option/Result 与 update 推导;JS/TS 工具链试点 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | M | todo |
|
||||
| PKG-CHAPTER-04 | artplayer-plugin-chapter<br>迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 基于既有公开核心类型的 Chapters/Option/Result 与 update 推导;JS/TS 工具链试点 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费 | M | todo |
|
||||
| PKG-CHAPTER-05 | artplayer-plugin-chapter<br>验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | todo |
|
||||
| PKG-CHAPTER-06 | artplayer-plugin-chapter<br>验证分发并同步文档 | PKG-CHAPTER-05, ENG-07 | chapter.js 示例、产物和变更记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
|
||||
|
||||
@@ -348,10 +348,10 @@
|
||||
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-TOOL-THUMB-01 | artplayer-tool-thumbnail<br>核对包契约与历史用法 | BASE-05 | 构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | todo |
|
||||
| PKG-TOOL-THUMB-01 | artplayer-tool-thumbnail<br>核对包契约与历史用法 | BASE-05 | 构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;核实 BASE-DIST-01 的 .esm.js 旧入口、缺失声明及无 main 的历史发布事实 | H | todo |
|
||||
| PKG-TOOL-THUMB-02 | artplayer-tool-thumbnail<br>建立特有行为与错误测试 | PKG-TOOL-THUMB-01, ENG-03, ENG-05 | 文件输入/拖放、抽帧网格、begin/end、进度/失败/重复任务 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo |
|
||||
| PKG-TOOL-THUMB-03 | artplayer-tool-thumbnail<br>整理内部职责与资源 | PKG-TOOL-THUMB-02, CORE-01 | 输入、抽帧队列、网格导出、URL/监听清理分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
|
||||
| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail<br>迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-TOOL-THUMB-04 | artplayer-tool-thumbnail<br>迁移自有源码和公开类型 | PKG-TOOL-THUMB-03, ENG-04, ENG-06, CORE-07 | 补齐真实 API 声明,自有 emitter/utils TS 化 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证 | H | todo |
|
||||
| PKG-TOOL-THUMB-05 | artplayer-tool-thumbnail<br>验证新旧核心和组合 | PKG-TOOL-THUMB-04, CORE-22 | 工具独立浏览器使用,生成缩略图在核心中显示 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-TOOL-THUMB-06 | artplayer-tool-thumbnail<br>验证分发并同步文档 | PKG-TOOL-THUMB-05, ENG-07 | tool.thumbnail.js、历史 ESM 兼容文件、types 路径和 tarball | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
|
||||
@@ -359,7 +359,7 @@
|
||||
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| SITE-01 | artplayer-vitepress<br>清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径 | M | todo |
|
||||
| SITE-01 | artplayer-vitepress<br>清点文档/示例/生成链 | BASE-04, BASE-05 | 中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向 | 所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径;明确 BASE-05 所记录站点实际分发与未设 private 的 manifest 意图 | M | todo |
|
||||
| SITE-02 | artplayer-vitepress<br>整理声明与示例生成器 | SITE-01, ENG-04, ENG-06 | build-ts/build-test 生成链的可验证 TS 脚本 | 不靠字符串拼接掩盖声明错误,生成示例有真实断言或仅标 smoke;替换固定 100ms 成功判定,明确异步错误、清理和生成覆盖限制 | M | todo |
|
||||
| SITE-03 | artplayer-vitepress<br>整理 i18n/文档/LLM 生成流程 | SITE-02 | build-i18n/build-docs/build-llm/trans-docs 的任务边界和错误处理 | 原命令兼容、生成可复现,翻译步骤不隐式运行远程服务 | M | todo |
|
||||
| SITE-04 | artplayer-vitepress<br>交叉核对逐包持续维护的文档 | CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 已随实现更新的中文/英文 API、包内实现地图、旧 JS 示例及已知能力限制的全包核对 | 未把缺环境的能力写成已验证,静态核对不等待设备任务;最终 demo 仍由 EX-03 验收 | M | todo |
|
||||
@@ -384,7 +384,7 @@
|
||||
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| REL-08 | workspace<br>提前建立逐包发布准入台账 | BASE-08, ENG-07 | 每批包/版本/源码/锁文件/工具/tarball integrity、必需测试/设备证据、限制和回退映射 | 受影响能力缺证据明确阻止对应批次;无关批次可独立准备,旧证据在候选内容变化后失效;站点采用真实构建/URL/资源验收,npm 分发依据历史核实,不从版本清单推断新增发布范围 | H | todo |
|
||||
| REL-01 | workspace<br>提前确定分包版本与差异方案 | REL-08, CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 每包版本/变更日志/依赖/类型差异方案和独立准入状态;按 version-policy.md 冻结各包下一 major(minor/patch 归零)并核实 registry 占用 | 所有包有方案和剩余门槛,未决项明确阻止相应发布;本步骤不声称已经可发布;全部包有 major 目标,版本冲突明确处理,独立准备任务同步 manifest/锁/依赖/日志 | H | todo |
|
||||
| REL-01 | workspace<br>提前确定分包版本与差异方案 | REL-08, CORE-21, SITE-03, PKG-CHAPTER-04, PKG-AMBILIGHT-04, PKG-AUDIO-04, PKG-AUTO-THUMB-04, PKG-VTT-THUMB-04, PKG-HLS-04, PKG-DASH-04, PKG-MULTI-SUB-04, PKG-JASSUB-04, PKG-MASK-04, PKG-ASR-04, PKG-ADS-04, PKG-VAST-04, PKG-CAST-04, PKG-DPIP-04, PKG-CANVAS-04, PKG-IFRAME-04, PKG-TOOL-THUMB-04, PKG-DANMUKU-06, PKG-MB-08 | 每包版本/变更日志/依赖/类型差异方案和独立准入状态;按 version-policy.md 冻结各包下一 major(minor/patch 归零)并核实 registry 占用 | 所有包有方案和剩余门槛,未决项明确阻止相应发布;本步骤不声称已经可发布;全部包有 major 目标,版本冲突明确处理,独立准备任务同步 manifest/锁/依赖/日志;按 BASE-05 区分 21 库与文档站分发,不机械把文档站当库上传 npm | H | todo |
|
||||
| REL-09 | workspace<br>落实全包下一 major 版本及依赖元数据 | REL-01, DOC-11 | 22 包版本目标落实、适用锁文件/依赖范围/示例/变更日志同步,核实 registry 版本占用 | 版本与 policy 一致,旧核心支持保留;必要拆子任务各自提交,候选构建前完成,不执行 publish | H | todo |
|
||||
| REL-02 | workspace<br>生成候选 tarball 并验证新旧组合 | REL-01, CORE-22, ENG-07, REL-09 | 各候选本地 tarball/integrity 与新旧核心/插件消费者验证报告,外部门槛单独标注 | 隔离安装和可自动化组合通过;设备结论不伪造,最终发布绑定同一候选内容;在目标 major 版本确定后构建 pack,不在测试后改版本 | H | todo |
|
||||
| REL-03 | workspace<br>完成真机、外部 SDK 与压力验收 | REL-02, EX-03, SITE-06, MOD-05 | Safari/移动/PiP/Cast/IMA/模型/长播放完整报告 | 包集成与全项目真机/SDK/压力结论齐全才完成本汇总;独立批次先建立自己的完整门槛子任务 | H | todo |
|
||||
@@ -421,6 +421,7 @@
|
||||
- BASE-02: [记录](changes/2026-09-10-BASE-02-public-api.md) [记录](baselines/public-api.json) [记录](baselines/api-coverage.md)
|
||||
- BASE-03: [记录](changes/2026-09-10-BASE-03-lifecycle.md) [记录](baselines/lifecycle.json) [记录](baselines/lifecycle-coverage.md)
|
||||
- BASE-04: [记录](changes/2026-09-10-BASE-04-dom.md) [记录](baselines/dom.json) [记录](baselines/dom-coverage.md) [记录](baselines/demo-inventory.json)
|
||||
- BASE-05: [记录](changes/2026-09-10-BASE-05-consumers.md) [记录](baselines/consumer-coverage.md) [记录](baselines/consumers.json) [记录](baselines/ssr.json) [记录](baselines/distribution.json)
|
||||
- ENG-01: [记录](changes/2026-09-10-ENG-01-reproducible-toolchain.md) [记录](baselines/toolchain-validation.json)
|
||||
- ENG-PM-01: [记录](changes/2026-09-10-ENG-PM-01-yarn-toolchain.md) [记录](baselines/yarn-validation.json)
|
||||
- ENG-02: [记录](changes/2026-09-10-ENG-02-readonly-ci.md) [记录](baselines/ci-validation.json)
|
||||
@@ -1,5 +1,11 @@
|
||||
# 进度与证据
|
||||
|
||||
## 当前实施:BASE-05 分发与类型消费者完成
|
||||
|
||||
固定发布包隔离消费的 18 项 Node runtime 检查通过;16 项 strict TS 场景中 8 项编译通过、8 项历史失败独立登记,未掩盖 NodeNext ESM 与 chapter 声明问题。内置浏览器两轮各 5 项 SSR 节点复用/媒体/清理检查通过。全包 manifest 与 118 个工作区 dist/types 资源已清点,thumbnail tool 和站点分发差异有后续责任。见 [BASE-05](changes/2026-09-10-BASE-05-consumers.md)。
|
||||
|
||||
当前 214 项任务,22 完成、192 待办。BASE-04 已提交 2b7054d8;本轮独立提交 BASE-05。没有生产 TS 迁移、推送或发布;下一任务 BASE-06 性能与资源基线。
|
||||
|
||||
## 当前实施:BASE-04 DOM 与输入基线完成
|
||||
|
||||
内置浏览器最终两轮 16 项检查通过,模板/类名/变量、用户样式、控件/设置、Tab/热键与网页全屏已留证。320px 裁切和控件 Tab 不可达已分配 CORE-13/14/23,未把历史问题当成正常兼容要求。29 示例、36 HTML 和 22 包的路径已映射,官方 docs 页面仍待真实运行。见 [BASE-04](changes/2026-09-10-BASE-04-dom.md)。
|
||||
|
||||
@@ -37,7 +37,7 @@ function sendBytes(req, res, bytes, filename) {
|
||||
const server = http.createServer(async (req, res) => {
|
||||
try {
|
||||
const url = new URL(req.url, `http://127.0.0.1:${port}`)
|
||||
const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle', '/reports/dom': 'dom' }[url.pathname]
|
||||
const reportName = { '/reports/api': 'api', '/reports/lifecycle': 'lifecycle', '/reports/dom': 'dom', '/reports/ssr': 'ssr' }[url.pathname]
|
||||
if (req.method === 'POST' && reportName) {
|
||||
assert.equal(req.headers.origin, `http://127.0.0.1:${port}`, 'Unexpected report origin')
|
||||
const chunks = []
|
||||
@@ -62,7 +62,7 @@ const server = http.createServer(async (req, res) => {
|
||||
if (reportName === 'lifecycle') {
|
||||
report.capture.media = Object.fromEntries(['video.mp4', 'video2.mp4'].map(name => [name, hash(fs.readFileSync(path.join(root, 'docs/assets/sample', name)))]))
|
||||
}
|
||||
if (reportName === 'dom') report.capture.media = { 'video.mp4': hash(fs.readFileSync(path.join(root, 'docs/assets/sample/video.mp4'))) }
|
||||
if (['dom', 'ssr'].includes(reportName)) report.capture.media = { 'video.mp4': hash(fs.readFileSync(path.join(root, 'docs/assets/sample/video.mp4'))) }
|
||||
const output = path.join(refactorDir, '.cache/reports')
|
||||
fs.mkdirSync(output, { recursive: true })
|
||||
fs.writeFileSync(path.join(output, `${reportName}.json`), `${JSON.stringify(report, null, 2)}\n`)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ts from 'typescript'
|
||||
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
const root = path.resolve(refactorDir, '..')
|
||||
const fixtures = path.join(refactorDir, 'fixtures/consumers')
|
||||
const parent = path.join(refactorDir, '.cache/consumers')
|
||||
const reportPath = path.join(refactorDir, 'baselines/consumers.json')
|
||||
const fixtureNames = ['runtime.cjs', 'public.ts', 'language.ts', 'optional-chapter.ts', 'legacy-plugin.ts']
|
||||
export const runtimeChecks = [
|
||||
'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', 'DIST.i18n-cjs-esm-global',
|
||||
'DIST.exports-boundary', 'DIST.direct-esm-file',
|
||||
]
|
||||
|
||||
export async function runConsumers() {
|
||||
const releases = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases
|
||||
fs.mkdirSync(parent, { recursive: true })
|
||||
const dir = fs.mkdtempSync(path.join(parent, 'published-'))
|
||||
try {
|
||||
const provenance = []
|
||||
for (const release of releases) {
|
||||
const archive = await ensureArchive(release)
|
||||
for (const [member, digest] of Object.entries(release.files)) {
|
||||
const bytes = readMember(archive, member)
|
||||
assert.equal(hash(bytes), digest, `Published member changed: ${member}`)
|
||||
const target = path.resolve(dir, 'node_modules', release.name, member.slice('package/'.length))
|
||||
assert(target.startsWith(path.join(dir, 'node_modules', release.name) + path.sep), 'Unsafe consumer path')
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true })
|
||||
fs.writeFileSync(target, bytes)
|
||||
}
|
||||
provenance.push({ name: release.name, version: release.version, integrity: release.integrity, sha256: release.sha256 })
|
||||
}
|
||||
for (const name of fixtureNames) fs.copyFileSync(path.join(fixtures, name), path.join(dir, name))
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), '{"private":true,"type":"module"}\n')
|
||||
const runtime = JSON.parse(execFileSync(process.execPath, [path.join(dir, 'runtime.cjs')], { cwd: dir, encoding: 'utf8', timeout: 20000, maxBuffer: 1024 * 1024 }))
|
||||
assert.equal(ts.version, JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).devDependencies.typescript, 'Unexpected TypeScript compiler')
|
||||
const types = []
|
||||
for (const [mode, module, moduleResolution, packageType] of [
|
||||
['node10-commonjs', ts.ModuleKind.CommonJS, ts.ModuleResolutionKind.Node10, 'commonjs'],
|
||||
['nodenext-cjs', ts.ModuleKind.NodeNext, ts.ModuleResolutionKind.NodeNext, 'commonjs'],
|
||||
['nodenext-esm', ts.ModuleKind.NodeNext, ts.ModuleResolutionKind.NodeNext, 'module'],
|
||||
['bundler-esm', ts.ModuleKind.ESNext, ts.ModuleResolutionKind.Bundler, 'module'],
|
||||
]) {
|
||||
fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ private: true, type: packageType }))
|
||||
for (const name of fixtureNames.filter(name => name.endsWith('.ts'))) {
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], target: ts.ScriptTarget.ES2020, module, moduleResolution, esModuleInterop: true }
|
||||
const program = ts.createProgram([path.join(dir, name)], options)
|
||||
const declarations = program.getSourceFiles().filter(file => /\/(?:artplayer|artplayer-plugin-chapter)\/types\//.test(file.fileName.replaceAll('\\', '/')) && file.isDeclarationFile)
|
||||
assert(declarations.every(file => file.fileName.startsWith(path.join(dir, 'node_modules').replaceAll('\\', '/'))), 'Types escaped isolated published packages')
|
||||
if (name !== 'legacy-plugin.ts' || mode !== 'node10-commonjs') assert(declarations.length > 0, 'Published declarations were not loaded')
|
||||
const diagnostics = ts.getPreEmitDiagnostics(program).map(d => ({
|
||||
file: d.file ? path.relative(dir, d.file.fileName).replaceAll('\\', '/') : null,
|
||||
line: d.file && d.start !== undefined ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null,
|
||||
code: d.code, message: ts.flattenDiagnosticMessageText(d.messageText, '\n').replaceAll(dir.replaceAll('\\', '/'), '<consumer>').replaceAll(dir, '<consumer>'),
|
||||
}))
|
||||
types.push({ mode, packageType, fixture: name, diagnostics, declarations: declarations.map(file => path.relative(dir, file.fileName).replaceAll('\\', '/')).sort() })
|
||||
}
|
||||
}
|
||||
return { schemaVersion: 1, task: 'BASE-05', sourceCommit: '2b7054d8', capturedAt: new Date().toISOString(), node: process.version, typescript: ts.version, releases: provenance,
|
||||
compilerOptions: { strict: true, skipLibCheck: false, noEmit: true, types: [], lib: ['es2020', 'dom'], target: 'es2020', esModuleInterop: true },
|
||||
fixtureHashAlgorithm: 'sha256-lf', fixtures: Object.fromEntries(fixtureNames.map(name => [name, hash(fs.readFileSync(path.join(fixtures, name), 'utf8').replaceAll('\r\n', '\n'))])), runtime, types }
|
||||
}
|
||||
finally {
|
||||
const resolved = path.resolve(dir)
|
||||
assert(resolved.startsWith(path.resolve(parent) + path.sep) && path.basename(resolved).startsWith('published-'), 'Unsafe consumer cleanup')
|
||||
fs.rmSync(resolved, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
export function consumerProfile(report) {
|
||||
return { releases: report.releases, fixtures: report.fixtures, typescript: report.typescript, compilerOptions: report.compilerOptions, runtime: report.runtime, types: report.types }
|
||||
}
|
||||
|
||||
export function validateConsumerReport(report) {
|
||||
assert.equal(report.schemaVersion, 1)
|
||||
assert.equal(report.task, 'BASE-05')
|
||||
assert.deepEqual(report.runtime.checks, runtimeChecks, 'Missing runtime consumer checks')
|
||||
assert.equal(report.fixtureHashAlgorithm, 'sha256-lf')
|
||||
assert.equal(report.compilerOptions.skipLibCheck, false)
|
||||
assert.equal(report.compilerOptions.strict, true)
|
||||
const releases = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases
|
||||
assert.deepEqual(report.releases, releases.map(({ name, version, integrity, sha256 }) => ({ name, version, integrity, sha256 })), 'Wrong published consumers')
|
||||
assert.equal(report.types.length, 16)
|
||||
const cases = new Set(report.types.map(item => `${item.mode}/${item.fixture}`))
|
||||
assert.equal(cases.size, 16)
|
||||
for (const mode of ['node10-commonjs', 'nodenext-cjs', 'nodenext-esm', 'bundler-esm']) {
|
||||
for (const fixture of fixtureNames.filter(name => name.endsWith('.ts'))) {
|
||||
const result = report.types.find(item => item.mode === mode && item.fixture === fixture)
|
||||
assert(result, 'Missing TS consumer case')
|
||||
if (mode !== 'nodenext-esm' && ['public.ts', 'language.ts'].includes(fixture)) assert.deepEqual(result.diagnostics, [], 'Previously valid consumer failed')
|
||||
if (mode !== 'nodenext-esm' && fixture === 'optional-chapter.ts') assert.deepEqual(result.diagnostics.map(d => d.code), [2554], 'Historical optional-argument result changed')
|
||||
if (mode === 'node10-commonjs' && fixture === 'legacy-plugin.ts') assert.deepEqual(result.diagnostics.map(d => d.code), [2307], 'Historical legacy resolution changed')
|
||||
}
|
||||
}
|
||||
for (const name of fixtureNames) assert.equal(report.fixtures[name], hash(fs.readFileSync(path.join(fixtures, name), 'utf8').replaceAll('\r\n', '\n')), 'Consumer fixture changed; review baseline')
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const result = await runConsumers()
|
||||
validateConsumerReport(result)
|
||||
if (process.argv.includes('--capture')) {
|
||||
assert(!fs.existsSync(reportPath), 'Do not overwrite frozen consumer results')
|
||||
fs.writeFileSync(reportPath, `${JSON.stringify(result, null, 2)}\n`)
|
||||
}
|
||||
else if (process.argv.includes('--inspect')) {
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
else {
|
||||
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --capture, --inspect or --check')
|
||||
const frozen = JSON.parse(fs.readFileSync(reportPath, 'utf8'))
|
||||
validateConsumerReport(frozen)
|
||||
assert.deepEqual(consumerProfile(result), consumerProfile(frozen), 'Published consumer behavior differs')
|
||||
console.log(`Published consumers rerun: ${result.runtime.checks.length} runtime checks, ${result.types.length} strict TS cases; historical diagnostics remain explicit`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import { consumerProfile, runConsumers, validateConsumerReport } from './consumers.mjs'
|
||||
import { validateDistribution } from './distribution.mjs'
|
||||
import { refactorDir } from './releases.mjs'
|
||||
import { validateSsr } from './ssr.mjs'
|
||||
|
||||
test('Published packages rerun isolated runtime and strict TypeScript consumers', async () => {
|
||||
const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/consumers.json'), 'utf8'))
|
||||
validateConsumerReport(frozen)
|
||||
const result = await runConsumers()
|
||||
validateConsumerReport(result)
|
||||
assert.deepEqual(consumerProfile(result), consumerProfile(frozen))
|
||||
for (const mutate of [
|
||||
value => value.runtime.checks.pop(),
|
||||
value => value.compilerOptions.skipLibCheck = true,
|
||||
value => value.types.find(item => item.mode === 'bundler-esm' && item.fixture === 'public.ts').diagnostics.push({ code: 2322 }),
|
||||
value => value.releases[0].version = 'wrong',
|
||||
]) {
|
||||
const changed = structuredClone(frozen)
|
||||
mutate(changed)
|
||||
assert.throws(() => validateConsumerReport(changed))
|
||||
}
|
||||
})
|
||||
|
||||
test('Distribution inventory preserves all packages and the thumbnail discrepancy', () => {
|
||||
const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/distribution.json'), 'utf8'))
|
||||
validateDistribution(frozen)
|
||||
const changed = structuredClone(frozen)
|
||||
changed.packages.pop()
|
||||
assert.throws(() => validateDistribution(changed))
|
||||
})
|
||||
|
||||
test('Browser SSR evidence requires node identity, real metadata and cleanup', () => {
|
||||
const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/ssr.json'), 'utf8'))
|
||||
validateSsr(frozen.report)
|
||||
const changed = structuredClone(frozen.report)
|
||||
changed.observations.videoWidth = 0
|
||||
assert.throws(() => validateSsr(changed))
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hash, refactorDir } from './releases.mjs'
|
||||
|
||||
const root = path.resolve(refactorDir, '..')
|
||||
const filename = path.join(refactorDir, 'baselines/distribution.json')
|
||||
function files(dir) {
|
||||
if (!fs.existsSync(dir)) return []
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => entry.isDirectory() ? files(path.join(dir, entry.name)) : [path.join(dir, entry.name)])
|
||||
}
|
||||
function targets(value, field) {
|
||||
if (typeof value === 'string') return [{ field, path: value }]
|
||||
if (value && typeof value === 'object') return Object.entries(value).flatMap(([key, child]) => targets(child, `${field}.${key}`))
|
||||
return []
|
||||
}
|
||||
export function captureDistribution() {
|
||||
const initial = JSON.parse(fs.readFileSync(path.join(refactorDir, 'package-inventory.json'), 'utf8'))
|
||||
return { schemaVersion: 1, task: 'BASE-05', capturedAt: new Date().toISOString(), sourceCommit: '2b7054d8',
|
||||
note: 'Workspace manifest and observed resource inventory. Existing dist files may be stale; presence and hashes are not proof of a fresh build or npm publication. Only core/chapter published tarballs are verified in releases.json and consumers.json.',
|
||||
packages: initial.packages.map(pkg => {
|
||||
const dir = path.join(root, 'packages', pkg.name)
|
||||
const manifestBytes = fs.readFileSync(path.join(dir, 'package.json'))
|
||||
const manifest = JSON.parse(manifestBytes)
|
||||
const resources = ['dist', 'types'].flatMap(name => files(path.join(dir, name))).map(file => ({ path: path.relative(dir, file).replaceAll('\\', '/'), bytes: fs.statSync(file).size, sha256: hash(fs.readFileSync(file)) })).sort((a, b) => a.path.localeCompare(b.path))
|
||||
const declaredTargets = ['main', 'module', 'legacy', 'types', 'typings', 'exports', 'typesVersions', 'browser', 'unpkg', 'jsdelivr'].flatMap(field => targets(manifest[field], field)).map(target => {
|
||||
const normalized = target.path.replace(/^\.\//, '')
|
||||
const regex = new RegExp(`^${normalized.split('*').map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*')}$`)
|
||||
return { ...target, observedMatches: resources.filter(resource => regex.test(resource.path)).map(resource => resource.path) }
|
||||
})
|
||||
const site = pkg.name === 'artplayer-vitepress'
|
||||
return { name: pkg.name, kind: site ? 'documentation-site' : 'npm-library', manifest, manifestSha256Lf: hash(manifestBytes.toString().replaceAll('\r\n', '\n')), declaredTargets, resources,
|
||||
sourceAssets: files(path.join(dir, 'src')).filter(file => !/\.[cm]?[jt]sx?$/.test(file)).map(file => path.relative(dir, file).replaceAll('\\', '/')).sort(),
|
||||
buildOutputs: site ? ['docs/document/** via vitepress outDir'] : [`dist/${pkg.name}.js`, `dist/${pkg.name}.legacy.js`, `dist/${pkg.name}.mjs`, `docs/compiled/${pkg.name}.{js,legacy.js,mjs} copied by root build`],
|
||||
distributionStatus: site ? 'Actual workflow builds a static site; no library entrypoints. Manifest lacks private:true; publication intent needs SITE/REL confirmation, not an automatic npm release.' : 'Workspace inventory; per-package published contract remains required unless listed in releases.json.',
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
export function validateDistribution(report) {
|
||||
const initial = JSON.parse(fs.readFileSync(path.join(refactorDir, 'package-inventory.json'), 'utf8'))
|
||||
assert.equal(report.schemaVersion, 1)
|
||||
assert.deepEqual(report.packages.map(pkg => pkg.name), initial.packages.map(pkg => pkg.name), 'Distribution package coverage changed')
|
||||
for (const pkg of report.packages) {
|
||||
assert.equal(pkg.manifest.name, pkg.name)
|
||||
if (pkg.kind === 'npm-library') assert(pkg.resources.length > 0 && pkg.declaredTargets.length > 0, `Missing library resources: ${pkg.name}`)
|
||||
for (const resource of pkg.resources) assert(!resource.path.startsWith('/') && !resource.path.split('/').includes('..') && /^[a-f0-9]{64}$/.test(resource.sha256), 'Invalid resource provenance')
|
||||
}
|
||||
assert.equal(report.packages.filter(pkg => pkg.kind === 'documentation-site').length, 1)
|
||||
const thumbnail = report.packages.find(pkg => pkg.name === 'artplayer-tool-thumbnail')
|
||||
assert(thumbnail.declaredTargets.some(target => target.path.endsWith('.esm.js') && !target.observedMatches.length), 'Missing thumbnail entrypoint discrepancy')
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
if (process.argv.includes('--capture')) {
|
||||
assert(!fs.existsSync(filename), 'Do not overwrite the historical distribution inventory')
|
||||
const report = captureDistribution()
|
||||
validateDistribution(report)
|
||||
fs.writeFileSync(filename, `${JSON.stringify(report, null, 2)}\n`)
|
||||
}
|
||||
else {
|
||||
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --capture or --check')
|
||||
const report = JSON.parse(fs.readFileSync(filename, 'utf8'))
|
||||
validateDistribution(report)
|
||||
console.log(`Frozen distribution inventory: ${report.packages.length} packages; current build and npm contents are not revalidated by this command`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { validatePublishedCapture } from './api.mjs'
|
||||
import { hash, refactorDir } from './releases.mjs'
|
||||
|
||||
export function validateSsr(report) {
|
||||
assert.equal(report.kind, 'ssr')
|
||||
validatePublishedCapture(report, 'ssr')
|
||||
assert.deepEqual(report.errors, [])
|
||||
assert.deepEqual(report.unhandled, [])
|
||||
assert.deepEqual(report.checks, ['SSR.browser-reuses-player', 'SSR.browser-reuses-video', 'SSR.browser-ready', 'SSR.browser-plugin', 'SSR.browser-cleanup'].map(id => ({ id, passed: true })))
|
||||
assert.equal(report.capture.media['video.mp4'], hash(fs.readFileSync(path.resolve(refactorDir, '../docs/assets/sample/video.mp4'))))
|
||||
assert(report.observations.videoWidth > 0 && report.observations.duration > 0)
|
||||
assert.equal(report.observations.serverMarker, 'preserved')
|
||||
}
|
||||
export function ssrProfile(report) {
|
||||
return { checks: report.checks, observations: report.observations }
|
||||
}
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/ssr.json'), 'utf8'))
|
||||
validateSsr(frozen.report)
|
||||
const index = process.argv.indexOf('--compare')
|
||||
if (index !== -1) {
|
||||
assert(process.argv[index + 1], '--compare requires a report path')
|
||||
const report = JSON.parse(fs.readFileSync(process.argv[index + 1], 'utf8'))
|
||||
validateSsr(report)
|
||||
assert.deepEqual(ssrProfile(report), ssrProfile(frozen.report))
|
||||
console.log('Published browser template reuse matches SSR baseline')
|
||||
}
|
||||
else {
|
||||
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --check or --compare <report>')
|
||||
console.log('Frozen browser SSR evidence verified; browser was not rerun')
|
||||
}
|
||||
}
|
||||
+19
-13
@@ -346,11 +346,17 @@
|
||||
"dependsOn": [
|
||||
"BASE-01"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "全包 manifest/资源路径清单;核心/试点的 UMD/AMD/ESM/CJS/legacy/i18n 和类型消费者基线;登记契约/版本/固定测试 ID/命令/报告与负责任务;区分库/npm 与站点实际分发类别,SSR import/模板/非浏览器构造错误分别捕获",
|
||||
"acceptance": "核心/试点隔离消费可运行,其他包由各自契约任务验证,thumbnail tool 差异已登记",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-10-BASE-05-consumers.md",
|
||||
"baselines/consumer-coverage.md",
|
||||
"baselines/consumers.json",
|
||||
"baselines/ssr.json",
|
||||
"baselines/distribution.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "BASE-06",
|
||||
@@ -383,7 +389,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "销毁/切源/类型差异、vendored 许可和外部 SDK 清单",
|
||||
"acceptance": "已确认与待复现分开,每项有负责任务及兼容处理路线",
|
||||
"acceptance": "已确认与待复现分开,每项有负责任务及兼容处理路线;纳入 BASE-TYPE-01/02/03/04 和 BASE-DIST-01 的发布/声明/工作区证据",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -492,7 +498,7 @@
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
"deliverable": "根与分包 tsconfig、显式 TS 依赖、正反例测试",
|
||||
"acceptance": "核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型",
|
||||
"acceptance": "核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型;复用 BASE-05 的四种消费模式,不以 skipLibCheck 掩盖 BASE-TYPE-01/03",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -527,7 +533,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "build/dev 入口解析、指定包参数、原交互保留",
|
||||
"acceptance": "三种产物、Less/SVG/worker 和本地 8082 demo 正常",
|
||||
"acceptance": "三种产物、Less/SVG/worker 和本地 8082 demo 正常;保持 BASE-05 的 AMD 同时写入全局行为及 i18n/legacy 入口",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -814,7 +820,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "plugins/toggle/cue/setting/static 的旧新类型对照",
|
||||
"acceptance": "每项有兼容重载/扩展或待决结论,不能删除旧类型样例",
|
||||
"acceptance": "每项有兼容重载/扩展或待决结论,不能删除旧类型样例;处理 BASE-TYPE-01/04,分别保留旧合法声明消费并验证真实返回",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -899,7 +905,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "template/icons/i18n/style、SSR 与 proxy 挂载",
|
||||
"acceptance": "DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器",
|
||||
"acceptance": "DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器;复跑 BASE-05 的 SSR import/模板、浏览器 useSSR 节点复用和非浏览器构造错误",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -1129,7 +1135,7 @@
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
"deliverable": "区间重叠/空列表/边界 seek、hover 标题和 update 用例",
|
||||
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言",
|
||||
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言;修正 BASE-TYPE-02 的可选参数声明并补正反例",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -1164,7 +1170,7 @@
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
"deliverable": "基于既有公开核心类型的 Chapters/Option/Result 与 update 推导;JS/TS 工具链试点",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -3218,7 +3224,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "构造/defaults/事件/方法/历史拼写、.esm.js 入口和缺失 types",
|
||||
"acceptance": "源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结",
|
||||
"acceptance": "源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;核实 BASE-DIST-01 的 .esm.js 旧入口、缺失声明及无 main 的历史发布事实",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -3272,7 +3278,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "补齐真实 API 声明,自有 emitter/utils TS 化",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;为 BASE-DIST-01 提供兼容入口/声明及消费者验证",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -3323,7 +3329,7 @@
|
||||
"status": "todo",
|
||||
"risk": "M",
|
||||
"deliverable": "中英文文档、插件页面、demo URL、编辑器声明与生成目录清单;六类 HTML 入口、prod/libs/code/example 加载行为及移动重定向",
|
||||
"acceptance": "所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径",
|
||||
"acceptance": "所有公开 API/插件有对应页面或明确补充任务;核实 BASE-DEMO-01 的历史 thumbnail 插件来源及 29 示例/36 HTML 路径;明确 BASE-05 所记录站点实际分发与未设 private 的 manifest 意图",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
@@ -3642,7 +3648,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "每包版本/变更日志/依赖/类型差异方案和独立准入状态;按 version-policy.md 冻结各包下一 major(minor/patch 归零)并核实 registry 占用",
|
||||
"acceptance": "所有包有方案和剩余门槛,未决项明确阻止相应发布;本步骤不声称已经可发布;全部包有 major 目标,版本冲突明确处理,独立准备任务同步 manifest/锁/依赖/日志",
|
||||
"acceptance": "所有包有方案和剩余门槛,未决项明确阻止相应发布;本步骤不声称已经可发布;全部包有 major 目标,版本冲突明确处理,独立准备任务同步 manifest/锁/依赖/日志;按 BASE-05 区分 21 库与文档站分发,不机械把文档站当库上传 npm",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user