diff --git a/refactor/baselines/factory-compatibility-proposals.json b/refactor/baselines/factory-compatibility-proposals.json new file mode 100644 index 000000000..99858c4af --- /dev/null +++ b/refactor/baselines/factory-compatibility-proposals.json @@ -0,0 +1,645 @@ +{ + "schemaVersion": 1, + "task": "PKG-FACTORY-01", + "status": "decision-pending", + "parentCommit": "ef976136a1bef37baab806c37d92cb9d50ddaae5", + "command": "node --test refactor/scripts/factory-assignability.test.mjs", + "scope": "72 exact compiler cases: 2 packages, 2 compilers, 2 published declarations and 4 structural proposals, 3 consumer usages. CommonJS Node resolution, strict and skipLibCheck=false. An additional test isolates the optional-overload regression. This is a decision checkpoint, not a package or release acceptance.", + "recommendation": "Use published 1.1.0 default factory types and explicit runtime views; preserve JS callable and self.default exports. Requires explicit acceptance of the 1.0.0 TypeScript import-equals direct-call migration.", + "proposals": [ + { + "name": "artplayer-proxy-canvas", + "declaration": "import type Artplayer from 'artplayer';\ntype Option = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void;\ntype Result = HTMLCanvasElement;\ntype Callable = (option?: Option) => (art: Artplayer) => Result;\ndeclare const plugin: Callable; export default plugin;" + }, + { + "name": "artplayer-plugin-ambilight", + "declaration": "import type Artplayer from 'artplayer';\ntype Option = { blur?: string; opacity?: number; frequency?: number; zIndex?: number; duration?: number };\ntype Result = { name: 'artplayerPluginAmbilight'; start: () => void; stop: () => void };\ntype Callable = (option: Option) => (art: Artplayer) => Result;\ndeclare const plugin: Callable; export default plugin;" + } + ], + "records": [ + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type '(option: Option) => (art: Artplayer) => HTMLCanvasElement'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "factory-replacement", + "diagnostics": [ + { + "code": 2741, + "message": "Property 'default' is missing in type '(_option?: Parameters[0]) => (_art: Artplayer) => HTMLCanvasElement' but required in type 'Factory'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "require-default", + "diagnostics": [ + { + "code": 2722, + "message": "Cannot invoke an object which is possibly 'undefined'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type 'Callable'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type '(option: Option) => (art: Artplayer) => HTMLCanvasElement'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "factory-replacement", + "diagnostics": [ + { + "code": 2741, + "message": "Property 'default' is missing in type '(_option?: Parameters[0]) => (_art: Artplayer) => HTMLCanvasElement' but required in type 'Factory'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "require-default", + "diagnostics": [ + { + "code": 2722, + "message": "Cannot invoke an object which is possibly 'undefined'." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-proxy-canvas", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type 'Callable'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.0.0", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type '(option: Option) => (art: Artplayer) => Result'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "published-1.1.0", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "factory-replacement", + "diagnostics": [ + { + "code": 2741, + "message": "Property 'default' is missing in type '(_option: Option) => (_art: Artplayer) => { name: \"artplayerPluginAmbilight\"; start(): void; stop(): void; }' but required in type 'Factory'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "required-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "optional-default", + "usage": "require-default", + "diagnostics": [ + { + "code": 2722, + "message": "Cannot invoke an object which is possibly 'undefined'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "latest-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "5.9.3", + "shape": "callable-export-equals", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type 'Callable'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.0.0", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type '(option: Option) => (art: Artplayer) => Result'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "published-1.1.0", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "factory-replacement", + "diagnostics": [ + { + "code": 2741, + "message": "Property 'default' is missing in type '(_option: Option) => (_art: Artplayer) => { name: \"artplayerPluginAmbilight\"; start(): void; stop(): void; }' but required in type 'Factory'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "required-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "optional-default", + "usage": "require-default", + "diagnostics": [ + { + "code": 2722, + "message": "Cannot invoke an object which is possibly 'undefined'." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "require-call", + "diagnostics": [ + { + "code": 2349, + "message": "This expression is not callable.\n Type 'typeof import(\"D:/github/ArtPlayer/test/types/factory-history\")' has no call signatures." + } + ] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "latest-default", + "usage": "require-default", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "factory-replacement", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "require-call", + "diagnostics": [] + }, + { + "name": "artplayer-plugin-ambilight", + "compiler": "4.3.5", + "shape": "callable-export-equals", + "usage": "require-default", + "diagnostics": [ + { + "code": 2339, + "message": "Property 'default' does not exist on type 'Callable'." + } + ] + } + ] +} diff --git a/refactor/changes/2026-09-12-PKG-FACTORY-01-decision.md b/refactor/changes/2026-09-12-PKG-FACTORY-01-decision.md new file mode 100644 index 000000000..39dc78f95 --- /dev/null +++ b/refactor/changes/2026-09-12-PKG-FACTORY-01-decision.md @@ -0,0 +1,17 @@ +# PKG-FACTORY-01 决策检查点 + +复核真实 npm Canvas/Ambilight 1.0.0、1.1.0 声明,两代 export= / export default +在相同严格 TypeScript 消费条件下支持不同的合法调用。增加可重复的四种候选类型形状 +对照,覆盖两个编译器、两个包、三个消费者,共72个精确诊断场景;独立测试进一步证明 +Ambilight 可选参数重载本身也破坏只接受必填参数的工厂替换,即使没有 `.default`。 + +详见[待确认取舍与迁移示例](../factory-compatibility-decision.md)以及 +[完整编译矩阵和声明提案](../baselines/factory-compatibility-proposals.json)。 + +本步只有测试、分析与任务记录,没有修改生产声明或 JS 输出,不需要重建和浏览器复验。 +专项 Node 测试和新增脚本 lint 通过,计划/风险注册校验通过。错误诊断是明确的历史/候选 +反例对照,不代表这些有错误的方案已经被接受。 + +PKG-FACTORY-01 仍 doing;FACTORY-TYPE-01 仍 open;两包05与REL-01依赖不变。 +本地 checkpoint commit 不是完成提交。用户确认后还需实施公开声明、格式包装、编辑器、 +完整安装矩阵与分发验证,再用独立完成 commit 关闭此任务。 diff --git a/refactor/factory-compatibility-decision.md b/refactor/factory-compatibility-decision.md new file mode 100644 index 000000000..9969adae2 --- /dev/null +++ b/refactor/factory-compatibility-decision.md @@ -0,0 +1,79 @@ +# Canvas / Ambilight 工厂类型兼容取舍(待确认) + +PKG-FACTORY-01 已复现当前声明的问题,但尚未修改生产声明,不能标记完成。 +本页推荐方案需要用户确认;Ads 的单独类型修正授权不适用于这里。 + +## 问题与真实发布对照 + +两包 npm 1.0.0 声明均为 `export =`,npm 1.1.0 均改为 `export default`。 +归档由各包 release 基线核验;测试读取真实 tarball 中的声明,不用工作区文件替代。 + +当前为了兼容运行时 `require(package)` 和 `require(package).default`,公开 Factory +增加了必填 `.default`。这使 1.1.0 原本合法的 `const replacement: typeof factory = ...` +报 TS2741。Ambilight 另外增加的可选参数重载也使只接受必填参数的旧替代工厂报 TS2322; +保留最后一个必填重载只能保住 Parameters,无法保住整个函数的赋值关系。 + +在相同 strict、skipLibCheck=false、esModuleInterop=true、Node/CommonJS 配置下, +TypeScript 5.9.3 和 4.3.5 的结果一致: + +| 声明方案 | 普通函数赋给 typeof 默认工厂 | import module = require 后直接调用 | import module = require 后调用 module.default | +| --- | --- | --- | --- | +| 实际发布 1.0.0 | 通过 | 通过 | TS2339 | +| 实际发布 1.1.0 | 通过 | TS2349 | 通过 | +| export = + 必填 self.default | TS2741 | 通过 | 通过 | +| export = + 可选 self.default | 通过 | 通过 | TS2722 | +| 纯函数 export default(推荐) | 通过 | TS2349 | 通过 | +| 纯函数 export = | 通过 | 通过 | TS2339 | + +共 72 个精确编译场景,另有两编译器的 Ambilight 可选重载独立对照。 +第一列针对各版本的 Parameters 生成替代工厂,不声称 1.0.0 与 1.1.0 的参数类型相同。 +本测试不是所有模块解析方式或打包安装验收,不能替代后续安装矩阵。 + +这是同一路径类型表达的冲突:要让任意合法普通函数可以赋值,就不能要求该函数必须 +拥有 `.default`;但把属性设为可选,又不能允许 strict 模式下无检查地调用它。 +按 import/require 分配声明可以改善模块解析,却不能仅凭同一个 CommonJS 消费文件 +区分默认导入与 `import = require`,因此不能把拆 `.d.mts` / `.d.cts` 当作全部兼容的证据。 + +## 推荐方案的具体影响 + +以两包已发布 1.1.0 的默认工厂类型为兼容基准:Canvas 回调可选;Ambilight 配置参数 +必填、字段可选。恢复默认导出为无必填自属性的纯函数,完整保留普通替代工厂、Parameters、 +ReturnType 和 Result 的赋值关系。可选参数、自 `.default` 等精确运行时形状使用独立 +RuntimeFactory 类型;不把它强制附加到默认工厂类型上。 + +运行时保留当前可调用工厂、`.default` 自别名、global、main/legacy/ESM 的实际行为。 +JS 使用者无需因为本方案修改调用。受影响的是仍使用 1.0.0 风格 TypeScript +`import factory = require('包名'); factory(...)` 的代码;它在实际 1.1.0 声明中已经失败, +当前重构中曾被恢复,采用推荐方案后不再由默认声明恢复该路径。 + +迁移示例(两包相同,示例采用 Ambilight): + +```ts +// 1.0.0 风格;推荐方案不再支持这条直接调用的类型路径。 +import factory = require('artplayer-plugin-ambilight') +factory({}) + +// 推荐:默认导入,保留 1.1.0 的纯函数类型。 +import ambilight from 'artplayer-plugin-ambilight' +ambilight({}) + +// 继续使用 import = require 的用户,可调用真实存在的 .default。 +import module = require('artplayer-plugin-ambilight') +module.default({}) +``` + +完整候选声明由 `scripts/factory-assignability.mjs` 的 declaration(pkg, 'latest-default') +生成。当前它只是可编译的核心签名提案,尚未加入全部命名类型、格式包装和安装验收, +不属于已经实施或批准的生产修复。 + +## 决策与后续 + +- 状态:待用户确认是否接受上述 1.0.0 TypeScript 导入迁移;不把大版本升级视为自动授权。 +- 若接受:修改两包声明和包装,补充 RuntimeFactory、全工厂赋值测试、编辑器和包内文档, + 重建并执行真实安装消费矩阵;保留逐版本负例,验证 JS 产物与浏览器证据,单独完成任务提交。 +- 若不接受:保留该风险与发布依赖,继续评估显式兼容入口;不把 optional .default、any、 + skipLibCheck 或删除历史消费者当作修复。 +- PKG-CANVAS-05、PKG-AMBILIGHT-05、REL-01 仍依赖本任务完成。 + +证据:[编译矩阵](baselines/factory-compatibility-proposals.json)、 +[原始回归](baselines/factory-assignment-gaps.json)。 diff --git a/refactor/plan.md b/refactor/plan.md index d9c8bcb7f..564d5b548 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。 -状态:todo 119 / doing 6 / blocked 0 / done 97 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 118 / doing 7 / blocked 0 / done 97 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -412,7 +412,7 @@ | ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 | | --- | --- | --- | --- | --- | --- | --- | -| PKG-FACTORY-01 | artplayer-proxy-canvas, artplayer-plugin-ambilight
修复已迁移工厂的反向赋值兼容 | PKG-CANVAS-04, PKG-AMBILIGHT-04, PKG-DPIP-04 | 复现两包旧typeof工厂替换与新.default/可选重载冲突,修复公开类型并补安装消费者、产物和文档 | 默认工厂的历史合法反向赋值继续通过;既有类型/运行时导出逐版本核对,不能只验证Parameters或直接调用 | H | todo | +| PKG-FACTORY-01 | artplayer-proxy-canvas, artplayer-plugin-ambilight
修复已迁移工厂的反向赋值兼容 | PKG-CANVAS-04, PKG-AMBILIGHT-04, PKG-DPIP-04 | 复现两包旧typeof工厂替换与新.default/可选重载冲突,修复公开类型并补安装消费者、产物和文档 | 默认工厂的历史合法反向赋值继续通过;既有类型/运行时导出逐版本核对,不能只验证Parameters或直接调用 | H | doing | ## 完成证据与阻塞 @@ -520,4 +520,4 @@ - PKG-CANVAS-02: [记录](changes/2026-09-12-PKG-CANVAS-02-tests.md) [记录](baselines/canvas-behavior-validation.json) [记录](canvas-validation.md) - PKG-CANVAS-03: [记录](changes/2026-09-12-PKG-CANVAS-03-lifecycle.md) [记录](baselines/canvas-lifecycle-validation.json) [记录](canvas-validation.md) - PKG-CANVAS-04: [记录](changes/2026-09-12-PKG-CANVAS-04-types.md) [记录](baselines/canvas-types-validation.json) [记录](canvas-validation.md) -- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.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) diff --git a/refactor/progress.md b/refactor/progress.md index 64b7aa719..5437520f6 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,13 @@ # 进度与证据 +## 当前检查点:PKG-FACTORY-01 类型兼容决策 + +Canvas/Ambilight 的工厂替换问题已增加72场景编译对照,以及可选重载的独立反例。 +推荐恢复已发布1.1.0默认工厂形状,保留JS导出,具体1.0.0 TypeScript迁移影响见 +[待确认决策](factory-compatibility-decision.md)。生产声明尚未修改;风险保持open, +本任务doing,不计入完成。当前222项:97 done、7 doing、118 todo。 +此提交仅保存可复现测试、提案与证据;后续仍须实施、安装消费和产物验证。 + ## 最新完成:Document PiP-04 类型、工厂赋值与导出兼容 默认工厂完整保留旧必填签名与可替换性;Result保持void/可写字段,显式RuntimeFactory diff --git a/refactor/risks.json b/refactor/risks.json index f3f119d8c..93321b561 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -3610,11 +3610,13 @@ "owners": [ "PKG-FACTORY-01" ], - "compatibleResolution": "Preserve historical complete factory assignments while modeling actual export variants explicitly; do not equate unchanged Parameters with complete callable compatibility.", + "compatibleResolution": "Preserve historical complete factory assignments while modeling actual export variants explicitly; do not equate unchanged Parameters with complete callable compatibility. The 72-case proposal matrix demonstrates that required or optional self.default and pure export-default/export-equals shapes each preserve different historical consumers; the proposed latest-release type baseline is awaiting explicit user acceptance.", "closureCriteria": "Both historical and candidate replacement fixtures, all supported export/declaration paths and installed consumers verified; unresolved variant conflicts require an explicit decision rather than silent narrowing.", "evidence": [ "refactor/baselines/factory-assignment-gaps.json", - "refactor/changes/2026-09-12-PKG-DPIP-04-types.md" + "refactor/changes/2026-09-12-PKG-DPIP-04-types.md", + "refactor/baselines/factory-compatibility-proposals.json", + "refactor/factory-compatibility-decision.md" ] } ] diff --git a/refactor/scripts/factory-assignability.mjs b/refactor/scripts/factory-assignability.mjs new file mode 100644 index 000000000..8bc78073b --- /dev/null +++ b/refactor/scripts/factory-assignability.mjs @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict' +import path from 'node:path' +import ts from 'typescript' +import compat from 'typescript-compat' +import { verifyAmbilightContract } from './ambilight-contract.mjs' +import { verifyCanvasContract } from './canvas-contract.mjs' +import { readMember } from './releases.mjs' + +export const packages = [ + { name: 'artplayer-proxy-canvas', verify: verifyCanvasContract, optional: true, option: '(ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void', value: '(_ctx: CanvasRenderingContext2D, _video: HTMLVideoElement) => {}', result: 'HTMLCanvasElement', replacement: 'document.createElement(\'canvas\')' }, + { name: 'artplayer-plugin-ambilight', verify: verifyAmbilightContract, optional: false, option: '{ blur?: string; opacity?: number; frequency?: number; zIndex?: number; duration?: number }', value: '{ blur: \'50px\', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3 }', result: '{ name: \'artplayerPluginAmbilight\'; start: () => void; stop: () => void }', replacement: '({ name: \'artplayerPluginAmbilight\' as const, start() {}, stop() {} })' }, +] + +export function declaration(pkg, shape) { + const head = `import type Artplayer from 'artplayer'; +type Option = ${pkg.option}; +type Result = ${pkg.result}; +type Callable = (option${pkg.optional ? '?' : ''}: Option) => (art: Artplayer) => Result; +` + if (shape === 'latest-default') + return `${head}declare const plugin: Callable; export default plugin;` + if (shape === 'callable-export-equals') + return `${head}declare const plugin: Callable; export = plugin;` + assert(['required-default', 'optional-default'].includes(shape)) + return `${head}interface Factory extends Callable { readonly default${shape === 'optional-default' ? '?' : ''}: Factory } +declare const plugin: Factory; export = plugin;` +} + +export function consumer(pkg, usage) { + const head = `import Artplayer from 'artplayer';` + if (usage === 'factory-replacement') { + return `${head}import plugin from './factory-history'; +const replacement: typeof plugin = (_option${pkg.optional ? '?' : ''}: Parameters[0]) => (_art: Artplayer) => ${pkg.replacement}; +void replacement;` + } + assert(['require-call', 'require-default'].includes(usage)) + return `${head}import plugin = require('./factory-history'); plugin${usage === 'require-default' ? '.default' : ''}(${pkg.value});` +} + +export function diagnostics(compiler, source, types) { + const filename = path.resolve('test/types/factory-history-consumer.ts') + const typefile = path.resolve('test/types/factory-history.d.ts') + const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] } + const host = compiler.createCompilerHost(options) + const original = host.getSourceFile.bind(host) + host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename + ? compiler.createSourceFile(file, source, language, true) + : path.resolve(file) === typefile ? compiler.createSourceFile(file, types, language, true) : original(file, language, ...rest) + const exists = host.fileExists.bind(host) + host.fileExists = file => path.resolve(file) === typefile || exists(file) + const program = compiler.createProgram([filename, typefile], options, host) + assert(program.getSourceFile(typefile)) + return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') })) +} + +export async function evaluateFactoryProposals() { + const records = [] + for (const pkg of packages) { + const contract = await pkg.verify() + const shapes = new Map(['1.0.0', '1.1.0'].map(version => [ + `published-${version}`, + readMember(contract.archives.get(version), `package/types/${pkg.name}.d.ts`).toString(), + ])) + for (const shape of ['required-default', 'optional-default', 'latest-default', 'callable-export-equals']) + shapes.set(shape, declaration(pkg, shape)) + for (const compiler of [ts, compat]) { + for (const [shape, types] of shapes) { + for (const usage of ['factory-replacement', 'require-call', 'require-default']) { + records.push({ name: pkg.name, compiler: compiler.version, shape, usage, diagnostics: diagnostics(compiler, consumer(pkg, usage), types) }) + } + } + } + } + return records +} diff --git a/refactor/scripts/factory-assignability.test.mjs b/refactor/scripts/factory-assignability.test.mjs new file mode 100644 index 000000000..f4b3fa163 --- /dev/null +++ b/refactor/scripts/factory-assignability.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +// eslint-disable-next-line test/no-import-node-test -- Node compatibility runner. +import test from 'node:test' +import ts from 'typescript' +import compat from 'typescript-compat' +import { consumer, declaration, diagnostics, evaluateFactoryProposals, packages } from './factory-assignability.mjs' + +test('Factory compatibility proposals retain exact published controls and expose conflicting CommonJS shapes', async () => { + const records = await evaluateFactoryProposals() + assert.equal(records.length, 72) + for (const item of records) { + const expected = { + 'published-1.0.0': { 'factory-replacement': [], 'require-call': [], 'require-default': [2339] }, + 'published-1.1.0': { 'factory-replacement': [], 'require-call': [2349], 'require-default': [] }, + 'required-default': { 'factory-replacement': [2741], 'require-call': [], 'require-default': [] }, + 'optional-default': { 'factory-replacement': [], 'require-call': [], 'require-default': [2722] }, + 'latest-default': { 'factory-replacement': [], 'require-call': [2349], 'require-default': [] }, + 'callable-export-equals': { 'factory-replacement': [], 'require-call': [], 'require-default': [2339] }, + }[item.shape][item.usage] + assert.deepEqual(item.diagnostics.map(diagnostic => diagnostic.code), expected, `${item.name} ${item.compiler} ${item.shape} ${item.usage}`) + } +}) + +test('Adding an optional overload breaks replacement factories even without a default property', () => { + const pkg = packages[1] + const optionalOverload = declaration(pkg, 'latest-default').replace( + 'declare const plugin: Callable;', + 'interface RuntimeCallable { (option?: Option): (art: Artplayer) => Result; (option: Option): (art: Artplayer) => Result } declare const plugin: RuntimeCallable;', + ) + for (const compiler of [ts, compat]) { + assert.deepEqual(diagnostics(compiler, consumer(pkg, 'factory-replacement'), declaration(pkg, 'latest-default')), []) + assert.deepEqual(diagnostics(compiler, consumer(pkg, 'factory-replacement'), optionalOverload).map(item => item.code), [2322]) + } +}) diff --git a/refactor/tasks.json b/refactor/tasks.json index b3a97d6e3..0197a9c98 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -4290,12 +4290,15 @@ "PKG-AMBILIGHT-04", "PKG-DPIP-04" ], - "status": "todo", + "status": "doing", "risk": "H", "deliverable": "复现两包旧typeof工厂替换与新.default/可选重载冲突,修复公开类型并补安装消费者、产物和文档", "acceptance": "默认工厂的历史合法反向赋值继续通过;既有类型/运行时导出逐版本核对,不能只验证Parameters或直接调用", "evidence": [ - "baselines/factory-assignment-gaps.json" + "baselines/factory-assignment-gaps.json", + "baselines/factory-compatibility-proposals.json", + "factory-compatibility-decision.md", + "changes/2026-09-12-PKG-FACTORY-01-decision.md" ] } ]