diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index fe576843f..234b07885 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -1,43 +1,79 @@
name: Node CI
on:
+ pull_request:
push:
- branches:
- - master
+ branches: [master, 'codex/**']
+ workflow_dispatch:
+ workflow_call:
+ inputs:
+ pages-artifact:
+ description: Prepare the checked docs directory for the separate Pages workflow
+ type: boolean
+ default: false
+
+permissions:
+ contents: read
+
+concurrency:
+ group: checks-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
jobs:
- build:
-
+ checks:
+ name: Checks and build
runs-on: ubuntu-latest
-
- strategy:
- matrix:
- node-version: [20.x]
-
+ timeout-minutes: 30
+ env:
+ CI: true
+ defaults:
+ run:
+ shell: bash
steps:
- - uses: actions/checkout@v2
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
- node-version: ${{ matrix.node-version }}
- - name: install, lint, build, and test
+ persist-credentials: false
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version-file: .node-version
+ package-manager-cache: false
+ - name: Install the pinned package manager
+ run: npm install --global --force yarn@1.22.22
+ - name: Validate workflow syntax
run: |
- yarn
- yarn test:playback
- yarn lint
- yarn build:all
- yarn test:dash-control
- env:
- CI: true
-
- - name: Deploy to GitHub Pages
- if: success()
+ curl --fail --silent --show-error --location https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz --output "$RUNNER_TEMP/actionlint.tar.gz"
+ printf '%s %s\n' '8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8' "$RUNNER_TEMP/actionlint.tar.gz" | sha256sum --check
+ tar -xzf "$RUNNER_TEMP/actionlint.tar.gz" -C "$RUNNER_TEMP" actionlint
+ "$RUNNER_TEMP/actionlint" -color
+ - name: Frozen install
run: |
- git config --global user.name 'github-actions[bot]'
- git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- git add -f docs
- git commit -m 'Deploy to GitHub Pages'
- git subtree split --prefix docs master -b gh-pages-temp
- git push origin gh-pages-temp:refs/heads/gh-pages --force
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ mkdir -p refactor/.cache/ci
+ yarn install --frozen-lockfile --non-interactive 2>&1 | tee refactor/.cache/ci/install.log
+ - name: Read-only checks and tests
+ run: yarn ci:check 2>&1 | tee refactor/.cache/ci/check.log
+ - name: Build libraries and documentation
+ run: yarn ci:build 2>&1 | tee refactor/.cache/ci/build.log
+ - name: Record source and tool versions
+ if: always()
+ run: |
+ mkdir -p refactor/.cache/ci
+ git rev-parse HEAD > refactor/.cache/ci/source.txt
+ node --version > refactor/.cache/ci/node.txt
+ yarn --version > refactor/.cache/ci/yarn.txt
+ sha256sum yarn.lock > refactor/.cache/ci/lock.sha256
+ git diff --stat > refactor/.cache/ci/generated-diff.txt
+ - name: Upload available logs
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: checks-${{ github.run_id }}-${{ github.run_attempt }}
+ path: refactor/.cache/ci/
+ include-hidden-files: true
+ retention-days: 14
+ if-no-files-found: warn
+ - name: Prepare Pages artifact
+ if: inputs.pages-artifact && github.ref == 'refs/heads/master'
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
+ with:
+ path: docs
+ include-hidden-files: true
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 000000000..ff2f1e2e1
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,34 @@
+name: Deploy Pages
+
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+jobs:
+ validate:
+ if: github.ref == 'refs/heads/master' && vars.PAGES_DEPLOY_ENABLED == 'true'
+ uses: ./.github/workflows/nodejs.yml
+ with:
+ pages-artifact: true
+
+ deploy:
+ if: github.ref == 'refs/heads/master' && vars.PAGES_DEPLOY_ENABLED == 'true'
+ needs: validate
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy the validated artifact
+ id: deployment
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
diff --git a/AGENTS.md b/AGENTS.md
index 118101108..ab762f3ca 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -74,7 +74,7 @@ Use the repo scripts rather than ad hoc bundler commands.
### Development
```bash
-npm run dev
+yarn dev
```
Starts the local dev site on port `8082` and interactively selects a package to watch. The selected package is built into:
@@ -84,7 +84,7 @@ Starts the local dev site on port `8082` and interactively selects a package to
### Production Build
```bash
-npm run build
+yarn build
```
Interactive build for one package. Outputs:
@@ -95,25 +95,27 @@ Interactive build for one package. Outputs:
Build all packages:
```bash
-npm run build all
+yarn build all
```
### Other Project Scripts
```bash
-npm run build:i18n
-npm run build:ts
-npm run build:docs
-npm run build:llm
-npm run build:test
-npm run lint
-npm run build:all
+yarn build:i18n
+yarn build:ts
+yarn build:docs
+yarn build:llm
+yarn build:test
+yarn lint
+yarn build:all
```
Notes:
-- `npm run lint` targets package source/types plus scripts and TypeScript demo assets.
-- `npm run build:all` is expensive; use it when a change truly spans builds/docs/types/lint together.
+- `yarn lint` is read-only; `yarn lint:fix` explicitly fixes the same source/type/script range.
+- `yarn ci:check` runs strict toolchain/plan/lint/tests; `yarn ci:build` generates outputs and checks imports.
+- See `refactor/ci-setup.md` for CI and separate Pages deployment; remote activation is tracked separately.
+- `yarn build:all` is expensive; use it when a change truly spans builds/docs/types/lint together.
## Useful Local URLs
@@ -251,9 +253,9 @@ If adding HLS-like quality/audio selection elsewhere:
For package-specific builds, the normal flow is:
-1. `npm run dev` and pick the package for fast local iteration
+1. `yarn dev` and pick the package for fast local iteration
2. validate in `http://localhost:8082`
-3. `npm run build` and pick the package when ready to update shippable artifacts
+3. `yarn build` and pick the package when ready to update shippable artifacts
## Documentation Expectations
diff --git a/package.json b/package.json
index 8ae6b10ba..c15dace23 100644
--- a/package.json
+++ b/package.json
@@ -38,9 +38,16 @@
"test:dash-control": "node --test test/dash-control.test.js",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
- "lint": "npx eslint packages/*/{src,types,package.json} scripts/*.js test/* docs/assets/ts/* --fix",
- "build:all": "npm run build all && npm run build:i18n && npm run build:ts && npm run build:docs && npm run lint",
- "check:toolchain": "node scripts/check-toolchain.mjs"
+ "lint": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" --no-fix",
+ "build:all": "yarn ci:build && yarn lint",
+ "check:toolchain": "node scripts/check-toolchain.mjs",
+ "lint:fix": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" --fix",
+ "check:plan": "node refactor/scripts/plan.mjs --check",
+ "test:node": "node --test test/playback.test.js test/dash-control.test.js test/toolchain.test.js test/build-docs.test.js",
+ "test:baseline": "node --test refactor/scripts/*.test.mjs",
+ "ci:check": "yarn check:toolchain --strict && yarn check:plan && yarn lint && yarn test:node && yarn test:baseline",
+ "ci:build": "yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
+ "test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
diff --git a/refactor/README.md b/refactor/README.md
index 02a8721f2..800500b7d 100644
--- a/refactor/README.md
+++ b/refactor/README.md
@@ -28,6 +28,7 @@
| [工具链与发布](toolchain-release.md) | TypeScript、Bun、构建、版本管理和发布回退 |
| [已实现的开发环境](toolchain-setup.md) | Node/Yarn 固定版本、锁文件、安装命令和实际验证范围 |
| [全包大版本策略](version-policy.md) | 每包分别升级一个 major 的目标清单、兼容要求和版本落地步骤 |
+| [已实现的 CI 入口](ci-setup.md) | Yarn 检查/构建、只读 lint、Pages 隔离及远端待验收状态 |
| [GitHub CI/CD](github-ci-cd.md) | PR/兼容矩阵、构建报告、Pages、npm 发布及远端准入验证 |
| [AI 协作流程](ai-workflow.md) | AI 接续工作、任务边界、验证、记录和交接模板 |
| [架构决策](decisions.md) | 已选方向、待验证方案及被拒绝方案 |
diff --git a/refactor/baselines/ci-validation.json b/refactor/baselines/ci-validation.json
new file mode 100644
index 000000000..5a34df2b2
--- /dev/null
+++ b/refactor/baselines/ci-validation.json
@@ -0,0 +1,61 @@
+{
+ "task": "ENG-02",
+ "date": "2026-09-10",
+ "sourceCommit": "9a6cfe5c",
+ "node": "24.21.0",
+ "yarn": "1.22.22",
+ "platform": "win32-x64",
+ "inputs": {
+ "package.json": "3d5cb61b9a802c19d7cdbb19318ab38562846725d972dcd3cf494d2754c779a8",
+ "yarn.lock": "5921f159477b65161b48d4b768f462cb05a537b6a257e62732ec9dfaf4d69e7e",
+ ".node-version": "73fb1b615e2043a933be1c0895cde4358036acc28d785692509b822aa53c761f",
+ ".github/workflows/nodejs.yml": "803842ba0c5bf5bd1806604ecdf0571792a245fff09865c6dc3c94fd9fbc4a1c",
+ ".github/workflows/pages.yml": "e63fb5609a98ae745ff5af68ddbf3adf18a9b483fee9d9d05a957674672f3459",
+ "scripts/build-docs.js": "95aae678608df443ccd14579fa39f2787ac364705f5d0c360ed55e8b0c8ca6e3",
+ "scripts/build-ts.js": "1e1f3448de0fbd88939c3a2ecaebc8a11a48e3b1b4c23dbaeac8b58a92ab7813",
+ "test/build-docs.test.js": "4382e9f445118ac02984425673c84b0678f598cc03ac418ec2bf3b0b2bad23d2"
+ },
+ "actionlint": {
+ "version": "1.7.12",
+ "windowsArchiveSha256": "6e7241b51e6817ea6a047693d8e6fed13b31819c9a0dd6c5a726e1592d22f6e9",
+ "linuxArchiveSha256": "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8",
+ "workflows": 2,
+ "passed": true,
+ "localShellcheck": false,
+ "localPyflakes": false,
+ "invalidKeyRejected": true
+ },
+ "check": {
+ "command": "yarn ci:check",
+ "passed": true,
+ "behaviorTests": 21,
+ "baselineTests": 2,
+ "sourceFilesUnchanged": 426,
+ "lintFailureRejectedWithoutFixing": true
+ },
+ "declarationGeneration": {
+ "command": "yarn build:ts",
+ "filesCompared": 21,
+ "typescriptPrinterOutputUnchanged": true,
+ "formattingFixTypes": [
+ "layout"
+ ]
+ },
+ "build": {
+ "command": "yarn ci:build",
+ "libraryPackages": 21,
+ "libraryArtifacts": 63,
+ "i18n": true,
+ "editorDeclarations": true,
+ "vitepress": true,
+ "postBuildImportSmokeFiles": 3,
+ "passed": true
+ },
+ "remote": {
+ "actionsRuns": "unverified",
+ "branchProtection": "unverified",
+ "pagesSource": "unverified",
+ "pagesEnableVariable": "unverified",
+ "npmPublication": "not performed"
+ }
+}
diff --git a/refactor/changes/2026-09-10-ENG-02-readonly-ci.md b/refactor/changes/2026-09-10-ENG-02-readonly-ci.md
new file mode 100644
index 000000000..f5988047b
--- /dev/null
+++ b/refactor/changes/2026-09-10-ENG-02-readonly-ci.md
@@ -0,0 +1,35 @@
+# ENG-02:只读 PR 检查与独立 Pages 流程
+
+日期:2026-09-10;分支 codex/compatible-modernization;起点 9a6cfe5c。
+
+## 改动
+
+- lint 改为只读,lint:fix 显式修复,覆盖新增 MJS 工具。新增 ci:check、ci:build、test:node、test:baseline、test:imports、check:plan;旧测试/构建入口保留。
+- Node CI 支持 PR、master/codex/** push、手动和复用调用;固定 Actions SHA、Node/Yarn、冻结安装、只读权限、并发与超时,失败也上传可获得的日志/来源摘要。
+- Pages 改为独立手动 workflow,仅 master 且 PAGES_DEPLOY_ENABLED=true 时准备和部署。复用同一次检查/构建得到的 docs artifact;仅部署作业有 Pages/OIDC 权限,移除 CI 内 Git 提交、subtree 和 force push。
+- 当前仅实现隔离框架,未切换远端 Pages source 或启用变量。合并后原 master 自动部署会停止;CI-02 完成域名/路径/恢复验证后再启用,详见 ci-setup.md。
+
+## 检查中发现并修复
+
+原 build-docs.js 没有把构建子进程的失败传给调用方,导致 CI 可能错误成功。新增 error/close 处理,并用真实子进程退出 23/0 验证失败和成功均正确传播。
+
+声明生成器产生的 46 处格式错误以前由 lint --fix 清理。将 layout 修正纳入声明生成器,只处理它刚生成的文件,不能自动改生产源码或调整类型逻辑。21 份声明经 TypeScript printer 规范化后与格式修正前完全一致,构建后只读 lint 通过。声明拼接本身的设计问题仍由后续类型/站点任务处理。
+
+旧 esm/i18n/ssr 文件只有导入和 console,不是完整行为断言;把它们放到构建后的 test:imports,避免检查旧 dist 后宣称候选已测。
+
+## 验证
+
+- Node 24.21.0 / Yarn 1.22.22,使用 ENG-PM-01 冻结安装的独立环境;开发依赖和 yarn.lock 未变。
+- 最终 ci:check 通过:21 项有行为断言的 Node 测试、2 项发布包/HTTP 基线测试。426 个源码/类型/测试文件检查前后哈希不变。
+- ci:build 的库包、i18n、编辑器声明、VitePress 全流程通过;21 库包 63 产物,构建后另有 3 个导入 smoke 通过。
+- 两份 workflow 经 actionlint 1.7.12 本地检查;Windows 归档 SHA-256 已核对。Windows 本轮禁用不可用的 shellcheck/pyflakes,Linux CI 命令保留其默认检查,远端尚未运行。
+- 负例:故意引入格式错误导致 ci:check 失败且文件字节未被修改;非法 workflow key 被 actionlint 拒绝。负例均在临时副本恢复,没有修改生产文件。
+- 计划/链接/依赖、源码 lint 和 Git 差异检查通过;报告见 [ci-validation.json](../baselines/ci-validation.json)。
+
+没有运行新的远端 workflow、发布或推送。最低 Node/跨平台/浏览器/完整类型/候选包矩阵由后续 ENG/CI 任务实施,不能将本次绿色基础检查等同整个重构通过。
+
+## 维护与回退
+
+根 AGENTS、ci-setup.md、github-ci-cd.md、工具说明和任务状态同步维护。新增第三方工具只有校验归档方式使用的 actionlint 1.7.12;Actions 固定 SHA 和版本在 YAML 注释中,来源是官方发布 tag 对应提交。
+
+可撤销 ENG-02 提交恢复旧脚本/workflow 配置;重新执行旧自动部署仍需要核对远端 Pages 状态。下一个任务 BASE-02 使用内置浏览器继续公共 API 快照,再推进事件/生命周期及试点所需测试基础。每项任务继续独立提交。
diff --git a/refactor/ci-setup.md b/refactor/ci-setup.md
new file mode 100644
index 000000000..d137f5897
--- /dev/null
+++ b/refactor/ci-setup.md
@@ -0,0 +1,50 @@
+# PR 检查和 Pages 操作说明
+
+## 本地命令
+
+使用 toolchain-setup.md 的 Node/Yarn 环境与冻结安装。
+
+| 命令 | 行为 |
+| --- | --- |
+| `yarn lint` | 只读 ESLint,覆盖包源码/声明、JS/MJS 工具、测试和编辑器声明 |
+| `yarn lint:fix` | 显式自动修复相同范围 |
+| `yarn test:node` | 显式执行 4 个有行为断言的 Node 测试文件,保留原 test:playback/test:dash-control 入口 |
+| `yarn test:imports` | 3 个既有包导入 smoke,构建后执行以使用新产物;不把 console 示例视为完整行为断言 |
+| `yarn test:baseline` | 固定发布包完整性及本地 HTTP 基线测试;首次可能下载已固定归档到缓存 |
+| `yarn ci:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、Node 和基线测试;允许写忽略缓存,不修改源码 |
+| `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
+| `yarn build:all` | 保留旧入口,执行 ci:build 后只读 lint |
+
+scripts/build-docs.js 保留原 npm run build 子命令兼容入口,现在传播失败退出码;包管理/锁维护继续使用 Yarn。MOD-02 可再统一旧内部脚本。build-ts.js 仅对刚生成的声明执行 ESLint layout 格式修正,使只读 lint 在构建后仍能通过;不是对生产源码执行自动修复。
+
+## PR 和主线
+
+.github/workflows/nodejs.yml 在 PR、master/codex/** push 和手动运行时触发,也供 Pages 复用。Checks and build 作业只授予 contents: read,checkout 不保留 Git 凭据,没有提交、推送、npm 发布或部署步骤。PR/推送取消过期运行,手动部署不被中途取消;初始 Ubuntu 作业限时 30 分钟。
+
+Actions 固定完整 SHA,Node 来自 .node-version,Yarn 固定 1.22.22;安装使用 frozen-lockfile。actionlint 1.7.12 从官方固定归档取得,先核对提交在 workflow 中的 SHA-256,再执行。没有执行 curl 管道脚本。日志在失败时也尽量上传,包含源码 SHA、工具版本、锁摘要和生成差异概览,保留 14 天。
+
+本轮不缓存 node_modules;矩阵、下载缓存、覆盖率、浏览器 trace 和候选 tarball 汇总由 CI-01 等后续任务扩展。当前测试通过不代表尚未实现的类型消费者或浏览器矩阵通过。将来的稳定 required 汇总名称由 CI-01/CI-04 核实后设置,不把本地 YAML 当作分支保护已生效。
+
+## Pages 隔离与启用条件
+
+.github/workflows/pages.yml 仅支持手动运行,限定 master,且仓库变量 PAGES_DEPLOY_ENABLED 必须为 true。初始启用状态未经远端核实;没有修改该变量或其他 GitHub 设置。保留此门槛用于 CI-02 完成域名、路径和远端配置验收后再启用,不能直接自动部署 PR 产物。
+
+Pages 先调用同一检查/构建 workflow,成功后上传同一次运行生成的完整 docs artifact,再在 github-pages 环境的独立作业调用官方 deploy-pages。仅 deploy 作业有 pages: write / id-token: write,没有 Git contents 写权限,也没有原先的 gh-pages force push。发布排队执行,不取消正在执行的发布。
+
+已静态核对当前 docs/CNAME 为 github.artplayer.org;上传包含隐藏文件以保留 .nojekyll。docs 根、document、compiled、旧 HTML/编辑器等实际线上路径和历史站点来源仍需 CI-02 逐项预检;目前未确认或修改线上 Pages source。合并该变更后旧 push-master 自动部署会停止,需先完成以下迁移步骤再启用新流程。
+
+1. CI-02 记录当前 Pages source、gh-pages ref、域名/DNS、关键 URL 和可恢复的旧产物。
+2. 先验证 artifact 内容及旧路径;设置 Pages source 为 GitHub Actions,核对 github-pages 环境只允许受信任 master 部署和所需审批。
+3. 经实际部署授权后设置 PAGES_DEPLOY_ENABLED=true,在 master 手动运行 Deploy Pages;记录 CI 和部署 URL,逐项探测旧路径。
+4. 出现问题时关闭启用变量,按保存的 Pages source/gh-pages ref 恢复;不默认重新 force push 覆盖历史。恢复旧 workflow 可从 ENG-02 前 Git 历史取回,但执行远端写入仍按部署授权处理。
+
+## 待核实的外部状态
+
+| 项目 | 当前证据 | 负责任务 |
+| --- | --- | --- |
+| 新 PR/主线 workflow 实际执行、故障演练 | 仅本地静态和命令验证,无远端 run | CI-04 |
+| required checks、fork 权限和环境审批 | 未读取/修改仓库设置 | CI-04 |
+| Pages source、启用变量、域名和回退目标 | 只读源码 CNAME,远端未知 | CI-02 |
+| npm 权限、trusted publisher、候选发布 | 未配置、未执行 | CI-03/CI-04 |
+
+参考:[GitHub Pages 自定义 workflow](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages)、[setup-node](https://github.com/actions/setup-node)、[actionlint 1.7.12](https://github.com/rhysd/actionlint/releases/tag/v1.7.12)。本轮已核对选用 Actions 的实际 action.yml 和发布 tag 对应 SHA;后续升级需重新检查。
diff --git a/refactor/github-ci-cd.md b/refactor/github-ci-cd.md
index eb9c1c65f..6909e0487 100644
--- a/refactor/github-ci-cd.md
+++ b/refactor/github-ci-cd.md
@@ -1,12 +1,12 @@
# GitHub CI/CD 优化与验收
-2026-09-10 用户明确要求在本次重构中优化和增强 GitHub CI/CD。范围包含仓库检查、测试矩阵、构建产物、文档站部署和 npm 发布准备,并持续维护脚本文档及故障处理指南。本文是待实施规范,不代表 workflows 已更新或远端运行成功。
+2026-09-10 用户明确要求在本次重构中优化和增强 GitHub CI/CD。范围包含仓库检查、测试矩阵、构建产物、文档站部署和 npm 发布准备,并持续维护脚本文档及故障处理指南。ENG-02 已实现初步检查与部署隔离,操作见 [ci-setup.md](ci-setup.md);完整矩阵及远端验收仍待后续任务。
-## 当前源码基线
+## ENG-02 前源码基线
-[nodejs.yml](../.github/workflows/nodejs.yml) 是当前唯一 workflow:仅 push master 触发,Ubuntu + Node 20.x,checkout/setup-node 使用 v2;单一 job 执行 yarn 安装、两组 Node 测试、lint 和 build:all,再提交 docs、subtree split 并 force push gh-pages。
+[nodejs.yml](../.github/workflows/nodejs.yml) 曾是唯一 workflow:仅 push master 触发,Ubuntu + Node 20.x,checkout/setup-node 使用 v2;单一 job 执行 yarn 安装、两组 Node 测试、lint 和 build:all,再提交 docs、subtree split 并 force push gh-pages。
-[package.json](../package.json) 中 lint 使用 --fix,build:all 又包含 lint;当前 workflow 没有 PR 触发、真实浏览器矩阵、类型消费者/tarball 检查、显式权限、并发取消、超时或报告上传。上述是文件检查结果,尚未核对 GitHub 的实际历史运行、Pages 配置、分支保护、环境规则或 npm 账号配置。旧流程的部署目标与访问路径必须先核实,不能仅改 YAML 就宣称迁移完成。
+改造前 [package.json](../package.json) 中 lint 使用 --fix,build:all 又包含 lint;当前 workflow 没有 PR 触发、真实浏览器矩阵、类型消费者/tarball 检查、显式权限、并发取消、超时或报告上传。上述是文件检查结果,尚未核对 GitHub 的实际历史运行、Pages 配置、分支保护、环境规则或 npm 账号配置。旧流程的部署目标与访问路径必须先核实,不能仅改 YAML 就宣称迁移完成。
## 目标流水线
@@ -22,7 +22,7 @@ ENG-02 先建立只读 PR 检查和部署隔离框架,后续 CI 任务逐步
## CI 的可靠性与运行成本
-- Node/TS/包管理器版本来自 ENG-01、BASE-08 和 Bun 试点;最低消费者兼容与构建所需运行时分别验证,不因 Actions 升级静默提高消费者要求。
+- Node/TS/包管理器版本来自 ENG-01/ENG-PM-01 和 BASE-08;用户指定 Yarn 为包管理器,Bun 仅隔离评估。最低消费者兼容与构建所需运行时分别验证,不因 Actions 升级静默提高消费者要求。
- 覆盖 Linux 与 Windows 的关键脚本;浏览器覆盖 Chromium/Firefox/WebKit 和有需要的正式 Chrome。真机、Cast 等证据仍使用环境矩阵,不将 hosted runner 通过当作真机通过。
- 安装使用选定包管理器的锁定模式;缓存按 OS、运行时、包管理器及锁文件隔离,浏览器缓存还绑定测试工具版本。禁止无关 PR 缓存/产物进入有发布权限的任务。
- 快速检查依据依赖图而非简单路径过滤;共享核心/构建/锁文件变化扩大检查,影响不确定时完整回归。文档单改可缩小范围,但 required 汇总检查必须给出真实结果,不能将失败/取消误报成功或留下永久 pending。
diff --git a/refactor/plan.md b/refactor/plan.md
index 586e5ceb8..5ee8ea4d2 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
-状态:todo 197 / doing 0 / blocked 0 / done 17 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
+状态:todo 196 / doing 0 / blocked 0 / done 18 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -75,7 +75,7 @@
| --- | --- | --- | --- | --- | --- | --- |
| ENG-01 | workspace
固定 Node、包管理器与依赖 | BASE-01 | 版本 pin、唯一锁文件、安装说明 | 干净环境可复现,最低 Node 与构建依赖一致,未夹带全量升级 | M | done |
| ENG-PM-01 | workspace
按用户选择切换固定 Yarn 包管理器 | ENG-01 | Yarn 固定版本、唯一 yarn.lock、工具检查与安装维护文档 | 干净冻结安装、Node 测试和全部构建通过;记录解析差异和依赖用途,独立提交 | M | done |
-| ENG-02 | workspace
拆分只读检查并建立 PR CI | ENG-01, ENG-PM-01 | lint/lint:fix、PR 与主线检查、独立部署任务;遵循 github-ci-cd.md,PR/重构分支触发、最小权限及 workflow 静态检查 | 仓库内检查可执行且不改源码、不发布;required checks 的外部设置状态列入发布台账,不阻塞本地框架建设 | M | todo |
+| ENG-02 | workspace
拆分只读检查并建立 PR CI | ENG-01, ENG-PM-01 | lint/lint:fix、PR 与主线检查、独立部署任务;遵循 github-ci-cd.md,PR/重构分支触发、最小权限及 workflow 静态检查 | 仓库内检查可执行且不改源码、不发布;required checks 的外部设置状态列入发布台账,不阻塞本地框架建设 | M | done |
| ENG-03 | workspace
建立公共行为与单元测试入口 | ENG-02, BASE-03 | 保留现有 node:test,测试目录/夹具/统一入口 | 已有 19 项回归保留,旧版与候选可用同一夹具运行 | M | todo |
| ENG-04 | workspace
建立类型测试基础 | ENG-02, BASE-05 | 根与分包 tsconfig、显式 TS 依赖、正反例测试 | 核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型 | M | todo |
| ENG-05 | workspace
建立真实浏览器测试服务 | ENG-03, BASE-04, BASE-08 | Playwright projects、本地 Range/失败媒体服务;复用 docs 页面/样本的状态隔离、错误采集与候选资源映射 | Chromium/Firefox/WebKit 的基础播放 smoke 和报告可执行;以媒体状态断言判定通过,区分轻量用例与真实编辑器交互 | M | todo |
@@ -420,3 +420,4 @@
- BASE-HARNESS-01: [记录](changes/2026-09-10-BASE-HARNESS-01-browser-fixture.md)
- 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)
diff --git a/refactor/progress.md b/refactor/progress.md
index b62756b89..cc69cc9b9 100644
--- a/refactor/progress.md
+++ b/refactor/progress.md
@@ -1,5 +1,11 @@
# 进度与证据
+## 当前实施:ENG-02 只读检查与 CI 基础已完成
+
+新增 PR/主线/复用检查与独立手动 Pages 流程;lint 与自动修复拆分,修复文档构建退出码和生成声明格式。23 项 Node/基线测试、3 个构建后导入 smoke、21 库包 63 产物与文档构建通过;426 个源/类型/测试文件只读检查前后不变,actionlint 静态和负例通过。详见 [ENG-02](changes/2026-09-10-ENG-02-readonly-ci.md) 与 [操作说明](ci-setup.md)。
+
+当前 214 项任务,18 完成、196 待办。ENG-PM-01 已提交 9a6cfe5c;本轮独立提交 ENG-02。未推送、未启用远端 CI/Pages、未发布;CI-02/CI-04 继续负责远端验收。下一任务 BASE-02,使用内置浏览器保存并校验公共 API 基线。
+
## 当前实施:ENG-PM-01 已完成 Yarn 切换
标准工具链为 Node 24.21.0 / Yarn Classic 1.22.22,唯一维护 yarn.lock。最终干净冻结安装、20 项 Node 测试、只读工具检查、21 库包及文档站构建通过,63 个库产物与 npm 基线 SHA-256 全同。详见 [ENG-PM-01](changes/2026-09-10-ENG-PM-01-yarn-toolchain.md)。既有搜索 peer 警告交 SITE-05 验证。
diff --git a/refactor/tasks.json b/refactor/tasks.json
index 50160b4ea..47b5cf50c 100644
--- a/refactor/tasks.json
+++ b/refactor/tasks.json
@@ -439,11 +439,14 @@
"ENG-01",
"ENG-PM-01"
],
- "status": "todo",
+ "status": "done",
"risk": "M",
"deliverable": "lint/lint:fix、PR 与主线检查、独立部署任务;遵循 github-ci-cd.md,PR/重构分支触发、最小权限及 workflow 静态检查",
"acceptance": "仓库内检查可执行且不改源码、不发布;required checks 的外部设置状态列入发布台账,不阻塞本地框架建设",
- "evidence": []
+ "evidence": [
+ "changes/2026-09-10-ENG-02-readonly-ci.md",
+ "baselines/ci-validation.json"
+ ]
},
{
"id": "ENG-03",
diff --git a/refactor/toolchain-setup.md b/refactor/toolchain-setup.md
index f478565a3..3b91e6fbe 100644
--- a/refactor/toolchain-setup.md
+++ b/refactor/toolchain-setup.md
@@ -7,7 +7,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、19 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。
-4. 执行 `yarn test:playback`、`yarn test:dash-control`、`yarn build all` 和 `yarn workspace artplayer-vitepress build`。只读 lint 和 PR CI 的拆分由 ENG-02 完成;旧 lint 当前仍有 --fix。
+4. 执行 `yarn test:playback`、`yarn test:dash-control`、`yarn build all` 和 `yarn workspace artplayer-vitepress build`。ENG-02 已拆分只读 lint 与 lint:fix;PR/主线入口和独立 Pages 流程见 ci-setup.md。
私有根包最低 Node 为 ^20.19.0 || >=22.12.0,与原本使用的 Vite 7 一致。本轮验证 Node 24.21.0,其他版本矩阵由 CI-01 接续,不能宣称所有最低环境已经通过。
diff --git a/scripts/build-docs.js b/scripts/build-docs.js
index b18b86771..3044b59cf 100644
--- a/scripts/build-docs.js
+++ b/scripts/build-docs.js
@@ -3,7 +3,13 @@ import spawn from 'cross-spawn'
const proc = spawn('npm', ['run', 'build'], {
cwd: './packages/artplayer-vitepress/',
+ stdio: 'inherit',
})
-proc.stdout.pipe(process.stdout)
-proc.stderr.pipe(process.stderr)
+proc.on('error', (error) => {
+ console.error(error.message)
+ process.exitCode = 1
+})
+proc.on('close', (code) => {
+ process.exitCode = code ?? 1
+})
diff --git a/scripts/build-ts.js b/scripts/build-ts.js
index b34df6830..e1e231fb4 100644
--- a/scripts/build-ts.js
+++ b/scripts/build-ts.js
@@ -1,5 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
+import { ESLint } from 'eslint'
import { glob } from 'glob'
function ensureDirExists(filePath) {
@@ -57,6 +58,13 @@ console.log(`✨ Built ${artplayerTSoutput}`);
pluginFiles.sort()
const allFiles = [...pluginFiles, 'artplayer.d.ts']
+ const eslint = new ESLint({ fix: true, fixTypes: ['layout'] })
+ const results = await eslint.lintFiles(allFiles.map(file => path.join('docs/assets/ts', file)))
+ await ESLint.outputFixes(results)
+ if (results.some(result => result.errorCount)) {
+ const formatter = await eslint.loadFormatter('stylish')
+ throw new Error(formatter.format(results))
+ }
const commonJsPath = path.join('docs/assets/js/common.js')
const commonJsContent = fs.readFileSync(commonJsPath, 'utf-8')
const newLibUris = allFiles.map(file => `'./assets/ts/${file}'`).join(',\n ')
diff --git a/test/build-docs.test.js b/test/build-docs.test.js
new file mode 100644
index 000000000..7cb39f599
--- /dev/null
+++ b/test/build-docs.test.js
@@ -0,0 +1,36 @@
+import assert from 'node:assert/strict'
+import { spawnSync } from 'node:child_process'
+import fs from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+// eslint-disable-next-line test/no-import-node-test -- Verify the CLI failure contract using a real child process.
+import { test } from 'node:test'
+import { fileURLToPath } from 'node:url'
+
+const root = fileURLToPath(new URL('../', import.meta.url))
+
+test('documentation build forwards child failure and success exit codes', () => {
+ const cache = path.join(root, 'refactor/.cache')
+ fs.mkdirSync(cache, { recursive: true })
+ const fixture = fs.mkdtempSync(path.join(cache, 'docs-exit-'))
+ try {
+ const pkg = path.join(fixture, 'packages/artplayer-vitepress')
+ fs.mkdirSync(pkg, { recursive: true })
+ for (const code of [23, 0]) {
+ fs.writeFileSync(path.join(pkg, 'package.json'), JSON.stringify({
+ private: true,
+ scripts: { build: `node -e "process.exit(${code})"` },
+ }))
+ const result = spawnSync(process.execPath, [path.join(root, 'scripts/build-docs.js')], {
+ cwd: fixture,
+ encoding: 'utf8',
+ })
+ assert.equal(result.status, code, result.stderr)
+ }
+ }
+ finally {
+ const relative = path.relative(cache, fixture)
+ assert(relative.startsWith('docs-exit-') && !relative.includes(path.sep))
+ fs.rmSync(fixture, { recursive: true, force: true })
+ }
+})