Remotion 可空新参数规范nullable-new-params Skill 与可选参数扫描脚本实战【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion本文聚焦 Remotion 单仓monorepo中的一条内部 API 编码规范新加入的函数参数、React props、类型/接口成员在内部代码中应写成必传可空T | null而非可选?:让每个调用方对无值做出显式选择。读完本文你将掌握 nullable-new-params Skill 的完整规则与 7 步工作流理解配套扫描脚本 find-new-optional-params.ts 的原理与用法并能在 PR 评审中正确区分内部契约与公共 API 的例外边界。规范来源与适用场景这条规范在仓库层面有双重依据。根目录的 AGENTS.md 在 Internal API optionality 一节明确规定When adding or reviewing TypeScript parameters, React props, or type/interface members, make new internal inputs preferrably nullable (T | null), not optional (?:). Public exported APIs are exempt when requiring the input would be breaking.也就是说该规范适用于新增输入的场景给函数加参数、给组件加 props、给类型/接口加成员。Skill 文档 SKILL.md 进一步说明当 diff 中出现了新的可选参数或可选类型成员时内部 Remotion 代码必须把它们改为 required nullable使所有调用方都不得不显式表态。核心规则Rule规范原文给出的规则逐条如下这也是评审时的判定标准内部契约写name: T | null不写name?: T。调用方必须显式传null来表示没有值。当null是缺省哨兵时实现里的检查应优先使用value null/value ! null。除非周围局部契约已经统一使用undefined作为缺省哨兵否则不要对新内部 API 使用undefined表示缺省。反模式包括frozenFrame?: number | null这类冗余形状——它既是可选的又允许 null实际应写成frozenFrame: number | null。公共 API 是例外如果变更的签名、props 类型或 options 对象是从包的公共入口public entrypoint导出、或已在packages/docs/docs中文档化的把新字段/参数改为必传就是一个 breaking change。此时应保持可选或新增向后兼容的 overload / options 路径并按需补充文档和默认值。这个例外边界与仓库整体设计一致packages/core/src内部大量使用T | null风格例如 CompositionManager.tsx、CompositionManagerContext.tsx 等文件均有数十处可空字段从源码结构看必传可空已经成为 Remotion 内部类型的主流写法而对外发布的 npm 包 API 则通过文档与入口导出严格隔离避免内部风格泄漏到公共契约。第一步扫描 diff 中的新增可选成员Skill 提供了一个基于 Bun 运行的扫描脚本用于检查 git diff 中新加入的可选成员/参数# 默认扫描工作区相对 HEAD 的 diff bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts # 变体 1扫描指定区间例如 PR 的完整变更范围 bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts origin/main...HEAD # 变体 2只扫描暂存区staged的变更 bun .agents/skills/nullable-new-params.ts --cached注意最后一条的准确写法见 SKILL.md 原文bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts --cached脚本的核心行为可以从 find-new-optional-params.ts 源码中逐段印证1. 参数处理与 git diff 组装const userArgs Bun.argv.slice(2); const gitArgs [diff, --unified0]; if (userArgs.length 0) { gitArgs.push(HEAD); } else { gitArgs.push(...userArgs); } if (!userArgs.includes(--)) { gitArgs.push(--, *.ts, *.tsx, *.mts, *.cts); }要点默认与HEAD做 diff传入参数时直接透传给git diff因此支持origin/main...HEAD区间和--cached等 git 原生选项。--unified0意味着不展示上下文行只看变更行本身这与后续按行解析的逻辑相配合。若用户未用--自定义文件过滤器则默认只扫描*.ts、*.tsx、*.mts、*.cts四类 TypeScript 文件。2. 两个正则识别新增可选const optionalMemberPattern /(^|[\s{(,;])(?:readonly\s)?(?:[A-Za-z_$][\w$]*|[][^][])\s*\?:/; const optionalMethodPattern /(^|[\s{(,;])(?:readonly\s)?[A-Za-z_$][\w$]*\s*\?\s*(?:[^])?\s*\(/;optionalMemberPattern命中name?:/readonly name?:/ 带引号的 key[my-prop]?:即选项成员和可选函数参数optionalMethodPattern命中name?()这种可选方法/函数成员含name?T()泛型形式。两者共同覆盖了 TypeScript 中新增可选项的三种常见写法类型成员、函数参数、对象上的可选方法。3. diff 行解析与行号追踪脚本逐行解析 unified diff 输出 b/path行确定当前文件并剥掉b/前缀 -n m hunk 头用正则/^ -\d(?:,\d)? \(\d)(?:,\d)? /提取新文件侧起始行号只对开头且非的新增行做模式匹配上下文行空格开头用于推进行号计数getReason()会跳过注释行//、*、/*开头和空行避免把注释里的伪代码误报为候选。4. 输出格式与退出码无候选时打印No newly added optional members or parameters found in the TypeScript diff.并以exit 0结束有候选时逐条打印文件:行号、原因optional member or parameter (\?:)或optional method or function member (?()与代码片段最后附一句行动指引并以exit 1 结束——退出码设计使其可直接嵌入 CI 或 pre-commit 流程做门禁。New optional member/parameter candidates found: packages/xxx/src/foo.ts:42 optional member or parameter (?:) bar?: number; For internal APIs, change these to required nullable values (name: T | null) and pass null explicitly. Keep optional only for exported/documented public APIs where requiring the value would be breaking.第二步对每个候选做公共/内部分类Skill 要求对扫描出的每个候选先分类再动手公共Public从包的入口导出、包含在 packageexports中或已在packages/docs/docs中文档化内部Internal局部 helper、内部组件 props、跨文件的 monorepo 内部 helper、测试工具、内部 context 数据以及没有经包入口暴露的类型拿不准时先 grep 包入口和 docs 目录确认再决定 API 形状。这个分类步骤正是规范中公共 API 例外的落地手段——它决定了候选是走重构为可空还是走保持兼容两条路径。第三步内部候选——从可选重构为必传可空类型成员的改法原文示例type Before { readonly frame?: number; }; type After { readonly frame: number | null; };函数参数的改法const before (frame?: number) {}; const after (frame: number | null) {};对readonly成员同样适用readonly frozenFrame?: number | null必须收敛为readonly frozenFrame: number | null消除既可选又可空的冗余形状。第四步更新所有调用点这是该规范最容易被遗漏的一步。改为必传可空后TypeScript 会强制每个调用方表态Skill 给出的约定是缺省时写field: null明确表达无值仅当undefined仍可能从周围代码流入时用field: maybeValue ?? null把undefined归一化为null不要在解构参数里用默认值掩盖必传的选择例如{ frame 0 } props会重新模糊掉显式契约。第五步更新实现逻辑与测试改为 null 哨兵后实现内的判空逻辑必须同步当0、、false是合法值时替换掉 truthy 检查if (frame)会把合法的0帧当成缺省对可空的 number / string / boolean优先value ! null而不是value测试与 fixture 保持显式不要为了绕过新字段而把大型 fixture 改成PartialT。第六步公共候选——保持向后兼容对分类为 Public 的候选Skill 给出的边界处理模式是在边界处收敛公共类型中的新字段保持可选foo?: T在公共 API 与内部实现的边界上解析出一个具体的内部值惯用写法是const internal publicValue ?? null边界以下的内部下游类型一律保持 required nullable。这样公共契约不破内部契约依然显式——正是 AGENTS.md 所说 Public exported APIs are exempt when requiring the input would be breaking 的标准落地方式。第七步验证Skill 要求的验证闭环包含三项再次运行扫描脚本直到 diff 中只剩有意保留的公共 API 例外bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts对涉及的包运行聚焦测试或构建例如bunx turbo run make --filterpackage-name这与根目录 AGENTS.md 中 Build a specific package 的bunx turbo run make --filterpackage-name命令一致属于仓库标准的单包构建路径。如果本次变更同时改动了文档按writing-docsskill见 writing-docs/SKILL.md执行文档流程包括在packages/docs/sidebars.ts注册页面、遵循一个 API 一个页面与公共 API 专属等约定。评审清单Review checklist合并 PR 前Skill 给出的最终检查清单可直接作为 review 模板diff 中没有残留新的内部?:成员或param?:参数每个内部调用点要么传真实值要么显式传null公共 API 保持向后兼容可空判断没有把合法的 falsy 值0、、false当作缺省当行为依赖缺省时测试至少覆盖一条显式null路径。附Skill 元信息与调用方式该 Skill 的 Agent 元数据定义在 agents/openai.yaml 中声明了展示名与默认提示词interface: display_name: Nullable New Params short_description: Fix new internal optional parameters default_prompt: Use $nullable-new-params to convert new internal optional parameters in my diff to required nullable parameters.从元数据可看出该 Skill 的定位是修复型fix面向已有 diff自动把新增的内部可选参数转换为必传可空形态。它与扫描脚本配合构成检测 → 分类 → 重构 → 验证的完整闭环是 Remotion 单仓在数十个包packages/下 core、renderer、studio、media 等并行开发时维持内部类型契约一致性的一套轻量工程手段。【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
