首页/新闻资讯/正文详情

从 Bun 打包产物还原 claude-code 源码:源映射反向提取完整教程

发布时间:2026/9/26 11:02:12 来源:云帆数科 栏目:资讯中心
从 Bun 打包产物还原 claude-code 源码:源映射反向提取完整教程
1. 从 cli.js.map 里把 TypeScript 源码捞回来这件事到底在做什么claude-code 这类 CLI 工具发布到 npm 时你拿到的往往不是原始工程而是一个被 Bun 打包过的cli.js旁边躺着一个cli.js.map。很多人第一次打开这个目录会愣住bun.lock、cli.js、cli.js.map、sdk-tools.d.ts、vendor/、package.json就是没有src/。于是问题来了——原始 TypeScript 代码去哪了答案通常就藏在源映射里。Bun 在bun build时如果开启了 sourcemap会把「打包后代码的每个位置」和「原始文件路径、行列号」的对应关系写进.map文件。更关键的是很多构建配置会把原始源码文本直接塞进sourcesContent字段。这意味着只要.map是完整的你不需要原始仓库也能把 TypeScript 源码反向提取出来。这篇教程面向三类人想研究 claude-code 内部实现结构的开发者、想学习 Bun 打包产物逆向思路的工程师、以及手上正好有一个带.map的 Bun 项目想还原源码的人。我会用可复制的 Node 脚本、完整的参数说明和验证步骤带你从cli.js.map走到一个能读、能搜、能对比的restored-src/目录。整个过程不依赖任何特殊网络手段纯本地文件操作。先明确一个判断能不能还原取决于.map里有没有sourcesContent。有就是「复制粘贴级」还原没有只能拿到文件路径骨架内容得另想办法。所以第一步永远是先探针再动手。2. 前置准备拿到产物、确认 Bun 构建特征、装好解析依赖2.1 获取带源映射的 npm 包产物如果你要复现的是 claude-code 的产物结构可以用 npm 把包下载到本地再解包而不是直接全局安装。这样你能拿到完整的文件列表mkdir claude-code-inspect cd claude-code-inspect npm pack anthropic-ai/claude-code2.1.88 --registryhttps://registry.npmjs.org tar -xzf anthropic-ai-claude-code-2.1.88.tgz ls -la package/解包后你会看到类似这样的结构package/ ├── bun.lock ├── cli.js ├── cli.js.map ├── sdk-tools.d.ts ├── vendor/ ├── package.json ├── LICENSE.md └── README.md这里每个文件都有明确分工。bun.lock是 Bun 的依赖锁记录精确版本cli.js是打包入口头部通常带#!/usr/bin/env buncli.js.map是我们要的主角sdk-tools.d.ts只是类型声明不含实现vendor/一般是第三方依赖的编译产物不是原始业务源码。判断一个项目是不是 Bun 构建看bun.lock和cli.js的可执行头基本就能确认。2.2 源映射的两种形态在动手写脚本前先理解.map的两种存在形式这决定了你的提取策略。外部映射就是独立的cli.js.map文件内部是 JSON核心字段包括version、sources、sourcesContent、mappings、names。其中sources是原始文件路径数组sourcesContent是与之一一对应的源码文本数组。只要sourcesContent存在且长度匹配还原就是逐项写文件。内联映射则是把整个 JSON 做 base64 后塞进cli.js末尾的注释里形如//# sourceMappingURLdata:application/json;base64,xxxx。这种情况你需要先把 base64 解出来再解析。两种形态的处理逻辑一致只是「读取入口」不同。2.3 安装解析依赖最省事的方式是用成熟的source-map库它帮你处理mappings的 VLQ 解码和sourceContentFor查询npm init -y npm install source-map如果你不想引入依赖也可以直接JSON.parse后读sourcesContent数组对「只提取源码」这个目标来说完全够用。下面两种脚本我都会给。3. 可复制配置探针脚本 两种还原脚本骨架3.1 先跑探针确认 sourcesContent 是否存在不要一上来就写文件先花十秒确认数据在不在。新建probe.jsimport fs from fs; const mapFile process.argv[2] || package/cli.js.map; const raw JSON.parse(fs.readFileSync(mapFile, utf-8)); console.log(version:, raw.version); console.log(file:, raw.file); console.log(sources 数量:, raw.sources?.length ?? 0); console.log(sourcesContent 数量:, raw.sourcesContent?.length ?? 0); console.log(是否内联:, raw.sourcesContent ? 含内容 : 仅路径); // 打印前 10 个原始路径看看目录结构 (raw.sources || []).slice(0, 10).forEach((s, i) { const hasContent raw.sourcesContent?.[i] ? 有内容 : 无内容; console.log([${i}] ${s} - ${hasContent}); });运行node probe.js package/cli.js.map如果输出里sourcesContent 数量大于 0且每个 source 后面标着「有内容」那恭喜直接进入 3.2。如果全是「无内容」说明这个包只保留了路径映射你只能拿到文件树内容需要另寻途径。3.2 方案 A不依赖第三方库直接读 sourcesContent这是最轻量的写法适合只想快速拿到源码的场景。新建extract-raw.jsimport fs from fs; import path from path; const mapFile process.argv[2] || package/cli.js.map; const outputDir process.argv[3] || ./restored-src; const raw JSON.parse(fs.readFileSync(mapFile, utf-8)); const sources raw.sources || []; const contents raw.sourcesContent || []; if (!contents.length) { console.error(该 map 不含 sourcesContent无法直接还原源码); process.exit(1); } let ok 0, skip 0; sources.forEach((src, i) { const content contents[i]; if (content null) { skip; return; } // 去掉 webpack/bun 常见的协议前缀避免生成非法路径 const clean src.replace(/^(webpack|bun|file):\/\//, ); const outPath path.join(outputDir, clean); fs.mkdirSync(path.dirname(outPath), { recursive: true }); fs.writeFileSync(outPath, content, utf-8); ok; }); console.log(还原完成成功 ${ok} 个跳过 ${skip} 个); console.log(输出目录${path.resolve(outputDir)});运行node extract-raw.js package/cli.js.map ./restored-src3.3 方案 B用 source-map 库兼容 mappings 查询如果你后续还想做「根据打包后行列号反查原始位置」这类操作用source-map更合适。新建extract-sm.jsimport fs from fs; import path from path; import { SourceMapConsumer } from source-map; const mapFile process.argv[2] || package/cli.js.map; const outputDir process.argv[3] || ./restored-src; const rawMap JSON.parse(fs.readFileSync(mapFile, utf-8)); const consumer await new SourceMapConsumer(rawMap); let ok 0, skip 0; for (const source of consumer.sources) { const content consumer.sourceContentFor(source, true); if (!content) { skip; continue; } const clean source.replace(/^(webpack|bun|file):\/\//, ); const outPath path.join(outputDir, clean); fs.mkdirSync(path.dirname(outPath), { recursive: true }); fs.writeFileSync(outPath, content, utf-8); ok; } consumer.destroy(); console.log(还原完成成功 ${ok} 个跳过 ${skip} 个);注意sourceContentFor(source, true)的第二个参数传true表示「找不到内容时返回 null 而不是抛异常」这样脚本不会因为个别缺失项中断。3.4 处理内联映射的补充脚本如果cli.js末尾是 base64 内联映射先把它抽出来存成.mapimport fs from fs; const js fs.readFileSync(package/cli.js, utf-8); const m js.match(/sourceMappingURLdata:application\/json;base64,([A-Za-z0-9/])/); if (!m) { console.error(未发现内联映射); process.exit(1); } const json Buffer.from(m[1], base64).toString(utf-8); fs.writeFileSync(inline.js.map, json, utf-8); console.log(已导出 inline.js.map大小:, json.length);之后把inline.js.map喂给前面的脚本即可。4. 验证还原结果从文件树、内容特征到映射回查4.1 检查目录结构与文件数量还原完先别急着读代码做三层验证。第一层是数量对齐探针里sources有多少个restored-src/里就应该有多少个文件跳过项除外。find restored-src -type f | wc -l再对比一下扩展名分布正常应该以.ts、.tsx为主夹杂少量.json、.jsfind restored-src -type f | sed s/.*\.// | sort | uniq -c | sort -rn4.2 内容特征抽查第二层是内容真实性。打开几个文件确认它们是可读的 TypeScript而不是被压缩成一行的产物。重点看三处有没有import/export语句、有没有类型注解如: string、interface、缩进是否正常。如果全是单行超长字符串说明sourcesContent存的是压缩后内容还原价值有限。head -n 30 restored-src/src/index.ts grep -rl interface restored-src | head grep -rl export function restored-src | head4.3 用 mappings 做一次反向回查第三层最能说明问题拿cli.js里某个已知位置反查它对应哪个原始文件。这能证明映射关系是自洽的而不是拼凑的。import fs from fs; import { SourceMapConsumer } from source-map; const rawMap JSON.parse(fs.readFileSync(package/cli.js.map, utf-8)); const consumer await new SourceMapConsumer(rawMap); // 假设 cli.js 第 100 行第 5 列有个标识符 const pos consumer.originalPositionFor({ line: 100, column: 5 }); console.log(反查结果:, pos); consumer.destroy();如果pos.source指向restored-src/里真实存在的文件且pos.line落在该文件合理范围内说明还原链路完整。这一步做完你基本可以确信手上的源码是可用的。4.4 结合 sdk-tools.d.ts 交叉印证sdk-tools.d.ts是类型声明虽然不含实现但它的接口名、方法签名可以和还原出的源码互相印证。比如声明里有个ToolRegistry接口你在还原源码里搜ToolRegistry应该能找到对应实现或引用。这种交叉验证能帮你快速判断哪些文件是核心逻辑哪些是边角工具。5. 本篇常见错排查路径非法、内容缺失、编码乱码、映射错位5.1 生成路径报 ENOENT 或非法字符sources里的路径可能带webpack://、bun://前缀或者含../这种会跳出输出目录的相对路径。直接path.join会生成非法路径甚至写到目录外。解决办法就是脚本里那行replace(/^(webpack|bun|file):\/\//, )同时建议对..做一次过滤const safe clean.split(/).filter(p p ! .. p ! .).join(/);5.2 sourcesContent 存在但个别项为 null这是正常现象。有些构建工具对纯类型文件或空文件不写内容sourcesContent[i]就是null。脚本里用if (content null) { skip; return; }跳过即可不要因为个别缺失就判定整个还原失败。跑完看成功/跳过比例跳过占比低于 10% 都算健康。5.3 还原出的文件是乱码或二进制如果打开文件看到大量\u0000或乱码通常是编码判断错了。sourcesContent理论上都是 UTF-8 字符串但如果你手动从 base64 解出来又用错了编码就会出问题。统一用Buffer.from(x, base64).toString(utf-8)和fs.writeFileSync(p, content, utf-8)不要混用latin1。5.4 反查位置对不上originalPositionFor返回的line为null说明该位置没有映射信息常见于构建工具注入的运行时胶水代码。换个位置再试比如从cli.js里搜一个明显的函数名拿到它的真实行列号再反查。如果大面积返回null那要怀疑.map和cli.js版本不匹配——确认两者是同一个包、同一次构建的产物。5.5 还原后无法直接编译还原出的是「源码文本」不等于「可构建工程」。tsconfig.json、构建脚本、部分配置通常不在sourcesContent里。想让它跑起来需要根据package.json的依赖和sdk-tools.d.ts的类型定义手动补齐配置。这一步属于二次工程化不在「还原」范畴内心里有预期就不会卡住。6. 把还原脚本接进你的日常调试流源码还原出来只是起点。真正有价值的是把它变成一个可复用的调试习惯每次拿到新的 Bun 打包产物先跑probe.js判断可行性再跑extract-raw.js落地最后用originalPositionFor做一次抽样回查。这三步固定下来你面对任何带.map的产物都不会再抓瞎。如果你在还原过程中需要频繁验证某个模型对代码结构的理解或者想把还原出的片段丢给模型做解释可以走模型对话入口快速试如果是要长期做代码分析、批量处理多个包的还原任务用 Coding Plan 会更顺手把脚本和验证流程固化下来。接入相关的 Key 和文档在 API Keys 与接入文档里都能找到按需取用即可。还原脚本本身不复杂难的是判断「这份 map 值不值得还原」以及「还原结果可不可信」。把探针和回查这两步养成肌肉记忆你就能在几分钟内给出结论而不是对着一堆文件猜半天。

相关推荐

微软AI业务营收拆解:Azure AI、GitHub Copilot与Power平台在2024年7月的增长逻辑
微软AI业务营收拆解:Azure AI、GitHub Copilot与Power平台在2024年7月的增长逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:02:12

OpenCode 实战技巧:用 TaoToken 统一 Key 打通 CLI 编码工作流
OpenCode 实战技巧:用 TaoToken 统一 Key 打通 CLI 编码工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:02:12

携程旅游 AI 网关落地实践:基于 Higress 的 MCP 接入与网关选型配置指南
携程旅游 AI 网关落地实践:基于 Higress 的 MCP 接入与网关选型配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:02:12

基于MediaPipe Holistic的八段锦动作识别:75个关键点与DTW匹配实战
基于MediaPipe Holistic的八段锦动作识别:75个关键点与DTW匹配实战

简介:基于计算机视觉的八段锦智能辅助训练系统选用MediaPipe Holistic模型,可同时检测33个身体关键点和42个手部关键点,在自建测试集上对8个标准动作的识别准确率达92%。资源面向动作识别与姿态估计方向的开发者、科研人员,可落地… · 2026/9/26 11:37:15

基于STM32的智能鸽子驯养系统:从定时器到状态机的嵌入式实战解析
基于STM32的智能鸽子驯养系统:从定时器到状态机的嵌入式实战解析

如果你的课题或者自己的小项目恰好是“基于STM32的智能鸽子驯养系统”,先别急着把它当成一个冷门的养殖设备。我做完这个项目最大的感受是:它本质上是一个把STM32核心外设几乎全用上的综合嵌入式练习。定时器、PWM、输入捕获、编码器模式、通信接口、电源… · 2026/9/26 11:37:08

dalle3 图像生成实战:用 TaoToken 统一 Key 打通 better captions 工作流
dalle3 图像生成实战:用 TaoToken 统一 Key 打通 better captions 工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:37:08

CUDA版PyTorch安装实战:驱动检查、版本选择与验证排坑全指南
CUDA版PyTorch安装实战:驱动检查、版本选择与验证排坑全指南

很多人看到“CUDA版PyTorch”这串词,第一反应就是安装过程复杂、变量太多。我在Windows笔记本和Linux服务器上反复装过十几遍环境之后想告诉你,真正费时间的不是安装动作本身,而是几个特别容易让人卡住的概念——比如驱动和CUDA到底什么关系、… · 2026/9/26 11:37:08

PX4固件体系结构深度解析:从实时操作系统到uORB中间件
PX4固件体系结构深度解析:从实时操作系统到uORB中间件

1. 先搞清楚PX4到底是个什么东西我最早接触PX4的时候,跟很多人一样,以为它就是一套飞控固件,烧进Pixhawk里就能飞。后来真正开始看源码、改代码、调参,才发现事情没那么简单——PX4不是一个“程序”,而是一整套软件体系… · 2026/9/26 11:37:02

kubectl资源管理命令实战:从排查故障到集群运维的完整指南
kubectl资源管理命令实战:从排查故障到集群运维的完整指南

1. 为什么资源管理命令值得系统性掌握 1.1 从一次"排查半小时"的真实经历说起 大概两年前的一个工作日下午,集群告警突然嗡嗡响起来,某核心服务连续三次健康检查失败。我当时的反应和大多数刚上手 Kubernetes 的运维一样,先 kube… · 2026/9/26 11:36:56

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码