@tabula-css/cli
tabula 命令行工具:build、build --check、check:css、scan、migrate、except add、eject、doctor、explain、canary。
安装
npm install --save-dev @tabula-css/cli开发依赖——tabula 是一个构建期与 CI 工具。它将 @tabula-css/core、@tabula-css/tokens、@tabula-css/registry、@tabula-css/preset 和 @tabula-css/stylelint-plugin 作为依赖打包,因此只需单独安装这个 CLI,就足以运行下面的每一条命令。@typescript-eslint/parser 作为常规依赖发布;eslint 是一个可选的对等依赖,只有 tabula canary 才需要它。
概览
tabula 是大多数项目使用的入口点。tabula build 读取你的令牌和 tabula.config.json,推导出完整的生成产物集合 .tabula/——正是这一步,把一个扁平令牌配置档案转化为其余一切(@tabula-css/merge、ESLint 插件、MCP 服务器)所读取的封闭词汇表。其他命令则重新检查该输出是否发生了漂移、扫描源文件中是否存在词汇表之外的类、强制执行 CSS 治理、将现有代码迁移到该配置档案上、注册限定范围的例外,以及离线解释一个诊断代码。
每一条命令都共享同一套退出码约定(packages/cli/src/types.ts):0 表示项目是干净的;1 表示项目违反了契约(工具本身工作正常,是样式写错了);2 表示工具本身无法运行(输入格式错误、项目缺失、内部错误)。--format=json 只在 stdout 上输出一个机器可读的信封 { tabula, ok, inputsHash, diagnostics },前面不附带任何面向人类的日志——这正是一个把 tabula build --format=json 接入 JSON 解析器的智能体所依赖的不变量。
build
npx tabula build
npx tabula build --check读取 tokens/*.tokens.json 和 tabula.config.json,推导出完整的产物集合,并将其原子化地写入 .tabula/(一旦校验失败,就不会写入任何内容)。打印产物列表以及本次构建的 inputsHash。
| 标志 | 默认值 | 含义 |
|---|---|---|
--check | 关闭 | 在内存中重新生成,并与已提交的 .tabula/ 逐字节比对;同时运行扫描门禁与 CSS 治理门禁。不向磁盘写入任何内容。只要发现漂移或门禁问题,退出码为 1。 |
--backend a|b|conform | b | 注册表后端:a(design-system API)、b(PostCSS 探针样式表遍历),或 conform(两者都运行,并断言二者一致)。 |
--out <dir> | .tabula | 输出目录。 |
--no-scan | 关闭 | 配合 --check 使用时,跳过扫描门禁(仅做漂移比对)。 |
--no-css | 关闭 | 配合 --check 使用时,跳过 CSS 治理门禁(仅做漂移比对)。 |
--css-entry <path> | — | 可重复。为 CSS 治理门禁指定额外的受认可入口样式表,会被传递给 check:css。 |
--css-ignore <dir> | — | 可重复。在遍历查找 .css 文件时额外跳过的目录名。 |
--check 会依次先运行漂移比对,然后是扫描门禁,然后是 CSS 治理门禁——因此在 --check 下出现的扫描或 CSS 相关问题,永远意味着“源码写错了”,而绝不意味着“注册表过期了”。.tabula/ 内一个清单未提及的多余文件,同样算作漂移。
npx tabula build --check --backend conformcheck:css
npx tabula check:css解析每一个项目 .css 文件(遍历整棵目录树,而不仅仅是 scan 的源码 glob,再减去一份固定的忽略列表),以及 .tabula/ 中生成的样式表,并运行来自 @tabula-css/stylelint-plugin 内核的五条 CSS 治理规则——与编辑器插件所运行的是完全相同的规则代码,因此两者不可能产生分歧。发现问题时退出码为 1。
| 标志 | 默认值 | 含义 |
|---|---|---|
--out <dir> | .tabula | 从何处读取生成的样式表(theme.css、profile.css、source.css)。 |
--css-entry <path> | 一份约定俗成的列表(src/app.css、src/index.css、app/globals.css 等) | 可重复。指定一个受认可的入口样式表,仅豁免于“禁止手写规则”这一条。 |
--css-ignore <dir> | node_modules、dist、build、coverage、.git、.next、.turbo、.vitest、var | 可重复。在遍历时额外跳过的目录名。 |
关于这条命令所强制执行的五条规则(TAB-E221–TAB-E225),参见 @tabula-css/stylelint-plugin;关于该门禁涵盖与未涵盖的内容,参见 CSS 治理。
scan
npx tabula scan在项目的源码 glob 上运行 Tailwind 自身的扫描器(@tailwindcss/oxide)——与 CSS 构建所使用的提取逻辑相同——并报告每一个既未注册又具有工具类形状(一条可解析的变体链、一个已注册的族前缀、一次封禁命中,或任意值/属性语法)的候选项,附带 file:line。之所以要有这层过滤,是因为该扫描器会提取文件中每一段“形似单词”的片段,包括普通的散文文本;如果全部报告出来,这个门禁就没法用了。发现问题时退出码为 1。
| 标志 | 默认值 | 含义 |
|---|---|---|
--strict | 关闭 | 当引用某条 tabula/ 规则的 eslint-disable 注释数量超过 budgets.maxSuppressions(TAB-W900)时,也判定为失败。 |
--out <dir> | .tabula | 从何处读取 registry.json。 |
抑制计数始终会被打印出来,即使没有加 --strict——一个没人看的预算,就不算预算。
一条能够解析、但其产物未被声明的变体链,会被报告为 TAB-E230("不在已注册的集合内,因此不产生任何 CSS")——这是 v0.2 的变体封闭性门禁。修复建议给出了两条出路:在 variants.products 中声明该产物并重新构建,或者把该链重写为规范的升序 rank 顺序。参数化的 aria-*/data-*/group-*/peer-* 变体在 v0.1 中不受支持,也不被此门禁覆盖。
migrate <what>
npx tabula migrate logical
npx tabula migrate spacing --write
npx tabula migrate merge
npx tabula migrate dark将代码迁移到该配置档案上的代码修改工具(codemod)。默认是演练模式(dry-run)——除非传入 --write,否则磁盘上不会有任何改动。其治理原则是:只重写那些可证明是一对一的部分;其余的一律用一条定位准确、说明清楚的 TODO 注释标记出来,而不是靠猜测。
| 子命令 | 作用 |
|---|---|
logical | 将 pl-/pr-/ml-/mr-/left-/right-/border-l-/border-r-/text-left/text-right → 转换为对应的逻辑形式(ps-、pe-、ms-、me-、start-、end-、border-s-、border-e-、align-start、align-end)。一对一,且仅限于类的 sink 位置。只修正轴向,不修正数值——之后运行 tabula scan 就能捕获一个未注册的目标数值。不需要已构建的注册表。 |
spacing | 将 space-x-*/space-y-* → 转换为 gap-x-*/gap-y-*,但仅当该元素能被证明带有匹配的 flex 方向(flex-row/flex-col)、且目标类已注册时才会转换。对于网格容器、负值、space-*-reverse、带变体前缀的类,或无法证明的轴向,一律拒绝转换(附带一条 TODO)。需要已构建的注册表——若没有则退出码为 2。 |
merge | 将 clsx / classnames / tailwind-merge 的 twMerge / 或某个外部的 cn 导入,重写为 @tabula-css/merge 的 cn。只改动导入语句;调用处保持不变(本地绑定名会通过 import { cn as clsx } from '@tabula-css/merge' 的形式被保留)。只处理单一说明符的情形——混合导入会被标记而不会被拆分。twMerge 会被重写,但始终会被标记:它按名称形状启发式规则进行合并,而 cn 按注册表声明的槽位归属进行合并,因此每一处调用点都需要人工复查。 |
dark | 即使加了 --write 也只报告,不改动。 列出每一处 dark:/light:/[data-theme=…]: 的用法,附带 file:line,以及为什么它需要一个令牌轴而非一次代码修改——该令牌在另一个主题下的字面量只存在于作者的设计意图之中,无法自动推导。 |
| 标志 | 默认值 | 含义 |
|---|---|---|
--write | 关闭 | 应用这些重写。若不加此标志,会打印一份统一 diff,且不做任何改动。 |
--out <dir> | .tabula | 从何处读取 registry.json(仅 migrate spacing 使用)。 |
退出码因子命令而异:0 表示无需处理,或 --write 已应用了每一项发现;1 表示仍有迁移工作待完成(一次带有待处理重写的演练运行,或任何被标记为 TODO 的发现——因此只要有任何用法,migrate dark 就始终是 1);2 表示工具本身出了问题或被误用(未知子命令、项目无法加载、缺少所需的注册表)。
except add
npx tabula except add \
--name card-shadow --type dimension --value 347px --families w \
--reason "Figma spec requires this exact width; no token is within 5%." \
--owner @design-systems --expires 2026-12-31 --allowed-in "src/marketing/**"通过将一个拟议的词汇表例外拼接进真实的令牌文档、并运行真实的 @tabula-css/tokens 校验器来校验它,然后打印出令牌文件的补丁。除非传入 --apply,否则不会写入任何内容。
| 标志 | 是否必需 | 含义 |
|---|---|---|
--name | 是 | 该例外的令牌名。 |
--type | 是 | DTCG 的 $type(dimension、color、duration 等)。 |
--value | 是 | 字面量 CSS 值;dimension/duration 值会被强制转换为 { value, unit }。 |
--families | 是 | 可重复/可用逗号拼接。该例外可以铸造的族前缀(w、h、p 等)。 |
--reason | 是 | 为什么没有已注册的令牌可以满足需求。 |
--owner | 是 | 对该例外负责的团队或个人。 |
--expires | 是 | YYYY-MM-DD。命名例外最多可以设定到 12 个月之后;--literal 逃生方式必须在 90 天内到期。 |
--allowed-in | 是 | 可重复。该例外的类可以出现在其中的 glob。 |
--literal | 否 | 将此逃生方式标记为第 2 通道(90 天的泄压阀),而非一个命名例外。 |
--chain | 否 | 注册一条一次性的变体链(hover:bg-accent-hover)作为一条例外,而非铸造一个取值——见下文。接受与理由/归属者/到期日相关的文书标志,而非 --type/--value/--families。 |
--apply | 否 | 将补丁写入拥有 exception 命名空间的令牌文件(如果没有则创建 tokens/exceptions.tokens.json)。不会重新构建——在你运行 tabula build 之前,.tabula/ 都是过期的。 |
--ticket、--description | 否 | 携带进补丁的额外元数据。 |
成功时,如果所请求的维度数值附近约 5%/2px 范围内已经存在某个令牌,会打印出该最接近的既有令牌(TAB-W401),促使人复用它,而不是铸造新的词汇。
链例外(--chain)
npx tabula except add --chain hover:bg-accent-hover \
--reason "One-off hover state the interaction preset does not cover." \
--owner @design-systems --expires 2026-12-31 --allowed-in "src/marketing/**"变体封闭性的对应机制(v0.2):取值形式铸造一个新的 class,而 --chain 则把单独的一条变体链,加在一个已经注册的工具类上加入白名单——否则,已声明产物模型(variants.products)会在扫描时拒绝它(TAB-E230)。它搭乘同一套例外机制——不加 --apply 就不会写入任何内容,运行的是真正的令牌校验器,并且要求 --reason、--owner、--expires 与 --allowed-in(到期日限制相同:命名例外 12 个月,--literal 则为 90 天)。--name 默认取该链的一个 DTCG 安全 slug;--type、--value 与 --families 不会被使用。参数化变体(group-*/peer-*/aria-*/data-*)会被拒绝——它们在 v0.1 中不受支持。这条链会落入注册表的 chainExceptions,读取器通过 hasChainClass() 来回答关于它的查询。
eject
npx tabula eject
npx tabula eject --to tabula-frozen --yes --write-imports实验性。 弹出(无论是这次单向冻结,还是 --report 反向映射分析)在 v0.3.0 中都以实验性形态发布——命令本身可用、有测试覆盖,但它的表面(标志、报告格式、层级表)在未来的次要版本中可能会发生变化。
把一份已验证的 .tabula/ 复制进一个项目自有的目录,该目录在原生的 @tailwindcss/cli 上编译,class 零改动,并且从此停止设计令牌流水线向它输送内容——一次单向冻结。默认是空跑: 不加 --yes,eject 会打印完整的计划(它将要复制的每一个文件、权限变更、它发现的 @import 重写、到期警告、单向门横幅,以及 cn() 策略提示),且不触碰任何东西。执行时,它会以 0644 写入每一份拷贝,并在目标目录中写入一份 EJECTED.md 出处文件。完整流程与它所处理的四种风险,见 弹出(Eject)。
| 标志 | 默认值 | 含义 |
|---|---|---|
--to <dir> | tabula-frozen | 冻结目标目录,位于项目根目录下。 |
--yes | 关闭(空跑) | 执行。不加此标志时,eject 只打印计划,不触碰任何东西。 |
--force | 关闭 | 允许一个已经包含文件的目标目录;否则一个非空目标会被拒绝(TAB-E240)。 |
--write-imports | 关闭 | 重写指向 .tabula/source.css 的项目 CSS @import 行,使其指向被冻结的拷贝。不加此标志时,eject 只会打印出它找到的文件以及确切的新 import 行。 |
--report | 关闭 | 仅分析,不冻结:把源码实际使用的每一个 class,按照相对原生 Tailwind v4 的可移植性分类到 A/B/C/D 四个层级,并写入 tabula-eject-report.md。参见报告层级。 |
--theme-port | 关闭 | 报告场景开关:按照 --tb-* 主题命名空间已被移植到原生命名空间(--color-*、--spacing-* 等)来分类,并在报告中包含移植说明。 |
--format=json | md | 配合 --report:以 JSON(顶层带 experimental: true)而非 Markdown 输出报告文档。 |
--write | 关闭 | 配合 --report:仅通过代码修改工具(codemod)引擎,应用可证明为 1:1 的层级 B 重命名(和 migrate 一样,先给出空跑 diff)。层级 C/D 的 class 永远不会被重写。 |
前置条件按顺序失败关闭:一个可加载的项目(TAB-E901);一份存在、未过期、未被手工编辑过的 .tabula/(与 doctor 相同的漂移门禁——TAB-E303 对应缺失/过期,TAB-E601 对应手工编辑或未被覆盖);以及一个可用的目标(TAB-E240,退出码为 2——出问题的是工具本身无法继续,而不是样式写错了)。一条已过期或将在 90 天内到期的例外会发出警告(TAB-W402)而不是阻塞:弹出之后不再有重新构建,因此常规的到期硬性报错(TAB-E141)再也不会触发。cn() 仍然需要 @tabula-css/merge 与被复制的 registry.json;用 tailwind-merge 替换它会改变渲染出的输出,EJECTED.md 会以书面形式说明这一点。
doctor
npx tabula doctor本地问题排查,按以下顺序检查:过期性(当前输入哈希与清单记录的哈希对比)、手工编辑过的产物(sha256 不匹配,TAB-E601)、目录覆盖情况(多余的文件,或清单中缺失的产物)、即将到期/已到期的例外(TAB-W401/TAB-E141)、@tabula-css/*/Tailwind 版本偏差(TAB-E302),以及逃生预算。任何一项硬性失败都会使退出码为 1,否则退出码为 0 并打印警告。
| 标志 | 默认值 | 含义 |
|---|---|---|
--out <dir> | .tabula | 从何处读取清单与产物。 |
doctor 不是一次经过身份验证的完整性检查——清单无法对自身进行哈希验证,因此一次一致的编辑(同时改动某个产物和其记录的哈希)在这里能够通过。真正的权威是 tabula build --check:它会从令牌和配置重新推导出每一个产物,并逐字节比对整个集合(包括清单本身),这也是为什么 CI 必须运行 --check 而不是 doctor。
explain <TAB-Exxx>
npx tabula explain TAB-E113完全离线地打印一个诊断代码的起因、该规则存在的原因,以及修复方法——取自 @tabula-css/core 冻结的 ERROR_CATALOG,即其他每一处诊断都会回退到的修复基准。一个无法识别的代码会得到最多三条编辑距离在 2 以内的建议。
canary
npx tabula canary生成一份经过精心设计、会违反 strict 预设所包含的每一条 ESLint 规则的测试样例(fixture),依据项目自己已构建的注册表以编程方式对其进行 lint,并在任何预期规则未能触发时判定为失败(TAB-E201)——这能捕获一个未接入的插件、一份被丢弃的配置,或一个失败开放的注册表,而这些是普通测试套件不会注意到的问题。需要安装 eslint 和 @typescript-eslint/parser;若未安装则退出码为 2。
| 标志 | 默认值 | 含义 |
|---|---|---|
--out <dir> | .tabula | 从何处读取 registry.json——它必须在 canary 运行之前就存在。 |
全局标志
| 标志 | 含义 |
|---|---|
--format=json | 在 stdout 上输出机器可读的信封 { tabula, ok, inputsHash, diagnostics };此模式下 stdout 不会输出任何其他内容。 |
--help | 打印用法说明。 |
参见
@tabula-css/registry—build所推导出、且其他每一条命令都会读取的产物。@tabula-css/stylelint-plugin—check:css与编辑器插件所共享的规则内核。@tabula-css/eslint-plugin—canary用来对其测试样例进行 lint 的strict预设。- 快速上手 — 完整的“构建 → lint → CI”演练。
- CSS 治理 —
check:css检查了什么、还未检查什么。 - 迁移 — 更深入的代码修改工具介绍,包括 shadcn 路径。
- 弹出(Eject) —
tabula eject的完整流程、四种风险,以及单向门策略。