mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
test(release): [REL-04] checkpoint independent installed package rollback
This commit is contained in:
1 parent
09459955c6
commit
aa29dc9212
14 files changed
+4717
-5
No files matched your search
+3
-1
@@ -45,7 +45,7 @@
|
||||
"check:toolchain": "node scripts/check-toolchain.mjs",
|
||||
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor,browser-validation}/**/*.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/vconsole-lifecycle.test.js test/browser-validation.test.js",
|
||||
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js test/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js test/pages-artifact.test.js test/site-notices.test.js test/vconsole-lifecycle.test.js test/browser-validation.test.js test/rollback-files.test.js",
|
||||
"test:baseline": "node --test refactor/scripts/*.test.mjs",
|
||||
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn check:release-ledger && yarn 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: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:site-notices && yarn build:test && yarn build:docs && yarn test:imports",
|
||||
@@ -60,6 +60,8 @@
|
||||
"test:browser:install": "playwright install chromium firefox webkit",
|
||||
"test:browser:report": "playwright show-report refactor/.cache/browser/html",
|
||||
"test:package": "node scripts/package-check.mjs",
|
||||
"test:rollback": "node scripts/rollback-consumer.mjs",
|
||||
"test:rollback:browser": "playwright test --config playwright.rollback.config.js",
|
||||
"test:package:release": "node scripts/package-check.mjs --release",
|
||||
"build:types": "node scripts/build-types.mjs --write",
|
||||
"check:types": "node scripts/build-types.mjs --check",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import process from 'node:process'
|
||||
import { defineConfig } from '@playwright/test'
|
||||
import base from './playwright.config.js'
|
||||
import { verifyRollbackArtifacts } from './scripts/rollback-artifacts.mjs'
|
||||
|
||||
verifyRollbackArtifacts(process.cwd(), process.env.ARTPLAYER_BROWSER_ARTIFACTS)
|
||||
export default defineConfig(base, {
|
||||
testMatch: ['**/playback.spec.js'],
|
||||
grep: /candidate: real playback/,
|
||||
retries: 0,
|
||||
workers: 2,
|
||||
outputDir: 'refactor/.cache/rollback-browser/results',
|
||||
reporter: [
|
||||
['list'],
|
||||
['json', { outputFile: 'refactor/.cache/rollback-browser/report.json' }],
|
||||
['html', { outputFolder: 'refactor/.cache/rollback-browser/html', open: 'never' }],
|
||||
],
|
||||
})
|
||||
@@ -29,6 +29,7 @@
|
||||
| [docs 页面与编辑器测试](docs-browser-testing.md) | 复用已有 HTML、加载版本、隔离状态和补强文档 smoke |
|
||||
| [多轮复盘与 npm 准入](release-reviews.md) | Chrome 验证分工、三轮全局复盘、问题闭环和候选发布门槛 |
|
||||
| [逐包发布准入台账](release-ledger.md) | 22包候选/证据绑定、源码和产物指纹、任务风险/许可阻断及严格预检 |
|
||||
| [回退演练与维护](rollback-rehearsal.md) | 独立核心/插件回退、冻结安装文件检查、真实播放及剩余发布门槛 |
|
||||
| [工具链与发布](toolchain-release.md) | TypeScript、Bun、构建、版本管理和发布回退 |
|
||||
| [已实现的开发环境](toolchain-setup.md) | Node/Yarn 固定版本、锁文件、安装命令和实际验证范围 |
|
||||
| [全包大版本策略](version-policy.md) | 每包分别升级一个 major 的目标清单、兼容要求和版本落地步骤 |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,29 @@
|
||||
# REL-04 核心与 Chapter 实际安装回退检查点
|
||||
|
||||
任务保持 doing,未增加完成任务数。生产源码、声明、分发文件及根锁未修改。
|
||||
|
||||
新增 `test:rollback`、`test:rollback:browser`,复用现有包构建和播放器浏览器用例。
|
||||
安装流程、文件完整性校验、浏览器产物来源验证分别拆分,维护说明见
|
||||
[回退演练](../rollback-rehearsal.md)。没有引入新依赖,使用已固定 Node/Yarn/Playwright。
|
||||
|
||||
## 验证
|
||||
|
||||
- `yarn test:package`:隔离生产构建与安装通过,36 项候选运行时检查,5/5 旧类型模式、8/8 精确类型模式。
|
||||
- `yarn test:rollback`:七次连续安装/升级/回退均通过,237 项运行时检查;所有文件与冻结锁恢复一致。
|
||||
- 最终安装产物在五种状态各执行三引擎真实播放,共 15/15,通过解码像素、播放/暂停/拖动/切源/销毁与 Chapter DOM 检查。
|
||||
- Node 文件验证负例、相关工程回归、scoped lint、plan 检查及严格工具链结果随机器证据保存。
|
||||
- 普通 installed runner 最初拒绝 REL-04 报告(它要求 ENG-07);这次尝试未运行浏览器。不改变其约束,新增独立回退验证入口后测试通过。初次七步报告及拒绝日志仍保存在 cache。
|
||||
|
||||
具体 HEAD、tarball/lock/报告指纹、命令和报告位置见
|
||||
[机器证据](../baselines/rollback-consumer-validation.json)。此检查点证明核心/Chapter
|
||||
流程,不证明所有包或最终 6.0.0 等候选已验收。浏览器读取已核验安装副本,原临时
|
||||
消费者已清理;新旧包必须按 archive 指纹区分,不能只看版本号。
|
||||
|
||||
## 主线与未完成项
|
||||
|
||||
只读 fetch 后,master/origin/master/FETCH_HEAD 均为
|
||||
`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`,当前 HEAD 比其领先 256 提交,
|
||||
没有主线新增提交。仍需执行可重复的冲突同步演练、iframe 更名回退和全包恢复表。
|
||||
Thumbnail 缺失完整历史包及站点远端恢复不因此关闭,正式发布仍须三轮复盘。
|
||||
|
||||
回退本次工程改动可以撤销本检查点提交;不涉及用户安装版本或远端状态。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 262 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 46 / doing 21 / blocked 0 / done 195 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 45 / doing 22 / blocked 0 / done 195 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -431,7 +431,7 @@
|
||||
| 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 |
|
||||
| REL-04 | workspace<br>提前演练回退与主线修复同步 | REL-08, ENG-07 | 分包旧版本/tag/依赖回退方案与 master 差异 | 隔离包演练可回退,主线修复同步流程可执行;正式每批再核对其实际回退产物 | H | todo |
|
||||
| REL-04 | workspace<br>提前演练回退与主线修复同步 | REL-08, ENG-07 | 分包旧版本/tag/依赖回退方案与 master 差异 | 隔离包演练可回退,主线修复同步流程可执行;正式每批再核对其实际回退产物 | H | doing |
|
||||
| REL-05 | workspace<br>经授权发布候选并收集反馈 | REL-03, REL-04, REVIEW-03 | 候选 tag、完整产物 integrity、反馈与复现记录 | 实际发布授权/操作/版本可追溯;本计划不自动执行发布 | H | todo |
|
||||
| REL-06 | workspace<br>经授权分批正式发布 | REL-05 | 正式分包版本/tag/站点文档及兼容公告 | 候选验收通过,安装/浏览器复核和回退入口就绪;候选反馈修复须独立建任务并复验三轮受影响结论,正式内容与最终验证产物一致 | H | todo |
|
||||
| REL-07 | workspace<br>关闭重构里程碑并维护后续队列 | REL-06, ENG-LINT-01 | 最终任务/设计/证据归档和维护指南 | 全范围任务有结论、无未解释兼容缺口;遗留项有明确后续责任 | L | todo |
|
||||
@@ -658,6 +658,7 @@
|
||||
- MOD-DEV-01: [记录](changes/2026-09-14-MOD-DEV-01-dev-server.md) [记录](baselines/dev-server-validation.json)
|
||||
- MOD-03: [记录](changes/2026-09-14-MOD-03-slider-updates.md) [记录](baselines/slider-updates-validation.json) [记录](changes/2026-09-14-MOD-03-performance-acceptance.md) [记录](baselines/core-performance-validation.json)
|
||||
- REL-08: [记录](changes/2026-09-14-REL-08-release-ledger.md) [记录](baselines/release-ledger-validation.json) [记录](release-ledger.md) [记录](release-ledger.json)
|
||||
- REL-04: [记录](changes/2026-09-15-REL-04-consumer-checkpoint.md) [记录](rollback-rehearsal.md) [记录](baselines/rollback-consumer-validation.json)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md) [记录](type-compatibility-policy.md) [记录](baselines/factory-compatibility-validation.json) [记录](changes/2026-09-13-PKG-FACTORY-01-compatible-types.md)
|
||||
- CORE-25: [记录](changes/2026-09-13-CORE-25-defaults-ssr.md) [记录](baselines/defaults-ssr-validation.json)
|
||||
- ENG-12: [记录](changes/2026-09-13-ENG-12-library-public.md) [记录](baselines/library-public-validation.json)
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# 进度与证据
|
||||
|
||||
## REL-04 核心/Chapter 回退演练检查点
|
||||
|
||||
新增实际隔离安装回退脚本,七步安装/升级/分别回退/整体恢复通过,237 项运行时
|
||||
检查及完整文件/冻结锁验证通过。五组状态三引擎真实播放 15/15;隔离候选类型
|
||||
5/5 旧模式、8/8 精确模式通过。新增脚本未安装依赖,未修改生产产物或根锁。
|
||||
主线 fetch 后仍为重构起点,无新增待同步提交。全包恢复表、iframe 更名回退和
|
||||
冲突同步演练尚待完成,REL-04 保持 doing;262 项为 195 done、22 doing、45 todo。
|
||||
见[检查点](changes/2026-09-15-REL-04-consumer-checkpoint.md)及
|
||||
[维护说明](rollback-rehearsal.md)。
|
||||
|
||||
## PKG-VAST-05 增补真实playUrl和SDK跳过按钮
|
||||
|
||||
真实HTTP标签、广告解码、跳过事件/按钮点击及主片恢复最终5/6通过;Chromium默认
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 回退演练与维护入口
|
||||
|
||||
REL-04 仍在进行中。本页记录已经可重跑的核心/Chapter 演练,不代表所有包或最终大版本候选已可回退。
|
||||
|
||||
## 已实现的安装演练
|
||||
|
||||
固定 Node `.node-version`、Yarn Classic 1.22.22,先运行 `yarn test:package`,再运行
|
||||
`yarn test:rollback`。前者在隔离快照中执行正常生产构建、类型生成和打包;后者读取
|
||||
同一 HEAD 的构建报告及已冻结 npm 历史 tarball。不会改写工作区生产产物或根锁文件。
|
||||
|
||||
`scripts/rollback-consumer.mjs` 建立四套消费者 manifest/lock:旧核心+旧插件、
|
||||
新核心+旧插件、旧核心+新插件、新核心+新插件。准备锁文件时从根锁解析离线依赖,
|
||||
随后在同一个仓库外消费者目录中连续执行以下步骤:
|
||||
|
||||
| 步骤 | 安装状态 | 验证意图 |
|
||||
| --- | --- | --- |
|
||||
| install-old | 旧核心+旧插件 | 原始安装 |
|
||||
| upgrade-core | 新核心+旧插件 | 独立升级核心 |
|
||||
| upgrade-plugin | 新核心+新插件 | 升级插件 |
|
||||
| rollback-plugin | 新核心+旧插件 | 单独回退插件 |
|
||||
| restore-candidate | 新核心+新插件 | 恢复升级状态 |
|
||||
| rollback-core | 旧核心+新插件 | 单独回退核心 |
|
||||
| rollback-all | 旧核心+旧插件 | 恢复完整旧组合 |
|
||||
|
||||
每一步恢复对应 manifest/lock 后执行 Yarn `install --offline --frozen-lockfile
|
||||
--force --ignore-scripts --non-interactive`;不会先删除 node_modules,因此能够检测
|
||||
替换过程中遗留的新版文件。冻结锁字节必须不变,所有包成员及文件清单必须与实际
|
||||
tarball 一致。版本号相同也检查内容;例如本次 Chapter 新旧均为 1.1.0。
|
||||
|
||||
`scripts/rollback-files.mjs` 专门检查安装清单、成员哈希和 workspace/symlink 逃逸。
|
||||
仅忽略包根目录的 Yarn `.yarn-metadata.json`、`.yarn-tarball.tgz` 安装元数据。
|
||||
负例覆盖相同版本的错误内容、新版残留文件及缺失文件;纳入 `test:node`。
|
||||
消费者临时目录在成功或失败后清理,日志、锁和已核验安装副本保存在
|
||||
`refactor/.cache/rollback/run-*`,`latest.json` 指向最近一次结果并记录成功状态。
|
||||
|
||||
复用 `test/package/runtime.cjs` 检查实际 CJS/ESM/UMD/AMD/legacy/i18n、SSR 与
|
||||
Emitter/公开形状。旧核心显式使用历史配置,保留历史 SSR 失败断言;不会因为
|
||||
回退而要求旧包支持新 `/runtime` 入口。安装脚本禁用不代表验证了 lifecycle hooks。
|
||||
|
||||
## 真实浏览器验证
|
||||
|
||||
设置 `ARTPLAYER_BROWSER_ARTIFACTS` 为成功演练输出中的某个
|
||||
`<action>-browser-artifacts.json`,运行 `yarn test:rollback:browser`。
|
||||
`playwright.rollback.config.js` 复用已提交播放用例,三引擎、零自动重试;检查实际
|
||||
解码像素、播放、暂停、seek、切换 docs 媒体、Chapter DOM、事件与 destroy。
|
||||
|
||||
`scripts/rollback-artifacts.mjs` 在浏览器启动前校验:演练已成功、HEAD/Node 一致、
|
||||
候选构建输入仍匹配当前源码/工具链、包来自已验证候选或冻结历史 archive、安装
|
||||
副本所有文件和对应锁哈希一致。直接使用普通 installed runner 会因报告不是
|
||||
ENG-07 而被拒绝;保留该门槛,回退使用独立验证入口。
|
||||
|
||||
浏览器通过独立 fixture 服务读取已核验安装副本,不是从仍存活的临时消费者目录
|
||||
加载。用例中的 `candidate` 是路由名,具体版本以演练 profile 为准。
|
||||
报告写入 `refactor/.cache/rollback-browser`;连续测试不同状态前,在前一进程
|
||||
结束后把报告移入唯一 cache 子目录,防止覆盖。端口沿用测试服务 8084。
|
||||
|
||||
## 当前结果与剩余工作
|
||||
|
||||
2026-09-15,七步安装/回退均通过,共 237 个运行时检查;五组状态在三个桌面引擎中
|
||||
15/15 真实播放通过。隔离候选构建另有 5/5 旧类型模式、8/8 精确类型模式通过。
|
||||
详见 [机器证据](baselines/rollback-consumer-validation.json) 和
|
||||
[检查点](changes/2026-09-15-REL-04-consumer-checkpoint.md)。
|
||||
|
||||
尚需:22 包逐包旧版本/tag/依赖恢复表、iframe 更名前后消费者回退、主线修改与
|
||||
JS→TS 冲突同步/中止演练。已执行 `git fetch origin master`,当时 master 没有
|
||||
新增待同步提交;这不等于验证过冲突解决。
|
||||
|
||||
Thumbnail 的完整旧 npm tarball 缺失、文档站的真实部署恢复继续受原有发布门槛
|
||||
约束。正式每批需要重新绑定实际 next-major 候选及旧包产物并重跑对应验证。
|
||||
不执行 npm tag 修改、unpublish、推送或部署,不把本演练当作这些动作的授权。
|
||||
+6
-2
@@ -4798,11 +4798,15 @@
|
||||
"REL-08",
|
||||
"ENG-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "分包旧版本/tag/依赖回退方案与 master 差异",
|
||||
"acceptance": "隔离包演练可回退,主线修复同步流程可执行;正式每批再核对其实际回退产物",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-15-REL-04-consumer-checkpoint.md",
|
||||
"rollback-rehearsal.md",
|
||||
"baselines/rollback-consumer-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "REVIEW-01",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
import { verifyInstalledArtifacts } from './installed-artifacts.mjs'
|
||||
import { verifyRollbackFiles } from './rollback-files.mjs'
|
||||
|
||||
export function verifyRollbackArtifacts(root, mapFile) {
|
||||
assert(mapFile, 'Set ARTPLAYER_BROWSER_ARTIFACTS to a rollback step map')
|
||||
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||
const directory = path.dirname(fs.realpathSync(mapFile))
|
||||
assert.equal(path.dirname(directory), fs.realpathSync(path.join(root, 'refactor/.cache/rollback')))
|
||||
const report = read(path.join(directory, 'report.json'))
|
||||
assert.equal(report.task, 'REL-04')
|
||||
assert.equal(report.passed, true, 'Rollback installation must pass first')
|
||||
assert.equal(report.node, process.versions.node)
|
||||
assert.equal(report.source, execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', windowsHide: true }).trim())
|
||||
const step = report.steps.find(item => item.browserArtifacts === path.basename(mapFile))
|
||||
assert(step, 'Unknown rollback step map')
|
||||
const candidate = verifyInstalledArtifacts(root, path.join(root, report.build, 'browser-artifacts.json'))
|
||||
const historical = read(path.join(root, 'refactor/baselines/releases.json')).releases
|
||||
const map = read(mapFile)
|
||||
const expectedNames = ['artplayer', 'artplayer-plugin-chapter']
|
||||
assert.deepEqual(Object.keys(map), expectedNames)
|
||||
const packages = report.profiles[step.profile].packages.map((pkg) => {
|
||||
const recorded = [...candidate.packages, ...historical].find(item => item.name === pkg.name && item.sha256 === pkg.sha256)
|
||||
assert(recorded, 'Rollback package is not a verified candidate or frozen historical archive')
|
||||
assert.equal(hash(fs.readFileSync(path.resolve(root, pkg.archive))), recorded.sha256)
|
||||
assert.equal(map[pkg.name], `./${step.action}/node_modules/${pkg.name}/dist/${pkg.name}.js`)
|
||||
return verifyRollbackFiles(path.join(directory, step.action), recorded)
|
||||
})
|
||||
assert.deepEqual(packages, step.packages)
|
||||
assert.equal(hash(fs.readFileSync(path.join(directory, `${step.profile}-yarn.lock`))), step.lockSha256)
|
||||
return { directory, action: step.action, packages }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { ensureArchive, hash } from '../refactor/scripts/releases.mjs'
|
||||
import { consumerDirectory, names, readJson, removeConsumer, run, runtimeConsumer, workspace, writeJson } from './package-consumer.mjs'
|
||||
import { verifyRollbackFiles } from './rollback-files.mjs'
|
||||
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use pinned Yarn')
|
||||
assert.equal(process.versions.node, fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use pinned Node')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn), 'Missing Yarn executable')
|
||||
assert.equal(run([yarn, '--version'], workspace).trim(), '1.22.22')
|
||||
const source = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: workspace, encoding: 'utf8', windowsHide: true }).trim()
|
||||
const builds = fs.realpathSync(path.join(workspace, 'refactor/.cache/packages'))
|
||||
const build = fs.realpathSync(path.resolve(workspace, readJson(path.join(builds, 'latest.json')).output))
|
||||
assert.equal(path.dirname(build), builds, 'Use a direct package-check output directory')
|
||||
const candidate = readJson(path.join(build, 'report.json'))
|
||||
assert.equal(candidate.source, source, 'Run yarn test:package on the current commit first')
|
||||
assert.deepEqual(candidate.packages.map(pkg => pkg.name), names, 'This rehearsal covers core and Chapter')
|
||||
const parent = path.join(workspace, 'refactor/.cache/rollback')
|
||||
fs.mkdirSync(parent, { recursive: true })
|
||||
const output = fs.mkdtempSync(path.join(parent, 'run-'))
|
||||
const relative = file => path.relative(workspace, file).replaceAll('\\', '/')
|
||||
const report = { task: 'REL-04', capturedAt: new Date().toISOString(), source, node: process.versions.node, yarn: '1.22.22', passed: false, build: relative(build), scope: names, profiles: {}, steps: [], limitations: ['Early rehearsal; not final next-major candidate acceptance.', 'Runtime imports and file restoration only; browser playback and other package rollbacks require separate evidence.'] }
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
const old = []
|
||||
for (const release of readJson(path.join(workspace, 'refactor/baselines/releases.json')).releases) {
|
||||
const archive = await ensureArchive(release)
|
||||
old.push({ ...release, archive })
|
||||
}
|
||||
assert.deepEqual(old.map(pkg => pkg.name), names, 'Historical package order must match the rehearsal profiles')
|
||||
const current = candidate.packages.map((pkg) => {
|
||||
assert.equal(path.basename(pkg.archive), pkg.archive)
|
||||
const archive = path.join(build, pkg.archive)
|
||||
assert.equal(hash(fs.readFileSync(archive)), pkg.sha256, 'Candidate archive changed')
|
||||
return { ...pkg, archive }
|
||||
})
|
||||
const profiles = { baseline: old, core: [current[0], old[1]], plugin: [old[0], current[1]], both: current }
|
||||
for (const [id, packages] of Object.entries(profiles)) {
|
||||
const dir = consumerDirectory()
|
||||
try {
|
||||
const manifest = { name: 'artplayer-rollback-consumer', private: true, dependencies: Object.fromEntries(packages.map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) }
|
||||
writeJson(path.join(dir, 'package.json'), manifest)
|
||||
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(dir, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, `${id}-prepare.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], dir))
|
||||
for (const pkg of packages) verifyRollbackFiles(dir, pkg)
|
||||
fs.copyFileSync(path.join(dir, 'package.json'), path.join(output, `${id}-package.json`))
|
||||
fs.copyFileSync(path.join(dir, 'yarn.lock'), path.join(output, `${id}-yarn.lock`))
|
||||
report.profiles[id] = { packages: packages.map(pkg => ({ name: pkg.name, version: pkg.version, archive: relative(pkg.archive), sha256: pkg.sha256 })), lockSha256: hash(fs.readFileSync(path.join(dir, 'yarn.lock'))) }
|
||||
}
|
||||
finally { removeConsumer(dir) }
|
||||
}
|
||||
const sequence = [
|
||||
['baseline', 'install-old'],
|
||||
['core', 'upgrade-core'],
|
||||
['both', 'upgrade-plugin'],
|
||||
['core', 'rollback-plugin'],
|
||||
['both', 'restore-candidate'],
|
||||
['plugin', 'rollback-core'],
|
||||
['baseline', 'rollback-all'],
|
||||
]
|
||||
for (const [id, action] of sequence) {
|
||||
fs.copyFileSync(path.join(output, `${id}-package.json`), path.join(consumer, 'package.json'))
|
||||
const lock = fs.readFileSync(path.join(output, `${id}-yarn.lock`))
|
||||
fs.writeFileSync(path.join(consumer, 'yarn.lock'), lock)
|
||||
fs.writeFileSync(path.join(output, `${action}-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock, 'Rollback changed the frozen lock')
|
||||
const packages = profiles[id].map(pkg => verifyRollbackFiles(consumer, pkg))
|
||||
const runtime = runtimeConsumer(consumer, { baseline: id === 'baseline' || id === 'plugin' })
|
||||
if (report.steps.length)
|
||||
assert.deepEqual(runtime.observations.api, report.steps[0].runtime.observations.api, 'Rollback public API shape changed')
|
||||
const artifacts = {}
|
||||
for (const name of names) {
|
||||
fs.cpSync(path.join(consumer, 'node_modules', name), path.join(output, action, 'node_modules', name), { recursive: true })
|
||||
artifacts[name] = `./${action}/node_modules/${name}/dist/${name}.js`
|
||||
}
|
||||
writeJson(path.join(output, `${action}-browser-artifacts.json`), artifacts)
|
||||
report.steps.push({ action, profile: id, lockSha256: hash(lock), packages, runtime, browserArtifacts: `${action}-browser-artifacts.json` })
|
||||
writeJson(path.join(output, 'report.json'), report)
|
||||
console.log(`${action}: exact installed files and ${runtime.checks.length} runtime checks passed`)
|
||||
}
|
||||
report.passed = true
|
||||
}
|
||||
catch (error) {
|
||||
report.error = { name: error.name, message: error.message }
|
||||
fs.writeFileSync(path.join(output, 'failure.log'), `${error.stack}\n${error.stdout || ''}\n${error.stderr || ''}`)
|
||||
throw error
|
||||
}
|
||||
finally {
|
||||
writeJson(path.join(output, 'report.json'), report)
|
||||
writeJson(path.join(parent, 'latest.json'), { output: relative(output), passed: report.passed })
|
||||
removeConsumer(consumer)
|
||||
console.log(`Rollback report: ${relative(output)}`)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
|
||||
export function verifyRollbackFiles(directory, pkg) {
|
||||
const root = path.join(directory, 'node_modules', pkg.name)
|
||||
assert.equal(fs.realpathSync(root), path.resolve(root), 'Rollback package must not be a workspace link')
|
||||
const actual = []
|
||||
function visit(folder) {
|
||||
for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
|
||||
const file = path.join(folder, entry.name)
|
||||
const relative = path.relative(root, file).replaceAll('\\', '/')
|
||||
assert(!entry.isSymbolicLink(), `Unexpected installed link: ${relative}`)
|
||||
if (entry.isDirectory()) {
|
||||
visit(file)
|
||||
}
|
||||
else {
|
||||
assert(entry.isFile(), `Unexpected installed special file: ${relative}`)
|
||||
// Yarn Classic may add these files at the package root.
|
||||
if (!['.yarn-metadata.json', '.yarn-tarball.tgz'].includes(relative))
|
||||
actual.push(`package/${relative}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(root)
|
||||
assert.deepEqual(actual.sort(), Object.keys(pkg.files).sort(), `Rollback contains missing or leftover files: ${pkg.name}`)
|
||||
for (const [member, digest] of Object.entries(pkg.files)) {
|
||||
assert(member.startsWith('package/') && !member.includes('\\') && !member.split('/').includes('..'), 'Unsafe rollback member')
|
||||
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), digest, `Rollback bytes differ: ${pkg.name}/${member}`)
|
||||
}
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
||||
assert.equal(manifest.name, pkg.name)
|
||||
assert.equal(manifest.version, pkg.version)
|
||||
return { name: pkg.name, version: pkg.version, archiveSha256: pkg.sha256, verifiedMembers: actual.length }
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
# Installed package checks
|
||||
|
||||
For independent core/plugin rollback, run `yarn test:package` then
|
||||
`yarn test:rollback`. The seven-step rehearsal restores frozen consumer locks,
|
||||
checks complete installed file inventories and runtime entrypoints, and saves
|
||||
verified installation copies. `yarn test:rollback:browser` validates a selected
|
||||
step through `ARTPLAYER_BROWSER_ARTIFACTS`. See
|
||||
[rollback maintenance](../../refactor/rollback-rehearsal.md) for report paths,
|
||||
browser scope and the remaining package-specific release gates.
|
||||
|
||||
Run `yarn test:package` with the pinned Node and Yarn after a frozen install. The
|
||||
default consumer scope is core and chapter. For Ambilight/Canvas/Document PiP browser work,
|
||||
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip`.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Exercise stale and incomplete rollback installations.
|
||||
import { test } from 'node:test'
|
||||
import { hash } from '../refactor/scripts/releases.mjs'
|
||||
import { consumerDirectory, removeConsumer } from '../scripts/package-consumer.mjs'
|
||||
import { verifyRollbackFiles } from '../scripts/rollback-files.mjs'
|
||||
|
||||
test('rollback verifies content even when candidate and old version match', () => {
|
||||
const dir = consumerDirectory()
|
||||
try {
|
||||
const root = path.join(dir, 'node_modules/artplayer')
|
||||
fs.mkdirSync(root, { recursive: true })
|
||||
const manifest = JSON.stringify({ name: 'artplayer', version: '1.0.0' })
|
||||
fs.writeFileSync(path.join(root, 'package.json'), manifest)
|
||||
fs.writeFileSync(path.join(root, 'index.js'), 'old')
|
||||
const pkg = { name: 'artplayer', version: '1.0.0', files: { 'package/package.json': hash(manifest), 'package/index.js': hash('old') } }
|
||||
assert.equal(verifyRollbackFiles(dir, pkg).verifiedMembers, 2)
|
||||
fs.writeFileSync(path.join(root, 'index.js'), 'new')
|
||||
assert.throws(() => verifyRollbackFiles(dir, pkg), /Rollback bytes differ/)
|
||||
fs.writeFileSync(path.join(root, 'index.js'), 'old')
|
||||
fs.writeFileSync(path.join(root, 'runtime.js'), 'new-only')
|
||||
assert.throws(() => verifyRollbackFiles(dir, pkg), /leftover files/)
|
||||
fs.unlinkSync(path.join(root, 'runtime.js'))
|
||||
fs.unlinkSync(path.join(root, 'index.js'))
|
||||
assert.throws(() => verifyRollbackFiles(dir, pkg), /missing or leftover/)
|
||||
}
|
||||
finally { removeConsumer(dir) }
|
||||
})
|
||||
Reference in new issue
Block a user