Bruno 磁盘 DSL 与序列化变更规范:.bru 与 .yml 双格式持久化的兼容性契约
Bruno 磁盘 DSL 与序列化变更规范.bru 与 .yml 双格式持久化的兼容性契约【免费下载链接】brunoOpensource IDE For Exploring and Testing APIs (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno本文以 Bruno 仓库中的工程规则文档 dsl-changes.md 为主体系统讲解 Bruno 如何将用户创建的集合、请求、环境变量与配置以纯文本文件形式持久化到磁盘与 Git 仓库以及修改这种落盘形状时必须遵守的兼容性契约只加不改、双向格式同步、无损耗往返、读取时迁移。读完本文你能掌握在bruno-filestore、bruno-lang/v2、bruno-schema等包中安全新增持久化字段的完整路径与验证方法。Bruno 的两种落盘格式Bruno 把用户在应用中创建的一切——collections集合、requests请求、environments环境、folders文件夹和 config配置——都以纯文本文件形式持久化在用户磁盘和 Git 仓库中。落盘格式共有两种外加两类辅助文件.bruBru DSL读与写都走 bruno-lang/v2/src 中的 v2ohm-js语法经由 bruno-filestore/src/formats/bru 的bruToJsonV2/jsonToBruV2完成。所有.bru相关改动都应落在 v2 侧v1 仅作历史保留。.ymlOpenCollection YAML由 bruno-filestore/src/formats/yml 处理。这是当前的DEFAULT_COLLECTION_FORMAT——规范定义在 constants.tsexport const DEFAULT_COLLECTION_FORMAT: CollectionFormat yml并在渲染进程侧的 constants.js 中重复了一份同样为yml。因此新建集合默认是.yml格式除非用户主动选择其他格式。bruno.json每个集合一份的配置经由stringifyCollection处理见 bruno-filestore/src/index.ts 中stringifyCollection(collectionObj, brunoConfig, options)的默认参数{ format: DEFAULT_COLLECTION_FORMAT }。.env文件环境变量文件经由parseDotEnv/dotenvToJson处理。需要特别澄清两个看起来像、实则无关的部分bruno-toml 包是被遗弃的孤儿——除了它自己的测试外没有任何地方 import 它也不在任何序列化路径上。做 DSL 变更时永远不需要碰它。preferences.json是应用级 electron-store 状态位于userData目录下不属于集合的落盘契约因此不在本规范范围内。为什么落盘形状是公开契约规则文档强调的核心判断是这些文件的生命周期会超过写入它的那个 Bruno 版本。具体场景有三旧版 Bruno 要能读取新版写出的文件使用不同版本的团队成员在同一个仓库中共享这些文件用户会手工提交甚至手工编辑这些文件。因此对落盘形状的任何修改都是一次公开契约public contract的变更。改错后果很直接解析失败、集合损坏或者用户数据被静默丢弃——而且这些数据所在的集合你根本无法伸手去修。另一个容易被忽视的事实成为这些文件的内存对象往往是在远离序列化层的地方组装的——在bruno-electron的 IPC handler 里或bruno-app的 Redux 里——然后才被交给bruno-filestore。filestore 无法感知某个字段的形状在上游已经变了。所以影响 DSL 的变更可能源自数据路径上的任何包而不仅是格式包本身。这条规则的实际适用范围是任何构建、修改或序列化集合/请求/环境/配置对象的位置——即序列化包加上bruno-app、bruno-electron、bruno-cli。文档点名的典型事故模式是在 app/electron 里加了一个持久化字段却没有把它接入bruno-filestore两种格式都要和bruno-schema——结果保存时数据被静默丢弃或直接报错。这正是该规则要防住的那类 bug。十条规则逐条解读1. 避免不必要的 DSL 变更先问自己这个需求能否在内存里、在 UI 层、或作为派生值解决而不触碰任何序列化形状多数增强都可以。只有当数据确实必须持久化时才去改落盘格式。2. 只加新、且必须可选新字段必须是可选的并带有安全默认值使得没有该字段的旧文件依然能解析、能正常工作。永远不要重命名、删除、改变现有字段的用途或类型也不要改变其语义——旧文件和旧版本应用都依赖当前含义。3. 往返必须无损耗parse(stringify(x))必须等于x且stringify永远不能丢弃它不认识字段。由新版本写出的文件被旧版本打开后重新保存不得丢失新字段。4. 在写出的字节上验证转义而不是在内存里当值被嵌入分隔符块.bru的多行…、注解参数时内存层面的escape/unescape往返测试可能通过而落盘文件依然是损坏的——解析器会在第一个被重新引入的分隔符处提前终止。规则要求对实际字节做parse(read(serialize(x)))测试并针对引号/反斜杠连续序列做模糊测试fuzz。转义要按单字符粒度进行把多字符分隔符整体切分来转义例如split()会留下碎片在长度 ≥ 分隔符的连续串中重新组合起来正确做法是先转义反斜杠再逐一转义每个单分隔符字符。仓库中可直接对照的实现是 bruno-lang/v2/src/utils.js 中的escapeMultilineDescription// Escapes a multiline descriptions own delimiter () so it can safely round-trip // inside a ... block. Any pre-existing \ must be doubled first so decoding // can tell it apart from the backslashes introduced by escaping . const escapeMultilineDescription (value) value.split(\\\).join(\\\\\).split(\\\).join(\\\\\\\\\); const unescapeMultilineDescription (value) value.split(\\\\\\\\\).join(\\\).split(\\\\\).join(\\\);注释明确说明了两步顺序的动机先加倍已有的\解码时才能把它与为转义而引入的反斜杠区分开。5. 新语法会打穿旧解析器——不只是丢字段只加可选字段保护的是字段级兼容但新增.bru语法新的转义形式、新的块分隔符、新的注解文法会让新版写出的文件在旧版本里直接解析失败——旧版本根本没有处理它的代码。这是超出丢字段范畴的前向兼容破坏需要在发布前做出显式决策并给出一条兼容路径。6. 内联 description 一律是字符串每一个内联description字段headers、params、assertions、variables、body 条目等在内存中都存为普通string。yml 侧解析器在读取时接受裸字符串或旧式的{ content }对象并归一化为字符串见 formats/yml/common 下的headers.ts、variables.ts、assertions.ts、actions.ts以及parseEnvironment.ts、body.ts写出时只产出裸字符串。这种读接受对象 / 写产出字符串的不对称是既定约定不是有损往返common/variables.spec.ts 就是这条约定的证明。.bru侧没有{ content }处理description 直接从语法中存为注解/文本块字符串。新的内联 description 类字段应遵循这个 string-only 形状。例外——docs不是 string-only。集合/文件夹的文档docs在 yml 层被写为对象{ content, type: text/markdown }见 stringifyCollection.ts 与 stringifyFolder.ts读取时再归一化回字符串.bru侧则写为普通文本块。所以遵循 string-only 形状只适用于内联description不适用于docs这类文档字段——复制任一模板之前先判断新字段属于哪一族。7. 两种格式必须锁定同步lockstep任何新增或变更的字段都必须在bru和yml两侧分别处理 parse 与 stringify否则同一个集合会因为格式不同而行为不同。关键点不存在专门的bru↔yml转换器。请求在两种格式之间迁移的方式是用源格式 parse 成共享内存对象再用目标格式重新 stringify。仓库中可以验证这条链路的真实调用是 SaveTransientRequest/index.js 向renderer:save-transient-requestIPC 通道传递sourceFormattargetFormat由 bruno-electron/src/ipc/collection.js 中的对应 handler 处理。因此只在一种格式里处理的字段会在请求从.bru集合复制/保存进.yml集合或反向的那一刻被静默丢弃。还要注意unset未设置情形一个字段在.bru路径默认/持久化为在.yml路径却是1或相反就是分叉 bug。默认 app-data 工作区与自定义文件系统工作区是同一类孪生体——一个工作区能持久化、另一个静默丢弃的值属于同级别的问题。新字段要更新的每一层bruno-schema-types类型、bruno-schemaYup 校验、bruno-filestore两种格式、bruno-converters导入/导出若.bru语法本身变化还包括bruno-langv2的文法。8.bruno-schemaYup不是可选的——它会拒绝你的字段集合/请求/环境的 Yup schema 都声明了.noUnknown(true)且是strict的并且校验发生在保存路径上例如bruno-app的slices/collections/actions.js中的itemSchema.validate、bruno-electron的store/global-environments.js中的environmentSchema.validateSync。这一点在 bruno-schema/src/collections/index.js 中可以直接确认——文件中大量 schema 都挂了.noUnknown(true)。结论一个能往返通过 filestore、但没有加入packages/bruno-schema的新序列化字段会在保存时抛出校验错误。bruno-schema运行时 Yup与bruno-schema-typesTS 类型是两个不同的包两者都必须更新。9. 形状必须变更时——读取时迁移绝不让用户手改文件加一个读取时的兼容垫片compat shim在解析的同时把旧形状升级为新形状沿用仓库既有模式ensureAuthV3Rc1BackwardsCompatibility位于 parseItem.ts——已确认存在于该文件第 33 行并在同文件第 68 行被parsedItemYml的解析路径调用formats/bru/index.ts中 pre-v3 的 status/statusText 交换处理。任何对旧形状的移除都要以 major 版本升级为门槛并留下带日期的TODO(remove after vN)。10. 为规模与一致性设计命名按永久来对待新键要跟随现有meta/section 块的命名与嵌套meta.name、meta.seq、meta.type、meta.tags……。优先选择能干净扩展的结构——带键的列表优于位置列表对象优于不断加宽的联合类型——并与相邻字段的既有建模方式保持一致。注意磁盘上的meta {}块并不与类型一一对应在bruno-schema-types中这些键被拍平到Item上seq、name、type、tags、description直接挂在 item 上而 v2 ohm 文法把meta当作开放字典处理——上面四个名字只是被建模的那些不是封闭集合。属性名要当作永久资产来命名DSL 键写一次实际上就永远定下来了重命名会打穿每一个现有文件并强制加兼容垫片。这些名字是用户可见的——人会读并手工编辑.bru/.yml——也会被推理集合的 AI agent 读取。选择清晰、描述性、完整拼写、无晦涩缩写与歧义的名字并与现有键一致一次做对。一个属性名值得比普通变量更严格的审视。11. 用测试证明契约为新字段添加往返测试parse → stringify → parse两种格式都要外加一个旧格式的黄金 fixture——它必须仍然能原样解析。仓库中的既有模式是每种序列化器旁边成对出现的parseItem.spec.ts/stringifyItem.spec.ts以及 collection、folder、environment 的parse*/stringify*位于 bruno-filestore/src/formats/yml 下parseItem.spec.ts与stringifyItem.spec.ts等文件已确认存在于该目录字段级用例可参考 common/variables.spec.ts 与 common/datatype.spec.ts。新增一个持久化字段通常要动的文件添加单个字段往往会横跨多个包.bru中心的改动大部分落在bruno-lang/v2bruno-lang/v2/src ——.bru的入口bruToJson.js、collectionBruToJson.js、envToJson.js、jsonToBru.js、jsonToCollectionBru.js、jsonToEnv.js外加共享辅助函数所在的utils.jsbruno-filestore/src/formats/yml ——.yml侧的 parse和stringifybruno-schema/src/collections/index.js —— Yup schema漏掉它就会在保存时失败bruno-schema-types/src/collection/item.ts —— TypeScript 类型bruno-converters以及bruno-electron/bruno-app中构建该对象的 collection utils序列化器旁边的往返 specsformats/yml/parseItem.spec.tsstringifyItem.spec.ts。方法论上规则文档给出了一条实操建议先在代码里找一个可比的、已经存在的字段照着它的接线方式再接入你的字段。修改 DSL 前的检查清单原文档给出的 checklist完整保留如下变更确属必要无法在内存 / UI 层解决新字段可选且带安全默认值没有重命名、删除或改类型的字段bru和yml两侧都处理了parse和stringify 都覆盖了类型bruno-schema-types、Yup schemabruno-schema、converters 都更新了旧文件仍能以不变形式解析——形状有变时已加读取时兼容垫片没有引入旧解析器读不了的.bru新语法或已确定前向兼容路径任何新转义都已通过落盘后重解析 fuzz验证且按单字符而非整体分隔符转义bru/yml以及默认 vs 自定义工作区行为一致包括 unset 情形已添加往返测试 旧格式 fixture 测试命名与结构与现有块一致且可扩展小结把 dsl-changes.md 压缩成一句话就是Bruno 的.bru与.yml文件是比应用本身寿命更长的公开契约任何触碰落盘形状的改动都要以旧文件、旧版本、手工编辑者三者都不受伤害为前提用双格式双向处理、bruno-schema/bruno-schema-types同步、读取时迁移和无损耗往返测试来兑现这份契约。对照仓库源码验证过的事实锚点包括默认格式常量 constants.ts、v2 转义实现 utils.js、Yup 的noUnknown(true)声明 bruno-schema/src/collections/index.js以及格式迁移走共享内存对象而非直接转换的 IPC 链路 collection.js。【免费下载链接】brunoOpensource IDE For Exploring and Testing APIs (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
