mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
build(types): ENG-04 add strict projects and legacy compiler checks
This commit is contained in:
1 parent
09902b8b8c
commit
771482426f
22 files changed
+346
-49
No files matched your search
+4
-2
@@ -45,9 +45,10 @@
|
||||
"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:check": "yarn check:toolchain --strict && yarn check:plan && yarn lint && yarn typecheck && 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"
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"devDependencies": {
|
||||
@@ -69,6 +70,7 @@
|
||||
"svgo": "4.1.0",
|
||||
"terser": "5.51.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-compat": "npm:typescript@4.3.5",
|
||||
"vite": "7.3.6"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.mts", "src/**/*.cts", "types/**/*.d.ts", "../../types/assets.d.ts"]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.mts", "src/**/*.cts", "types/**/*.d.ts", "../../types/assets.d.ts"]
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
| [风险维护说明](risk-guide.md) | 证据等级、关闭条件、第三方边界和校验命令 |
|
||||
| [第三方来源清单](third-party.json) | 依赖解析、vendor 文件指纹、SDK/CDN 与许可来源缺口 |
|
||||
| [消费者与真实环境矩阵](environment-matrix.md) | 22 包的版本依据、能力/设备/样本、现有证据、验证任务及发布影响 |
|
||||
| [类型检查与迁移入口](typechecking.md) | 根/分包配置、当前/旧编译器、严格正反例和历史声明错误 |
|
||||
|
||||
## 维护规则
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# ENG-04:类型检查基础落地
|
||||
|
||||
日期 2026-09-10;起点 09902b8b;分支 codex/compatible-modernization。
|
||||
|
||||
新增根/核心/chapter tsconfig、浏览器资源声明、yarn typecheck 与 CI 接入。脚本自动发现分包配置,禁止严格检查退化为跳过声明或生成产物,并拒绝新 TS 包缺少配置。实现检查和消费者解析模式分开,当前核心/chapter 的生产 TS 文件数诚实输出为零;本任务没有改写生产源码或公开声明。
|
||||
|
||||
固定 TypeScript 5.9.3;新增根开发 alias typescript-compat=typescript 4.3.5,支持精确 alias 的 Yarn 工具检查与拒绝浮动 alias 的回归。4.1.6 试探无法解析现有类型内 getter,最终依赖已移除;4.3.5 是当前已测旧版本点,不是对全部历史消费者下限的新声明。
|
||||
|
||||
3 个严格配置通过;现代 TS 的 Node10+CJS、NodeNext+CJS、Bundler+ESM,以及 4.3.5 Node10+CJS 消费通过。NodeNext ESM 的 7 个既有诊断逐条保存并关联 BASE-TYPE-01,不当作候选通过。原发布包四模式/16 场景仍由基线测试执行;新增实际错误 URL、无效错误断言和源码类型错误的回归,不用跳过/any 消除历史差异。
|
||||
|
||||
验证:完整 ci:check 通过工具链、计划、只读 lint、类型入口、21 项 Node 和 19 项基线测试,共 40 项;特定类型文件 lint 通过。冻结安装核对新依赖;原 1335 个 lock selector 的版本/integrity 全同,只增加一个 alias。Yarn 合并了 esbuild/p-map 同版本 selector;esbuild 的已存在 selector resolved 合并到相同内容的 Yarn registry 地址,未升级生产依赖。
|
||||
|
||||
实际文件职责、运行命令、JS/TS 边界、旧编译器范围与后续工作见 [维护说明](../typechecking.md)。独立提交 ENG-04,撤销本提交可恢复原检查和依赖,不影响生产产物。下一项 ENG-06 让正常构建支持 TS 和非交互选包,随后接续单元/浏览器测试服务与 chapter 试点。
|
||||
@@ -8,10 +8,11 @@
|
||||
| --- | --- |
|
||||
| `yarn lint` | 只读 ESLint,覆盖包源码/声明、JS/MJS 工具、测试和编辑器声明 |
|
||||
| `yarn lint:fix` | 显式自动修复相同范围 |
|
||||
| `yarn typecheck` | 根/迁移包严格检查、当前与兼容 TS 消费;历史 NodeNext ESM 错误单独核对,见 typechecking.md |
|
||||
| `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:check` | 严格 Node/Yarn/锁检查、计划、只读 lint、类型、Node 和基线测试;允许写忽略缓存,不修改源码 |
|
||||
| `yarn ci:build` | 21 库包、i18n、编辑器声明和文档站构建,以及构建后包导入 smoke;会生成 dist 和 docs 内容 |
|
||||
| `yarn build:all` | 保留旧入口,执行 ci:build 后只读 lint |
|
||||
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 189 / doing 0 / blocked 0 / done 25 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 188 / doing 0 / blocked 0 / done 26 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
| 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 和各环境类型;复用 BASE-05 的四种消费模式,不以 skipLibCheck 掩盖 BASE-TYPE-01/03 | M | todo |
|
||||
| ENG-04 | workspace<br>建立类型测试基础 | ENG-02, BASE-05 | 根与分包 tsconfig、显式 TS 依赖、正反例测试 | 核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型;复用 BASE-05 的四种消费模式,不以 skipLibCheck 掩盖 BASE-TYPE-01/03 | M | done |
|
||||
| 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 正常;保持 BASE-05 的 AMD 同时写入全局行为及 i18n/legacy 入口 | H | todo |
|
||||
| ENG-07 | workspace<br>建立 tarball 消费与产物检查 | ENG-04, ENG-06 | 隔离 npm 消费 fixtures、API/声明/入口差分 | 不借 workspace 源码通过,能识别缺文件与默认导出变化 | H | todo |
|
||||
@@ -428,3 +428,4 @@
|
||||
- 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)
|
||||
- ENG-04: [记录](changes/2026-09-10-ENG-04-typechecking.md) [记录](typechecking.md)
|
||||
@@ -1,5 +1,9 @@
|
||||
# 进度与证据
|
||||
|
||||
## 当前实施:ENG-04 类型检查基础完成
|
||||
|
||||
根/核心/chapter 配置和 yarn typecheck 已接入 CI。TS 5.9.3 三种消费模式与 TS 4.3.5 旧解析通过,NodeNext ESM 七项历史诊断单独记录;40 项 Node/基线测试通过。见 [ENG-04](changes/2026-09-10-ENG-04-typechecking.md)。当前 214 项,26 完成、188 待办;工具代码已有实施,生产源码 TS 迁移仍为零。BASE-08 提交 09902b8b,本任务独立提交。下一项 ENG-06 构建 TS 与非交互入口,再完成测试服务和 chapter 源码迁移。
|
||||
|
||||
## 当前实施:BASE-08 环境矩阵完成
|
||||
|
||||
22 包的格式/类型/运行环境/codec/SDK/设备与样本缺口已分配任务和发布影响;见 [矩阵](environment-matrix.md)。只引用已有基线,没有新增浏览器通过结论。当前 214 项,25 完成、189 待办;生产源码 TS 迁移仍为零。BASE-07 提交 ed293231;本任务独立提交。下一项直接实施 ENG-04 类型检查配置,再推进构建与 chapter 试点。
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import test from 'node:test'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkConsumer, checkProject } from '../../scripts/typecheck.mjs'
|
||||
import { refactorDir } from './releases.mjs'
|
||||
|
||||
test('Current and compatibility compilers reject invalid old public calls and unused error assertions', () => {
|
||||
const original = fs.readFileSync(path.join(refactorDir, '../test/types/public.ts'), 'utf8')
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs'), [])
|
||||
const invalid = "import Artplayer from 'artplayer'; new Artplayer({ container: '#player', url: 42 });"
|
||||
assert(checkConsumer(compiler, 'node10-commonjs', invalid).some(d => d.code === 2322))
|
||||
const unused = original.replace('url: 42', "url: 'valid.mp4'")
|
||||
assert(checkConsumer(compiler, 'node10-commonjs', unused).some(d => d.code === 2578))
|
||||
}
|
||||
})
|
||||
|
||||
test('Browser implementation configuration catches source errors without emitting JavaScript', () => {
|
||||
const parent = path.join(refactorDir, '.cache')
|
||||
fs.mkdirSync(parent, { recursive: true })
|
||||
const directory = fs.mkdtempSync(path.join(parent, 'types-check-'))
|
||||
try {
|
||||
const config = path.join(directory, 'tsconfig.json')
|
||||
fs.writeFileSync(config, JSON.stringify({ extends: path.join(refactorDir, '../tsconfig.base.json'), include: ['source.ts'] }))
|
||||
fs.writeFileSync(path.join(directory, 'source.ts'), 'export const title: string = 42;\n')
|
||||
assert.deepEqual(checkProject(config).diagnostics.map(d => d.code), [2322])
|
||||
assert(!fs.existsSync(path.join(directory, 'source.js')))
|
||||
}
|
||||
finally {
|
||||
const relative = path.relative(parent, directory)
|
||||
assert(relative.startsWith('types-check-') && !relative.includes(path.sep))
|
||||
fs.rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
+5
-2
@@ -509,11 +509,14 @@
|
||||
"ENG-02",
|
||||
"BASE-05"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "M",
|
||||
"deliverable": "根与分包 tsconfig、显式 TS 依赖、正反例测试",
|
||||
"acceptance": "核心/试点与迁移模块严格检查,未迁移第三方/包历史问题独立台账;明确最低/当前 TS 和各环境类型;复用 BASE-05 的四种消费模式,不以 skipLibCheck 掩盖 BASE-TYPE-01/03",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-10-ENG-04-typechecking.md",
|
||||
"typechecking.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ENG-05",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
1. 切换到 .node-version 指定的 Node,安装 Yarn 1.22.22;例如 `npm install --global yarn@1.22.22`,然后确认 `node --version` 和 `yarn --version`。npm 仅可用于安装 Yarn 工具及消费者兼容检查,不用于维护本仓库依赖锁。
|
||||
2. 干净 checkout 执行 `yarn install --frozen-lockfile --non-interactive`,禁止 CI 自动更新锁或忽略安装脚本/engines。参见 [Yarn Classic install](https://classic.yarnpkg.com/en/docs/cli/install/)。
|
||||
3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、19 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。
|
||||
3. 执行 `yarn check:toolchain --strict`,核对实际 Node/Yarn、20 个固定开发工具和 22 个 workspace 的声明及传递依赖锁条目。普通检查允许满足最低工具要求的 Node,同时打印标准版本。
|
||||
4. 执行 `yarn test:playback`、`yarn test:dash-control`、`yarn build all` 和 `yarn workspace artplayer-vitepress build`。ENG-02 已拆分只读 lint 与 lint:fix;PR/主线入口和独立 Pages 流程见 ci-setup.md。
|
||||
|
||||
私有根包最低 Node 为 ^20.19.0 || >=22.12.0,与原本使用的 Vite 7 一致。本轮验证 Node 24.21.0,其他版本矩阵由 CI-01 接续,不能宣称所有最低环境已经通过。
|
||||
@@ -14,6 +14,7 @@
|
||||
## 锁文件和依赖维护
|
||||
|
||||
- 根构建工具固定精确版本并放在 devDependencies;发布包的 dependencies/peer 范围保持原样。
|
||||
- ENG-04 新增 typescript-compat(npm:typescript@4.3.5)作为旧编译器消费探针;源码继续使用 typescript 5.9.3。精确 npm alias 也由锁检查保护;实际覆盖与命令见 [类型检查说明](typechecking.md)。
|
||||
- 添加根开发依赖使用 `yarn add --dev --exact --ignore-workspace-root-check <name>@<version>`,包级依赖使用 `yarn workspace <name> add ...`;运行依赖升级需要独立兼容证据。
|
||||
- manifest 和 yarn.lock 同次提交;不提交 package-lock.json、bun.lock 或第二份安装锁。旧 npm 锁及验证报告保留于 ENG-01 Git 历史,原本地 Yarn 锁也已在忽略缓存中备份。
|
||||
- 新增 @yarnpkg/lockfile 1.1.0 为显式开发依赖,供检查器使用 Yarn 官方锁解析器;不依赖 Lerna 偶然安装的传递依赖。只验证 registry 依赖图及完整性字段,实际下载完整性由 Yarn 安装验证;peer 兼容仍由消费者测试和安装报告验证。
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# 已实现的类型检查与迁移入口
|
||||
|
||||
ENG-04 使用开发编译器 TypeScript 5.9.3,并将 TypeScript 4.3.5 作为 npm alias typescript-compat 固定在根开发依赖。后者仅用于旧编译器消费回归,不进入播放器运行依赖或产物。Yarn 锁文件校验现在接受精确版本的 npm alias,仍拒绝范围和浮动版本。
|
||||
|
||||
## 命令及真实覆盖
|
||||
|
||||
```sh
|
||||
yarn typecheck
|
||||
yarn ci:check
|
||||
```
|
||||
|
||||
typecheck 读取根及已建立配置的分包项目,以编译器 API 执行,不依赖两个 TS 安装可能影响的全局 tsc 命令。当前根配置检查两个消费/浏览器类型文件,核心检查 14 个声明及共享资源声明,chapter 检查自身声明及共享资源声明。配置实际编译经过的生产 TS 文件数另行输出;本任务完成时为零,不能把声明检查说成生产 JS 已严格检查。
|
||||
|
||||
自动检查当前工作区的 TS 5.9.3 Node10+CJS、NodeNext+CJS、Bundler+ESM 及 TS 4.3.5 Node10+CJS。正例包括旧配置、ready 回调 this、插件、play 和 update;反例拒绝错误 URL/章节时间。没有 ts-expect-error 生效时编译也须报错,防止 any 或声明放宽使反例失效。
|
||||
|
||||
TS 5.9.3 的 NodeNext ESM 当前有 7 个准确诊断,单独保存在 test/types/known-diagnostics.json,并绑定 BASE-TYPE-01。它们来自原有默认导出声明互操作问题;检查输出明确是历史失败,不能作为候选验收通过。修复由 CORE-07、PKG-CHAPTER-04 和 ENG-07 接续,届时移除对应失败记录、增加成功消费断言,不能一键刷新以容忍新回归。
|
||||
|
||||
BASE-05 的四模式、16 场景、真实发布 tarball 隔离验证仍由 test:baseline 执行。本入口补上当前工作区的检查,不代替隔离发布消费;ENG-07 接入候选 tarball。BASE-TYPE-02/03/04 的可选参数、legacy 声明与返回值问题继续由原任务负责,没有在本任务偷偷修改生产声明。
|
||||
|
||||
## 配置与文件职责
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| 根 tsconfig.base.json | 浏览器实现基线:ES2020+DOM/DOM.Iterable、strict、noUncheckedIndexedAccess、noImplicitOverride、noEmit、skipLibCheck=false、types=[] |
|
||||
| 根 tsconfig.json | 当前公共消费与浏览器环境类型用例;不是无条件将全部遗留包标为已检查 |
|
||||
| packages/artplayer/tsconfig.json | 核心自有 TS 及现有公开声明的入口 |
|
||||
| packages/artplayer-plugin-chapter/tsconfig.json | 试点 TS 及现有公开声明的入口 |
|
||||
| types/assets.d.ts | 当前 Less inline 和 SVG 的 string 导入类型;不提供泛化的任意模块声明 |
|
||||
| scripts/typecheck.mjs | 固定编译器检查、分包发现、四种现代消费模式、兼容编译器与历史诊断核对 |
|
||||
| test/types/ | 真实旧调用、无 Node 全局污染/数组越界反例、明确历史诊断;无运行时副作用,因为只编译不执行 |
|
||||
| refactor/scripts/types.test.mjs | 验证非法旧调用、无效错误断言和生产 TS 错误确实被拒绝,检查不生成 JS |
|
||||
|
||||
实现由现有 bundler 构建,所以源码配置采用 Bundler 模块解析;发布声明仍须经过独立 Node 模式验证。[TypeScript 模块解析说明](https://www.typescriptlang.org/tsconfig/moduleResolution) 区分了这些模式,[官方编译器选项指南](https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options) 也说明 bundler 成功不保证未打包声明能被 Node 消费。
|
||||
|
||||
allowJs=true、checkJs=false 允许渐进迁移时引用旧 JS;只将迁移的 TS 和声明纳入配置根文件,旧 JS 不因被引用就变成已迁移。迁移任务须缩小不明确的 JS 边界,vendored 独立列入第三方台账,不能用 ts-nocheck/any 把自有代码隐藏过去。新增生产 TS 的包若没有 tsconfig,入口会拒绝继续。
|
||||
|
||||
不默认启用 exactOptionalPropertyTypes 去收紧旧调用允许的显式 undefined;公开类型接受范围变化需要逐项消费者证据。内部数组/索引检查启用 [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig/noUncheckedIndexedAccess.html),防止将不存在的元素当作已存在。浏览器 types=[] 防止依赖树中的 @types/node 自动污染 DOM 代码;worker 和 Node 工具需要自身配置,不把 DOM 与 WebWorker 全局混在一起。
|
||||
|
||||
## 编译器版本边界
|
||||
|
||||
4.1.6 试探无法解析当前 artplayer.d.ts 的类型内 getter(首个错误在 template.html);已改用实际通过公共消费样例的 4.3.5,并从最终依赖/锁文件移除 4.1.6。4.3.5 是当前核心/chapter 的已测旧编译器点,不是证明所有历史版本都以它为最低,也不是授权抬高用户已有支持窗口。其他包在自身契约任务核对声明语法和实际旧版本消费者;未查明时保持未知。
|
||||
|
||||
源码检查用 5.9.3 不要求用户也使用 5.9.3。生成声明时须保留旧编译器能理解的公开语法,并在版本落实和候选 tarball 后重跑。NodeNext/Bundler 的消费模式由当前编译器验证,不传给不具备这些模式的 4.3.5。
|
||||
|
||||
## 后续接入
|
||||
|
||||
新迁移包复制分包配置并按实际运行环境调整,加入特有正反例;编译器入口自动发现包配置。TS 构建入口与声明生成由 ENG-06/07 和各包迁移处理;不能只增加一个空 config 就宣布包迁移完成。所有生产变更仍按每任务独立提交和包内架构文档规则交付。
|
||||
@@ -17,8 +17,9 @@ const nodeVersion = fs.readFileSync(path.join(root, '.node-version'), 'utf8').tr
|
||||
const [major, minor] = process.versions.node.split('.').map(Number)
|
||||
assert((major === 20 && minor >= 19) || (major === 22 && minor >= 12) || major >= 23, 'Build tooling requires Node ^20.19.0 || >=22.12.0')
|
||||
for (const [name, version] of Object.entries(manifest.devDependencies)) {
|
||||
assert(/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(version), `Unpinned development dependency: ${name}`)
|
||||
assert.equal(lock[`${name}@${version}`]?.version, version, `Lock resolution differs: ${name}`)
|
||||
const pinned = version.match(/^(?:npm:(?:@[^/]+\/)?[^@]+@)?(\d+\.\d+\.\d+(?:-[\w.-]+)?)$/)?.[1]
|
||||
assert(pinned, `Unpinned development dependency: ${name}`)
|
||||
assert.equal(lock[`${name}@${version}`]?.version, pinned, `Lock resolution differs: ${name}`)
|
||||
}
|
||||
|
||||
const checked = new Set()
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
|
||||
const root = fileURLToPath(new URL('../', import.meta.url))
|
||||
const relative = name => path.relative(root, name).replaceAll('\\', '/')
|
||||
const read = name => JSON.parse(fs.readFileSync(path.join(root, name), 'utf8'))
|
||||
|
||||
export function checkProject(configPath) {
|
||||
const config = ts.readConfigFile(configPath, ts.sys.readFile)
|
||||
if (config.error)
|
||||
return { files: [], diagnostics: [config.error] }
|
||||
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath))
|
||||
assert.equal(parsed.options.strict, true, 'Migrated projects must stay strict')
|
||||
assert.equal(parsed.options.skipLibCheck, false, 'Do not hide declaration errors')
|
||||
assert.equal(parsed.options.noEmit, true, 'Type checks must not write build artifacts')
|
||||
assert.deepEqual(parsed.options.types, [], 'Browser projects must not inherit ambient Node/test globals')
|
||||
const program = ts.createProgram(parsed.fileNames, parsed.options)
|
||||
return { files: parsed.fileNames, diagnostics: [...parsed.errors, ...ts.getPreEmitDiagnostics(program)] }
|
||||
}
|
||||
|
||||
export function checkConsumer(compiler, mode, source = fs.readFileSync(path.join(root, 'test/types/public.ts'), 'utf8')) {
|
||||
const nodeNext = mode.startsWith('nodenext')
|
||||
const module = nodeNext ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS
|
||||
const moduleResolution = nodeNext ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs
|
||||
const filename = path.join(root, 'test/types', nodeNext ? `consumer.${mode.endsWith('-cjs') ? 'cts' : 'mts'}` : 'consumer.ts')
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], esModuleInterop: true, module, moduleResolution }
|
||||
const host = compiler.createCompilerHost(options)
|
||||
const getSourceFile = host.getSourceFile.bind(host)
|
||||
host.getSourceFile = (name, languageVersion, onError, shouldCreateNewSourceFile) => path.resolve(name) === filename
|
||||
? compiler.createSourceFile(filename, source, languageVersion, true)
|
||||
: getSourceFile(name, languageVersion, onError, shouldCreateNewSourceFile)
|
||||
const program = compiler.createProgram([filename], options, host)
|
||||
assert(program.getSourceFile(filename), 'Consumer fixture was not loaded')
|
||||
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({
|
||||
file: diagnostic.file ? relative(diagnostic.file.fileName).replace(/consumer\.[cm]?ts$/, 'consumer.ts') : null,
|
||||
code: diagnostic.code,
|
||||
line: diagnostic.file && diagnostic.start !== undefined ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1 : null,
|
||||
message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n').replaceAll(root.replaceAll('\\', '/'), '<workspace>/'),
|
||||
}))
|
||||
}
|
||||
|
||||
export function runTypechecks() {
|
||||
const dependencies = read('package.json').devDependencies
|
||||
assert.equal(ts.version, dependencies.typescript)
|
||||
assert.equal(`npm:typescript@${compat.version}`, dependencies['typescript-compat'])
|
||||
const configs = [path.join(root, 'tsconfig.json')]
|
||||
for (const name of fs.readdirSync(path.join(root, 'packages'))) {
|
||||
const config = path.join(root, 'packages', name, 'tsconfig.json')
|
||||
if (fs.existsSync(config)) {
|
||||
configs.push(config)
|
||||
}
|
||||
else {
|
||||
const sources = ts.sys.readDirectory(path.join(root, 'packages', name, 'src'), ['.ts', '.tsx', '.mts', '.cts'])
|
||||
assert(!sources.some(file => !/\.d\.[cm]?ts$/.test(file)), `Add a package tsconfig before migrating ${name}`)
|
||||
}
|
||||
}
|
||||
let sourceCount = 0
|
||||
for (const config of configs) {
|
||||
const result = checkProject(config)
|
||||
assert.equal(result.diagnostics.length, 0, ts.formatDiagnosticsWithColorAndContext(result.diagnostics, {
|
||||
getCurrentDirectory: () => root,
|
||||
getCanonicalFileName: name => name,
|
||||
getNewLine: () => '\n',
|
||||
}))
|
||||
sourceCount += result.files.filter(file => relative(file).includes('/src/') && !/\.d\.[cm]?ts$/.test(file)).length
|
||||
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
|
||||
}
|
||||
for (const mode of ['node10-commonjs', 'nodenext-cjs', 'bundler-esm']) {
|
||||
assert.deepEqual(checkConsumer(ts, mode), [], `Current consumer failed: ${mode}`)
|
||||
console.log(`Consumer passed: TS ${ts.version} ${mode}`)
|
||||
}
|
||||
assert.deepEqual(checkConsumer(compat, 'node10-commonjs'), [], 'Old compiler consumer failed')
|
||||
console.log(`Consumer passed: TS ${compat.version} node10-commonjs`)
|
||||
const expected = read('test/types/known-diagnostics.json')
|
||||
const diagnostics = checkConsumer(ts, 'nodenext-esm')
|
||||
assert.deepEqual(diagnostics, expected.diagnostics, 'NodeNext ESM changed: review BASE-TYPE-01; a fix needs updated success assertions')
|
||||
console.log(`Historical failure retained: ${expected.risk}, ${diagnostics.length} NodeNext ESM diagnostics; this is not candidate approval`)
|
||||
console.log(`TypeScript production source files checked: ${sourceCount}; unmigrated JS is not counted`)
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url))
|
||||
runTypechecks()
|
||||
@@ -32,6 +32,8 @@ test('toolchain guard rejects unlocked dependencies, competing locks and the wro
|
||||
|
||||
writeManifest({ ...manifest, devDependencies: { ...manifest.devDependencies, 'unlocked-tool': '1.0.0' } })
|
||||
assert.match(run().stderr, /Lock resolution differs: unlocked-tool/)
|
||||
writeManifest({ ...manifest, devDependencies: { ...manifest.devDependencies, 'typescript-compat': 'npm:typescript@^4.3.5' } })
|
||||
assert.match(run().stderr, /Unpinned development dependency: typescript-compat/)
|
||||
writeManifest(manifest)
|
||||
fs.writeFileSync(path.join(fixture, 'packages/example/package.json'), JSON.stringify({ name: 'example', dependencies: { 'option-validator': '^99.0.0' } }))
|
||||
assert.match(run().stderr, /Dependency missing from Yarn lock: option-validator/)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const element: HTMLDivElement = document.createElement('div')
|
||||
const value: string | undefined = ['first'][1]
|
||||
void [element, value]
|
||||
|
||||
// @ts-expect-error Node globals must not leak into browser packages.
|
||||
globalThis.process.cwd() // eslint-disable-line node/prefer-global/process -- Check that browser projects cannot see ambient Node globals.
|
||||
// @ts-expect-error Array indexing may return undefined in implementation checks.
|
||||
const unchecked: string = ['first'][1]
|
||||
void unchecked
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"risk": "BASE-TYPE-01",
|
||||
"owners": [
|
||||
"ENG-07",
|
||||
"CORE-07",
|
||||
"PKG-CHAPTER-04"
|
||||
],
|
||||
"scope": "Existing workspace NodeNext ESM failure, not a candidate waiver. Remove expected failures when a documented fix adds passing consumer assertions.",
|
||||
"diagnostics": [
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2349,
|
||||
"line": 8,
|
||||
"message": "This expression is not callable.\n Type 'typeof import(\"<workspace>/packages/artplayer-plugin-chapter/types/artplayer-plugin-chapter\")' has no call signatures."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2351,
|
||||
"line": 10,
|
||||
"message": "This expression is not constructable.\n Type 'typeof import(\"<workspace>/packages/artplayer/types/artplayer\")' has no construct signatures."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 7006,
|
||||
"line": 10,
|
||||
"message": "Parameter 'player' implicitly has an 'any' type."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2709,
|
||||
"line": 12,
|
||||
"message": "Cannot use namespace 'Artplayer' as a type."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2683,
|
||||
"line": 12,
|
||||
"message": "'this' implicitly has type 'any' because it does not have a type annotation."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2339,
|
||||
"line": 17,
|
||||
"message": "Property 'html' does not exist on type 'typeof import(\"<workspace>/packages/artplayer/types/artplayer\")'."
|
||||
},
|
||||
{
|
||||
"file": "test/types/consumer.ts",
|
||||
"code": 2349,
|
||||
"line": 18,
|
||||
"message": "This expression is not callable.\n Type 'typeof import(\"<workspace>/packages/artplayer-plugin-chapter/types/artplayer-plugin-chapter\")' has no call signatures."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { Option } from 'artplayer'
|
||||
import Artplayer from 'artplayer'
|
||||
import chapter from 'artplayer-plugin-chapter'
|
||||
|
||||
const options: Option = {
|
||||
container: '#player',
|
||||
url: 'video.mp4',
|
||||
plugins: [chapter({ chapters: [{ start: 0, end: 5, title: 'First' }] })],
|
||||
}
|
||||
const art = new Artplayer(options, function (player) {
|
||||
// eslint-disable-next-line ts/no-this-alias -- Assert the public ready callback receiver type.
|
||||
const self: Artplayer = this
|
||||
self.pause()
|
||||
player.seek = 1
|
||||
})
|
||||
const playback: Promise<void> = art.play()
|
||||
const html: string = Artplayer.html
|
||||
const plugin = chapter({})(art)
|
||||
plugin.update({ chapters: [] })
|
||||
art.on('custom', (value: unknown) => value)
|
||||
void [playback, html]
|
||||
|
||||
// @ts-expect-error A media URL is a string.
|
||||
const invalid = new Artplayer({ container: '#player', url: 42 })
|
||||
void invalid
|
||||
// @ts-expect-error Chapter start times are numeric.
|
||||
chapter({ chapters: [{ start: '0', end: 5, title: 'Invalid' }] })
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"types": [],
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"skipLibCheck": false,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"include": ["test/types/*.ts"]
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
declare module '*.less?inline' {
|
||||
const css: string
|
||||
export default css
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const svg: string
|
||||
export default svg
|
||||
}
|
||||
@@ -3250,7 +3250,7 @@ es-set-tostringtag@^2.1.0:
|
||||
has-tostringtag "^1.0.2"
|
||||
hasown "^2.0.2"
|
||||
|
||||
esbuild@0.27.7:
|
||||
esbuild@0.27.7, "esbuild@^0.27.0 || ^0.28.0":
|
||||
version "0.27.7"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.7.tgz#bcadce22b2f3fd76f257e3a64f83a64986fea11f"
|
||||
integrity sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==
|
||||
@@ -3311,38 +3311,6 @@ esbuild@^0.21.3:
|
||||
"@esbuild/win32-ia32" "0.21.5"
|
||||
"@esbuild/win32-x64" "0.21.5"
|
||||
|
||||
"esbuild@^0.27.0 || ^0.28.0":
|
||||
version "0.27.7"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz"
|
||||
integrity sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==
|
||||
optionalDependencies:
|
||||
"@esbuild/aix-ppc64" "0.27.7"
|
||||
"@esbuild/android-arm" "0.27.7"
|
||||
"@esbuild/android-arm64" "0.27.7"
|
||||
"@esbuild/android-x64" "0.27.7"
|
||||
"@esbuild/darwin-arm64" "0.27.7"
|
||||
"@esbuild/darwin-x64" "0.27.7"
|
||||
"@esbuild/freebsd-arm64" "0.27.7"
|
||||
"@esbuild/freebsd-x64" "0.27.7"
|
||||
"@esbuild/linux-arm" "0.27.7"
|
||||
"@esbuild/linux-arm64" "0.27.7"
|
||||
"@esbuild/linux-ia32" "0.27.7"
|
||||
"@esbuild/linux-loong64" "0.27.7"
|
||||
"@esbuild/linux-mips64el" "0.27.7"
|
||||
"@esbuild/linux-ppc64" "0.27.7"
|
||||
"@esbuild/linux-riscv64" "0.27.7"
|
||||
"@esbuild/linux-s390x" "0.27.7"
|
||||
"@esbuild/linux-x64" "0.27.7"
|
||||
"@esbuild/netbsd-arm64" "0.27.7"
|
||||
"@esbuild/netbsd-x64" "0.27.7"
|
||||
"@esbuild/openbsd-arm64" "0.27.7"
|
||||
"@esbuild/openbsd-x64" "0.27.7"
|
||||
"@esbuild/openharmony-arm64" "0.27.7"
|
||||
"@esbuild/sunos-x64" "0.27.7"
|
||||
"@esbuild/win32-arm64" "0.27.7"
|
||||
"@esbuild/win32-ia32" "0.27.7"
|
||||
"@esbuild/win32-x64" "0.27.7"
|
||||
|
||||
escalade@^3.1.1, escalade@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
|
||||
@@ -6292,12 +6260,7 @@ p-map@4.0.0, p-map@^4.0.0:
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
p-map@^7.0.1:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz"
|
||||
integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==
|
||||
|
||||
p-map@^7.0.4:
|
||||
p-map@^7.0.1, p-map@^7.0.4:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz"
|
||||
integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==
|
||||
@@ -7594,6 +7557,11 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
"typescript-compat@npm:typescript@4.3.5":
|
||||
version "4.3.5"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
|
||||
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
|
||||
|
||||
typescript@5.9.3, "typescript@>=3 < 6":
|
||||
version "5.9.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
|
||||
|
||||
Reference in new issue
Block a user