关于 tldraw-migrate
用于将项目迁移到较新 tldraw SDK 的技能,面向 SDK 使用者。它检测起始与目标版本,获取相关发布说明和文档,并指导升级依赖及处理 TypeScript 错误和弃用 API。
主要功能
- 01检测起始版本和目标版本
- 02获取变更记录与 SDK 文档
- 03升级已安装的 tldraw 包
- 04指导修复 TypeScript 错误
- 05查找已弃用的 API 用法
适用场景
- 01迁移到最新稳定版 SDK
- 02升级到 tldraw 预发布版本
- 03处理 SDK 升级后的类型错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tldraw/ tldraw --skill tldraw-migrate安装后在 Claude Code 中输入/tldraw-migrate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignoredetect-target.mjsdetect-versions.mjsfetch-release-notes.mjsREADME.mdtype-errors.md详细介绍
tldraw 迁移助手
你正在帮助将一个项目迁移到更新版本的 tldraw SDK。请仔细遵循此流程。
在本技能中,${SKILL_DIR} 指代此技能自身的目录(即 SKILL.md、辅助 .mjs 脚本以及缓存的 references/ 文件夹所在的位置)。下面的自动获取代码块会从 SKILL_DIR 环境变量解析该路径(如果已设置),否则会探测常见的技能位置(.claude/skills/、.agents/skills/、.codex/skills/、.cursor/skills/、skills/)。当你在工作流后续运行引用 ${SKILL_DIR} 的 shell 命令时,请替换为相同的绝对路径。如果你将此技能放在其他位置,请在运行自动获取代码块之前设置 SKILL_DIR。
参数:/tldraw-migrate [from-version] [target]。两者均可选。from-version 默认为从 git 历史记录中检测到的上一个 tldraw 版本。target 默认为 latest(npm 上的最新稳定版本);传入 dist-tag(canary、next、beta)或预发布 semver(例如 4.6.0-canary.abc123)可迁移到预发布版本。
已解析的迁移:!SKILL_DIR="${SKILL_DIR:-$(for d in .claude/skills/tldraw-migrate .agents/skills/tldraw-migrate .codex/skills/tldraw-migrate .cursor/skills/tldraw-migrate skills/tldraw-migrate; do [ -d "$d" ] && printf %s "$d" && break; done)}" && FROM=$(node "$SKILL_DIR/detect-versions.mjs" $ARGUMENTS || true) && TARGET=$(node "$SKILL_DIR/detect-target.mjs" $ARGUMENTS) && if [ -z "$FROM" ]; then echo "ERROR: could not detect previous tldraw version. Pass it explicitly: /tldraw-migrate <from-version> [target]" >&2; else echo "from $FROM → target $TARGET"; fi
资源(调用时自动获取)
下面的三个代码块是为 Claude Code 的 ! 前缀自动执行语法编写的。如果你是一个不会自动运行这些代码块的 Agent(Cursor、Codex、普通 Claude 等),请在继续之前将每个代码块作为 shell 命令执行 —— 它们会下载更新日志、完整文档,以及(对于预发布目标)工作流其余部分所依赖的下一个版本发布说明。
!SKILL_DIR="${SKILL_DIR:-$(for d in .claude/skills/tldraw-migrate .agents/skills/tldraw-migrate .codex/skills/tldraw-migrate .cursor/skills/tldraw-migrate skills/tldraw-migrate; do [ -d "$d" ] && printf %s "$d" && break; done)}" && mkdir -p "$SKILL_DIR/references" && CHANGELOG="$SKILL_DIR/references/tldraw-releases.txt" && PREV=$(node "$SKILL_DIR/detect-versions.mjs" $ARGUMENTS || true) && if [ -z "$PREV" ]; then echo "ERROR: could not detect previous tldraw version — skipping changelog fetch. Pass it explicitly: /tldraw-migrate <from-version> [target]" >&2; else node "$SKILL_DIR/fetch-release-notes.mjs" "$PREV" > "$CHANGELOG" && echo "Saved changelog (from $PREV, sourced from github.com/tldraw/tldraw) to $CHANGELOG ($(wc -l < "$CHANGELOG") lines)"; fi
!SKILL_DIR="${SKILL_DIR:-$(for d in .claude/skills/tldraw-migrate .agents/skills/tldraw-migrate .codex/skills/tldraw-migrate .cursor/skills/tldraw-migrate skills/tldraw-migrate; do [ -d "$d" ] && printf %s "$d" && break; done)}" && DOCS="$SKILL_DIR/references/tldraw-full-docs.txt" && STALE=$(find "$DOCS" -mtime +30 2>/dev/null) && if [ -s "$DOCS" ] && [ -z "$STALE" ]; then echo "Using cached full docs at $DOCS ($(wc -l < "$DOCS") lines) — delete the file or wait 30 days to refresh"; else curl --fail -sS https://tldraw.dev/llms-full.txt -o "$DOCS" && echo "Saved full docs to $DOCS ($(wc -l < "$DOCS") lines)"; fi
!SKILL_DIR="${SKILL_DIR:-$(for d in .claude/skills/tldraw-migrate .agents/skills/tldraw-migrate .codex/skills/tldraw-migrate .cursor/skills/tldraw-migrate skills/tldraw-migrate; do [ -d "$d" ] && printf %s "$d" && break; done)}" && mkdir -p "$SKILL_DIR/references" && TARGET=$(node "$SKILL_DIR/detect-target.mjs" $ARGUMENTS) && case "$TARGET" in canary|next|beta|alpha|rc|*-canary*|*-next*|*-beta*|*-alpha*|*-rc*) NEXT="$SKILL_DIR/references/tldraw-next.mdx" && curl --fail -sS https://raw.githubusercontent.com/tldraw/tldraw/main/apps/docs/content/releases/next.mdx -o "$NEXT" && echo "Pre-release target ($TARGET) — saved next-release notes to $NEXT ($(wc -l < "$NEXT") lines)" ;; *) echo "Stable target ($TARGET) — skipping next-release notes" ;; esac
- 已筛选的变更日志 — 从上一版本到当前版本之间稳定版本的发布说明,直接取自
tldraw/tldrawmain 分支上的apps/docs/content/releases/(GitHub 才是事实来源,而非 tldraw.dev)。每一项破坏性变更(标记为💥)都带有一个<details><summary>Migration guide</summary>块,其中包含改动前/后的迁移方案。这些迁移块是特定版本修复方案的主要来源 —— 本技能有意不重复它们。当你遇到 TS 错误时,在此文件中 grep 相关 API 名称,并阅读其迁移块。 - 完整文档 — 完整的 tldraw SDK 文档(约 1.5MB)。不要一开始就读它。按需使用 Grep 或带行范围的 Read 搜索特定主题(例如自定义形状、TLTextOptions)。
- 下一版本发布说明 — 仅当目标是预发布版本时才存在。 即将发布的版本的进行中发布说明(来自
main的原始 MDX)。同样的<details><summary>Migration guide</summary>结构。稳定版变更日志不会覆盖 canary/next 的差异;这些差异就在此文件中。
搜索迁移方案:
始终同时 grep 稳定版变更日志和 next.mdx(后者只对预发布目标存在 —— 2>/dev/null 会吞掉文件缺失的警告)。迁移块通常有 40–80 行,因此默认使用 -A60 作为后续上下文行数。
# List every breaking change with a migration block
grep -nE '💥|<summary>Migration guide' ${SKILL_DIR}/references/tldraw-releases.txt ${SKILL_DIR}/references/tldraw-next.mdx 2>/dev/null
# Find the migration recipe for a specific symbol
grep -n -B2 -A60 'getIndicatorPath\|TLUserStore\|EmbedShapeUtil' ${SKILL_DIR}/references/tldraw-releases.txt ${SKILL_DIR}/references/tldraw-next.mdx 2>/dev/null
第 1 步:了解环境
在做任何更改之前,先扫描项目以了解你要处理的内容。并行运行以下检查:
- 包管理器:检查锁文件(
yarn.lock、pnpm-lock.yaml、bun.lockb、package-lock.json)。全程使用对应的工具。 - tldraw 包:
grep -E "tldraw|@tldraw" package.json—— 安装了哪些包,版本是多少?注意:并非每个项目都会用到所有 tldraw 包。 - 源码目录:找出项目的 TypeScript 源码所在位置(通常是
src/、app/或lib/;Next.js App Router 项目没有src/)。下面每次 grep 都使用该目录 —— 不要假设是src/。 - 导入风格:
grep -r "from '@tldraw" <source-dir> --include="*.ts" --include="*.tsx" -l | head -5—— 项目是从'tldraw'、'@tldraw/editor'还是两者都导入?这会影响模块增强的目标。 - TypeScript:检查
package.json中是否有typecheck或tsc脚本。如果都没有,退回到npx tsc --noEmit(TypeScript 通常是 devDependency)。同时检查 TypeScript 版本。 - 构建工具:检查
package.json的脚本中的构建命令(vite、next、webpack、esbuild 等)。 - 代码检查工具:检查 oxlint/eslint/biome 配置文件(
.oxlintrc*、.eslintrc*、eslint.config.*、biome.json)。代码检查工具之后可能有助于捕获弃用项。 - Monorepo:
package.json是在工作目录根目录,还是这是一个嵌套的包?检查 workspace 配置。
第 2 步:升级软件包
使用检测到的包管理器,将项目依赖中已有的所有 tldraw 软件包升级到解析出的目标版本(在上面的 "Resolved migration" 行中打印)。不要添加项目尚未使用的新软件包。
- 对于稳定目标(
latest或稳定的 semver):安装该 tag/版本,例如yarn add tldraw@latest或npm install [email protected]。 - 对于预发布目标(
canary、next、beta或预发布 semver):安装该 tag/版本,例如yarn add tldraw@canary。如果列出了多个@tldraw/*软件包,请将它们全部固定到同一目标,以避免版本偏差。
将版本升级单独提交。 在修复类型之前,仅暂存并提交 package.json 和 lockfile。后续步骤中的源代码更改保留在各自的提交中,这样最终的 git diff 就可以作为 "迁移所触及的内容" 进行审查,而不会与依赖更改混在一起。
第 3 步:识别所有 TypeScript 错误
运行项目的类型检查命令(来自第 1 步)并对错误进行分类:
-
按 TS 代码统计错误数量(例如 TS2344、TS2786),以了解分布情况。
-
列出出现错误的唯一文件,以了解影响范围。
-
识别错误模式。 以下类别与版本无关——它们描述的是常见错误的形态。针对每个重命名、移除或签名更改的具体修复方法位于发行说明的迁移块中(参见上文的 "搜索迁移配方"),而不在本技能中。
- TS2786 "cannot be used as JSX component" / "bigint not assignable to ReactNode":React 类型偏差。
@types/react(通常还有@types/react-dom)与 tldraw 捆绑的版本不匹配。要查找捆绑版本:使用 npm 或 yarn classic 时,检查node_modules/@tldraw/editor/node_modules/@types/react/package.json;使用 pnpm 或 yarn berry 时,运行包管理器的 "why" 命令(例如pnpm why @types/react)。 - TS2344 "does not satisfy constraint TLShape/TLBaseBoxShape" / TS2416
shapeType不匹配:自定义形状或绑定类型未针对新的全局 props 映射注册。修复模式见 4b。 - TS2305 / TS2724 "has no exported member" / "is not exported":API 已移除或重命名。在
tldraw-releases.txt/tldraw-next.mdx中匹配的Migration guide块中查找替代项。 - TS2339 "property does not exist":API 已重命名或方法签名已更改。同样查找。
- TS2515 "non-abstract class does not implement inherited abstract member":基类新增了一个必需方法。错误中会指明该方法名——在迁移块中搜索该方法名。(注意:如果你的实现只是抛出异常,TypeScript 可能会推断为
never;请添加显式返回类型以满足基类签名。) - TS2345 出现在
createShapes/updateShapes上:异构映射数组需要as TLShapePartial[]/as TLCreateShapePartial[]类型断言。参见 4e。 - TS2345 涉及两个结构相似的
Editor类解析到node_modules/@tiptap/core和node_modules/@tldraw/editor/node_modules/@tiptap/core下的不同路径:TipTap v2/v3 双重安装。参见 4d。
- TS2786 "cannot be used as JSX component" / "bigint not assignable to ReactNode":React 类型偏差。
第 4 步:按影响程度依次修复错误
按此顺序修复 —— 每项修复都会消除大量下游错误。每完成一个子步骤后,重新运行类型检查,并确认该子步骤针对的错误码已解决或正在减少。忽略无关的错误码;它们属于后续子步骤。
| 子步骤 | 症状 |
|---|---|
| 4a. React 类型 | TS2786“bigint 无法赋值给 ReactNode” |
| 4b. 自定义形状与绑定 | TS2344 约束错误、TS2416 shapeType 不匹配 |
| 4c. API 重命名、移除、抽象方法 | TS2305、TS2724、TS2339、TS2515 |
| 4d. TipTap 导入 | 仅当项目依赖 @tiptap/* 时 —— 否则跳过 |
| 4e. 剩余类型错误 | 使用 .map() 的 createShapes/updateShapes、遗留的类型断言 |
阅读 type-errors.md,获取项目实际存在的错误码对应的解决方案。不要从头读到尾 —— 直接跳到匹配的子步骤。
第 5 步:修复弃用项
所有类型错误解决后,查找并修复 @deprecated API 的使用。这些代码仍能编译,但应当迁移。
-
查找已弃用的符号。 更新日志是最可靠的起点 —— tldraw 的
.d.ts文件使用多行 JSDoc,因此对类型定义执行 grep 很脆弱(声明行通常位于@deprecated标签之后 2–5 行,而非紧邻其后)。从这里开始:
grep -i 'deprecated' ${SKILL_DIR}/references/tldraw-releases.txt当目标是预发布版本时,还要执行:
grep -i 'deprecated' ${SKILL_DIR}/references/tldraw-next.mdx。要对照类型定义交叉检查(一次一个包 —— 对项目导入的每个
@tldraw/*包以及tldraw本身重复此操作),请使用-A而非-B:grep -A5 '@deprecated' node_modules/@tldraw/editor/dist-cjs/index.d.ts \ | grep -oE '\b(class|function|interface|const|type|let|var)\s+\w+' \ | awk '{print $NF}' | sort -u这能捕获多行 JSDoc,但会产生一些误报 —— 应将输出视为候选列表,而非权威列表。应以更新日志的 grep 结果为准。
-
如果配置了 linter,请运行它 —— 弃用规则(例如 eslint 的
deprecation/deprecation)会自动标记已弃用的导入。如果未配置 linter,请跳过此步骤。 -
在项目源码中搜索每个已弃用的符号。使用
@deprecatedJSDoc 注释、更新日志条目或文档中推荐的替代方案进行替换。 -
对类型检查未能捕获的重命名进行核查。 如果更新日志或
tldraw-next.mdx描述了某项重命名,而项目仍在导入旧名称,TypeScript 可能通过通配符重新导出解析它而未能发现。浏览更新日志/next.mdx 中的“renamed” / “moved to”条目,并在源码中 grep 你找到的所有旧名称。
第 6 步:验证
运行项目的类型检查和构建命令(如第 1 步中所发现的那样)。
如果仍有错误,重复分类并修复的循环。
迁移后健全性检查
所有错误解决后,做一次快速审计:
-
统计本次迁移添加的带类型
as断言。 简单地grep ' as '会多算,因为它会包含as const(推荐的收窄模式)以及 import 路径中的as。基于 diff 并过滤:git diff -- '<source-dir>/**/*.ts' '<source-dir>/**/*.tsx' \ | grep -E '^\+' | grep -v '^+++' \ | grep -E '\bas\s+[A-Z]' | grep -v 'as const'本次迁移新增的带类型断言应不超过少数几个(理想情况下为零,主要合理解例是异构
createShapes/updateShapes数组上的as TLShapePartial[]/as TLCreateShapePartial[])。如果你在整个迁移中新增超过约 5 个,请回头修复——你几乎肯定在错误使用新 API。 -
审查你添加的每个带类型断言:逐一验证是否确实必要,检查能否用
as const、satisfies、泛型类型参数或模块增强来替代。删除或替换所有有更简洁替代方案的断言。 -
审计模块增强。 模块增强适用于
TLGlobalShapePropsMap/TLGlobalBindingPropsMap注册,以及添加真正缺失的公共类型。它不适用于重新暴露 SDK 已降级为@internal的符号——那是权宜之计,不是修复。改为寻找公共替代品(迁移块通常会指明)。 -
验证没有桩代码或死代码:如果你为已移除的 API 做了桩(例如用 no-op 替换已移除的函数),确保调用方代码不依赖返回值。如果依赖,请在迁移块或文档中寻找恰当替代。
第 7 步:报告
以一条总结消息结束迁移并发送给用户。不要注水——只讲事实:
- 从 → 目标版本,如果是标签,给出解析后的目标版本(
canary→4.7.0-canary.abc123)。 - 修改的文件(数量,如果不多则附列表)。
- 修复的 TypeScript 错误,按代码细分(例如
TS2786 ×12, TS2344 ×3, TS2305 ×1)。使用你在第 4 步收集的前后计数。 - 新增的带类型
as断言(数量,加上每个断言的一行理由——应为零或接近零;标记任何你不确定的)。 - 新增的模块增强(涵盖内容——
TLGlobalShapePropsMap、TLGlobalBindingPropsMap、@tiptap/core链式命令等)。 - 已迁移的弃用(数量及符号)。
- 任何标记为未记录的内容——在任何迁移块中都找不到的符号、更新日志中的缺口,或与实际 API 表面不符的配方。这些都值得反馈给 tldraw 团队。
- 任何跳过或推迟的内容——如果你搁置了某个困难的重命名或留下了
TODO,请明确说明,以便用户知道还有哪些需要自己处理。
质量检查
- 类型安全至关重要。 目标是实现与原始代码同等类型安全的迁移。不要添加
as any、as unknown或宽泛的类型断言。不要添加@ts-ignore或@ts-expect-error。这些永远不可接受 —— 如果你无法让类型正常工作,说明你还不理解新的 API。停下来,先阅读 changelog 和类型定义再继续。 - 优先使用 TypeScript 的类型收窄特性,而非类型断言。 使用
as const表示字面量类型,使用satisfies在不拓宽类型的前提下进行类型检查,在调用处使用泛型参数,使用模块扩充来扩展接口。这些才是迁移的正确工具 ——as断言不是。 - 对于大量相同模式的批量处理,使用并行 Agent。 先手动将一个文件按配方处理以确认可行,然后在一条消息中为每个剩余文件生成一个 Agent(多个 Agent 工具调用并行执行)。向每个 Agent 传递:(1) 待迁移文件的路径,(2) 来自模板文件的前后对比 diff,(3)
tldraw-releases.txt中相关的迁移代码块。这在 4b(跨多个 shape 文件进行机械式TLBaseShape→TLGlobalShapePropsMap迁移)和 4c(一个符号重命名波及整个项目)中最有用。 - 不要只是消除错误 —— 要理解新的 API。 当方法签名发生变化时(例如新增了参数、属性被重命名为更丰富的类型),请阅读 changelog 和当前的类型定义,理解其_为什么_变化。一个能编译但在新 API 期望真实数据的地方传入硬编码/假值的修复,比类型错误更糟糕 —— 它会悄无声息地降低行为质量。例如,如果一个函数新增了必填参数,请检查应该由哪些 shape props 或编辑器状态来提供这些参数,而不是传入
0或1。 - 当不确定某个 API 模式时,在完整文档(
${SKILL_DIR}/references/tldraw-full-docs.txt)中 grep 该特定 API 的用法示例。文档中包含展示每个 API 规范用法的代码示例。
提示
- 阅读
${SKILL_DIR}/references/tldraw-releases.txt获取筛选后的 changelog —— 这是了解变更内容的主要来源 - 当你需要某个特定 API 的文档时,grep
${SKILL_DIR}/references/tldraw-full-docs.txt - 搜索 tldraw 类型时,尝试多个路径 —— dist 目录结构因版本和包管理器而异