Node.js 多平台兼容性检查器
功能概述
本技能静态分析用户指定的 Skill 中 scripts 文件夹下的 .js / .mjs 文件,找出所有可能导致跨平台不兼容的地方(路径分隔符、Shell 命令、环境变量、换行符等),并给出明确的优化建议。
环境说明
- Shell 类型:PowerShell 5(Windows)
- $SKILL_DIR:当前 Skill 所在目录(SKILL.md 所在的文件夹)
- scripts 目录:
$SKILL_DIR/scripts/ - 依赖安装:无需安装依赖,脚本使用纯 Node.js 内置模块
- 条件执行:本 Skill 运行命令时采用条件执行(前一条成功才执行下一条),跨平台规则如下:bash/zsh(Linux/macOS)用
&&;PowerShell 5(Windows)用; if ($?) { };禁止单&
重要:执行 scripts 目录下的脚本前,必须先
cd到$SKILL_DIR/scripts目录。
全业务脚本索引清单
| 脚本名称 | 功能说明 |
|---|---|
check-obfuscated.js |
检测 JS 文件是否被 javascript-obfuscator 混淆(正则 /_0x[a-f0-9]+/g,阈值 ≥ 10 视为混淆)。扫描 .js / .mjs 文件。默认递归扫描子目录,加 --no-recursive 仅扫描根目录。同时导出 isObfuscated(content, threshold) 供 check-cross-platform.js 复用 |
check-cross-platform.js |
静态分析 scripts/ 下源码的跨平台兼容性,输出 JSON 问题列表。只检测、不修改源代码。默认递归扫描子目录,加 --no-recursive 仅扫描根目录 |
核心约束(必须严格遵守)
- 只检测指定技能的 scripts 文件夹:必须由用户明确指定技能名称,只分析该技能里面的
$SKILL_DIR/scripts/文件夹及其子目录下的.js/.mjs文件,不检测其他任何文件夹(如scripts-backup、backup、src、references等一律禁止)。读取任何文件前,必须执行第四步的"路径边界自检"。 - 只分析,不修改文件:绝对不能使用 Edit、Write 等工具修改用户的源代码文件。只能读取(Read)和搜索(Grep/Glob)代码,然后把分析结果以文本形式回复给用户。
- 目标 Node.js 版本:v18.20.8 及以上。低于此版本的废弃 API 不需要担心;但高于此版本的实验性 API 要提醒用户。
- 语法范围:用户只写 ES6 模块语法(
import/export/await/ 顶层await),不写 TypeScript,不写 CommonJS(require)。如果代码里混用了 CommonJS,要指出来。 - 代码语言:只分析
.js/.mjs文件,不分析.ts/.jsx/.tsx。若scripts/下存在.cjs文件,作为警告项指出(因为.cjs会被 Node.js 按 CommonJS 解析)。 - 回复语言:全程用中文回复。
- 跳过混淆代码:对每个待分析文件,先检测是否为 javascript-obfuscator 生成的混淆代码。已混淆的代码不进入兼容性分析。检测算法复用 check-obfuscated.js 的核心逻辑。
执行步骤
第一步:确认技能并收集待分析文件
获取技能名称:用户必须明确指定要检查的技能名称(例如:
feishu-docx)。定位 scripts 文件夹:根据技能名称构建 scripts 文件夹路径,使用 Glob 工具列出该路径下所有的
.js/.mjs文件。检查 package.json:确认
scripts/或其上级技能根目录的package.json中是否包含"type": "module"。Node.js 会从文件所在目录向上查找 package.json,因此只要scripts/或技能根目录任意一处声明了"type": "module",.js文件就会按 ESM 解析。向用户确认:在开始分析前,向用户确认要检查的技能名称和 scripts 文件夹路径是否正确。
重要:如果用户未指定技能名称,或者指定了其他无关文件夹,必须拒绝执行,并提示用户只能检测指定技能下的 scripts 文件夹。
第二步:混淆检测(关键过滤步骤)
传入第一步确认的技能目录路径,通过 Shell 调用本技能自带的检测脚本。默认递归扫描 scripts/ 及其所有子目录,如果只想扫描根目录,加 --no-recursive:
注意:脚本在目标技能路径后自动追加了 /scripts ,--target 参数需要传入技能根目录而非 scripts 目录。
cd $SKILL_DIR/scripts; if ($?) { node check-obfuscated.js --target <技能目录路径> }
仅扫描根目录:
cd $SKILL_DIR/scripts; if ($?) { node check-obfuscated.js --target <技能目录路径> --no-recursive }
- 检测模式:
/_0x[a-f0-9]+/g - 判定阈值:单文件中该模式匹配次数 ≥ 10 即视为混淆
- 固定输出 JSON:脚本输出结构化 JSON,
obfuscated数组为已混淆文件列表,source数组为源码文件列表
根据输出分两路处理:
| JSON 字段 | 处理方式 |
|---|---|
source 数组 |
文件为源码,进入第三步运行跨平台静态分析器 |
obfuscated 数组 |
文件已混淆,跳过分析,记入报告的"已跳过清单" |
硬停止分支(必须执行):如果
source数组为空(即scripts/下所有文件都被判定为混淆),立即停止整个任务,仅输出以下一句话作为最终回复,不生成结构化报告、不扩展检测范围、不读取其他任何文件夹:无法完成分析:scripts 目录下无可分析源码(全部为混淆代码),拒绝输出报告。
第三步:运行跨平台静态分析器
对未混淆的源码调用本技能自带的 check-cross-platform.js,输出结构化 JSON。默认递归扫描 scripts/ 及其所有子目录,只扫描根目录加 --no-recursive:
cd $SKILL_DIR/scripts; if ($?) { node check-cross-platform.js --target <技能目录路径> }
仅扫描根目录:
cd $SKILL_DIR/scripts; if ($?) { node check-cross-platform.js --target <技能目录路径> --no-recursive }
- 该脚本自动扫描
scripts/下所有.js/.mjs文件,跳过已混淆文件,检测常见跨平台问题。 - 输出字段
issues为检测出的问题列表,每条包含file、line、severity、category、title、reason、suggestion、code。 - 输出字段
packageJsonType为"module"/"commonjs"/"missing",若.js文件缺少"type": "module",会以严重问题形式报告。
第四步:读取代码(补充人工复核)
路径边界自检(读取每个文件前必须执行):对待读取的文件路径进行验证,确认其绝对路径位于第一步定位的 scripts/ 目录内。可通过对两个路径做 path.resolve() 后比较前缀实现:path.resolve(filePath).startsWith(path.resolve(scriptsDir) + path.sep)。如果路径不在 scripts/ 目录内,立即停止读取,不允许以任何理由(包括"完成任务"、"找不到源码"、"参照对比"等)越界读取其他文件夹的文件。
此外,check-cross-platform.js 与 check-obfuscated.js 在扫描 scripts/ 时也会执行路径边界保护与符号链接跳过,防止意外越界。
使用 Read 工具逐个读取未混淆的 JS 文件。只读,不写。结合第三步的 JSON 结果,对照下方《兼容性检查清单》进行复核,补充分析器可能遗漏的上下文相关或更细微的问题。
第五步:按检查清单逐项分析
对照下方《兼容性检查清单》逐项扫描代码。每发现一处问题,记录:
- 文件路径(用可点击的
file:///链接形式给出,包含行号) - 问题代码片段
- 不兼容原因(说明在哪个平台会出问题)
- 优化建议(给出可直接替换的代码示例)
第六步:输出报告
按下方《报告格式》输出结构化报告。即使没有发现问题,也要明确告知用户"代码兼容性良好"。
兼容性检查清单
一、路径与文件分隔符(最高频问题)
- 硬编码路径分隔符:字符串中出现
\\或/用于拼接文件系统路径(不含 URL、正则表达式、import 路径等非文件系统场景)。- 错误示例:
const file = 'data\\config.json'/const file = 'data/config.json' - 正确做法:
import { join } from 'node:path'; const file = join('data', 'config.json')
- 错误示例:
- 硬编码绝对路径:出现
C:\Users\xxx、D:\、/home/xxx、/usr/local/...、/Users/xxx等平台特定的绝对路径。- 正确做法:用
os.homedir()、os.tmpdir()、process.cwd()动态获取。
- 正确做法:用
- 字符串拼接路径:使用
+ '/' +、+ '' +或模板字符串手动拼接路径片段。- 错误示例:
const p = dir + '/' + file - 正确做法:使用
path.join()/path.resolve()。
- 错误示例:
- 路径分隔符判断:用
''或'/'判断当前平台。- 正确做法:用
path.sep或process.platform。
- 正确做法:用
- 环境变量 PATH 分隔符:用
;或:分割PATH。- 正确做法:用
path.delimiter(Windows 是;,macOS/Linux 是:)。
- 正确做法:用
- 路径大小写敏感性:代码假设文件名大小写不敏感(Windows 行为),在 Linux 上会找不到文件。检查是否有同目录下仅大小写不同的文件名引用。
- Unicode 规范化差异(NFC vs NFD):macOS 文件系统使用 NFD(分解形式),Linux/Windows 使用 NFC(组合形式)。包含重音字符的文件名在 macOS 上创建后,传到 Linux 上可能因规范化形式不同而找不到文件。
- 错误示例:
fs.readFileSync('café.txt')— 该文件在 macOS 上实际存储为cafe\u0301.txt(NFD),在 Linux 上用 NFC 字符串查找会失败。 - 正确做法:避免在文件名中使用重音/非 ASCII 字符;或用
fs.readdir动态查找目标文件而非硬编码文件名。
- 错误示例:
- 用
path.join()拼接 URL:path.join()在 Windows 上会用\分隔,导致 URL 无效。- 错误示例:
const url = path.join('https://example.com/api', 'users')(Windows 上得到https://example.com/api\users) - 正确做法:
const url = new URL('./users', 'https://example.com/api/').href,或用字符串拼接'https://example.com/api/' + 'users'。
- 错误示例:
二、ESM 模块语法相关(用户只用 ESM)
- 使用了 CommonJS:出现
require()、module.exports、exports.xxx。- ESM 中这些全部不可用。
require替换:用import静态导入,或动态import()。
-
__dirname/__filename未使用 polyfill:若代码直接使用__dirname/__filename而未声明const __filename = fileURLToPath(import.meta.url)或const __dirname = dirname(fileURLToPath(import.meta.url)),则 ESM 下会报错。已正确 polyfill 的视为合规,不再报错。 - package.json 缺少 type 字段:如果用
.js扩展名写 ESM,需要确认scripts/或技能根目录的package.json里有"type": "module",否则 Node.js 会按 CommonJS 解析报错。如两处均无"type": "module",报告为严重问题。 - import 路径缺扩展名:ESM 中
import './foo'会失败,必须写import './foo.js'(除非是目录且有 index.js,或配了 exports)。 - 动态 import() 路径缺扩展名:
import('./foo')同样要求相对路径带扩展名,与静态 import 规则一致。 - 内置模块未使用 node: 前缀:v16+ 推荐
import fs from 'node:fs',可避免与同名用户模块冲突。如果代码用了不带node:前缀的内置模块导入,报告为建议项。 - 顶层 await:用户允许使用,无需报错,但要提醒需在 ESM 环境下才能用。
- 存在
.cjs文件:用户只写 ES6 JS / ESM,若scripts/下出现.cjs文件,提醒其会被 Node.js 按 CommonJS 解析,建议改为.js或.mjs。
三、Shell 命令与子进程
- 调用平台特定命令:通过
child_process.exec/execSync/spawn调用了平台特定的命令。- Windows 专属:
cmd、powershell、dir、copy、del、move、tasklist、taskkill、where、type、findstr。 - macOS/Linux 专属:
bash、sh、ls、cp、rm、mv、ps、kill、which、cat、grep、open。 - 跨平台替代:
ls->fs.readdir;cp->fs.copyFile;rm->fs.rm;cat->fs.readFile;open/start-> 用第三方包open,或按平台分支调用child_process.exec(macOS:open,Windows:start,Linux:xdg-open)。
- Windows 专属:
- shell 选项硬编码:
spawn(cmd, args, { shell: 'bash' })在 Windows 上会失败。 -
shell: true的平台差异:spawn(cmd, args, { shell: true })在 Windows 上使用cmd.exe,在 macOS/Linux 上使用/bin/sh。如果args中包含含空格或特殊字符的路径,在不同平台上可能被 shell 错误解析。 - exec 跨平台差异:
exec默认用cmd.exe(Windows)或/bin/sh(Linux/macOS),管道、重定向语法不同。
四、换行符与文本处理
- 硬编码
\r\n:字符串中出现\r\n用于读写文件或拼接多行文本。- 正确做法:改为
\n。跨平台共享的配置、数据、源码文件应保持一致的 LF 换行,避免 Windows 写出\r\n后传到 Linux 引入问题。读文件后用.replace(/\r\n/g, '\n')规范化再处理。
- 正确做法:改为
-
EOL误用于跨平台文件写入:writeFileSync(path, content + EOL)或JSON.stringify(obj) + os.EOL等,将平台相关换行符写入配置文件或数据文件。- 正确做法:改为
"\n"。os.EOL仅适合控制台输出(console.log/console.error)和平台原生格式文件(如.bat、.ps1),不能用于跨平台共享的文件,否则 Windows 上会写入\r\n,破坏跨平台一致性。
- 正确做法:改为
- 按
\n切分行:在 Windows 上文件可能是\r\n,切分后会残留\r。- 正确做法:用
.split(/\r?\n/)。
- 正确做法:用
- 正则未处理
\r:/^\s*$/m等正则在 Windows 文件内容上可能行为异常。
五、环境变量与用户目录
- 读取
HOME环境变量:Windows 上是USERPROFILE。- 正确做法:
os.homedir()自动处理。
- 正确做法:
- 读取
USERPROFILE环境变量:macOS/Linux 上没有。- 正确做法:
os.homedir()自动处理。
- 正确做法:
- 读取
APPDATA:macOS/Linux 没有。- 跨平台做法:用第三方包
env-paths,或按平台分支获取。
- 跨平台做法:用第三方包
- 环境变量名大小写:Windows 环境变量名不区分大小写,Linux/macOS 区分。代码不应依赖
process.env.PATH与process.env.Path等价。直接使用process.env.Path在类 Unix 上可能读不到值,统一用process.env.PATH。 -
os.tmpdir()返回值差异:Windows 返回C:\Users\xxx\AppData\Local\Temp,macOS 返回/var/folders/...,Linux 返回/tmp。不要假设固定路径或路径长度。
六、文件系统行为差异
- 文件权限操作:
fs.chmod/fs.chmodSync在 Windows 上行为不同(无法设置 Unix 权限位,只能切换只读位)。如果代码依赖 Unix 权限(如0o755),要提醒。 - 符号链接:Windows 上创建符号链接可能需要管理员权限。
fs.symlink的type参数在 Windows 上有特殊要求('dir'/'file'/'junction')。 - 文件路径长度:Windows 默认 260 字符限制(除非启用长路径支持)。如果代码会构造很深的路径,要提醒。
- 文件锁:Windows 上被占用的文件无法删除/重命名,macOS/Linux 通常可以。
fs.rename/fs.unlink在 Windows 上可能抛EPERM。 -
fs.watch()的recursive选项:recursive: true在 Linux 上不支持(Node.js v18.x),只在 Windows 和 macOS 上可用。如果代码用了递归监听,提醒 Linux 用户会报错。
七、Node.js API 版本兼容性(目标 v18.20.8+)
- 实验性 API:使用了带
ExperimentalWarning的 API(如node:test、fetch在 v18 是实验性的、--watch、node:sea等)。提醒用户部分功能可能需要加--experimental-*标志,且未来版本可能变更。 - 已废弃 API:使用
fs.exists(异步回调版本已废弃)、util.isArray等。提醒用新 API。fs.exists替代:fs.existsSync()或fs.promises.access()。 - Node.js 内置 fetch:v18+ 提供且可使用,但在整个 v18.x 中仍为实验性(会输出
ExperimentalWarning),直到 Node.js 21 才稳定。如果代码依赖fetch,提醒用户 v18.20.8 可用但会触发实验性警告。 - node: 协议前缀:v16+ 推荐
import fs from 'node:fs',可避免与同名用户模块冲突。如果代码用了不带node:前缀的内置模块导入,建议加上但不强制。 - 使用
process.exit()强制退出:代码中调用process.exit()强制终止进程。- 正确做法:① 在 async 函数中直接
return,让事件循环自然结束;② 若需非 0 退出码,设置process.exitCode = 1后 return。 - 原因:
process.exit()会强制立即终止进程,此时若存在未完成的异步 I/O(如 fetch 连接、文件流、定时器),可能触发 Node.js 底层 libuv 断言错误(如!(handle->flags & UV_HANDLE_CLOSING)),在 Node.js 24.x 等高版本中尤为常见。不使用process.exit()可以避免此类运行时崩溃,且不影响正常退出行为。
- 正确做法:① 在 async 函数中直接
八、平台分支判断
- process.platform 判断不完整:只判断了
'win32'和'darwin',漏了'linux'。常见平台值:'win32'/'darwin'/'linux'/'aix'/'freebsd'/'openbsd'/'sunos'。- 错误示例:
const isMac = platform === 'darwin'; const isWin = platform === 'win32'; const isOther = !isMac && !isWin;(应明确判断 linux 而非兜底)。
- 错误示例:
- 判断架构时遗漏常见值:
process.arch常见值'x64'/'arm64'/'ia32',Apple Silicon 是'arm64'。
九、第三方包兼容性
- 使用了原生模块:如
node-sass、bcrypt、sharp(部分版本)等含原生绑定,在不同平台需要编译。提醒用户确认目标平台有预编译包,或改用纯 JS 替代。 - 路径相关第三方包:优先用 Node.js 内置
path/os,避免多余依赖。
报告格式
输出报告时严格遵循以下结构:
# Node.js 跨平台兼容性检查报告
## 概要
- 待分析文件:N 个
- 已跳过(已混淆):M 个
- 实际检查文件:N - M 个
- 发现问题:X 处
- 严重(会导致运行失败):A 处
- 警告(特定场景下出问题):B 处
- 建议(最佳实践):C 处
- 兼容性评级:优秀 / 良好 / 需修复 / 不兼容
## 已跳过的文件(已混淆)
> 以下文件被检测为 javascript-obfuscator 混淆产物(_0x 标识符出现次数 ≥ 10),未进行兼容性分析。
| 文件路径 | 匹配次数 | 跳过原因 |
|---------|---------|---------|
| [文件名.js](file:///绝对路径) | 156 | 包含混淆特征标识符 |
(如 M = 0,此小节显示"无")
## 问题清单
### 问题 1:[严重/警告/建议] 简短标题
- **位置**:[文件名](file:///绝对路径#L行号)
- **代码**:
```js
// 问题代码
```
- **原因**:说明在哪个平台、什么场景下会出问题。
- **建议**:给出可直接替换的代码示例。
```js
// 优化后的代码
```
### 问题 2:...
## 未发现问题的检查项
(列出已扫描但未发现问题的检查类别,让用户知道你检查过哪些方面)
## 总结建议
(一两句话总结整体兼容性情况,并指出最需要优先修复的问题)
```
## 严重程度定义
- **严重**:在目标平台上会直接抛错或无法运行(如硬编码 `C:\` 路径在 Linux 上必失败)。
- **警告**:在特定场景下会出问题(如按 `\n` 切分 Windows 文件内容会残留 `\r`)。
- **建议**:能跑但不符合最佳实践(如未用 `node:` 前缀、未用 `path.join`)。
## 错误处理
| 错误场景 | 错误表现 | 处理方式 |
|---------|---------|---------|
| 用户未指定技能名称 | 无法定位 scripts 文件夹 | 拒绝执行,提示用户只能检测指定技能下的 scripts 文件夹 |
| scripts 目录不存在 | check-obfuscated.js 返回 error 字段 | 提示用户该技能无 scripts 目录,无法分析 |
| 所有文件均为混淆代码 | source 数组为空 | 立即停止整个任务,仅输出"无法完成分析:scripts 目录下无可分析源码(全部为混淆代码),拒绝输出报告。" |
| 文件路径越界 | 待读取文件不在 scripts/ 目录内 | 立即停止读取,不允许以任何理由越界读取其他文件夹的文件 |
## 工作原则
1. **务实优先**:聚焦会导致真实运行失败的问题,不要为了凑数量而鸡蛋里挑骨头。
2. **给出可复制代码**:每个建议都要给出用户能直接粘贴使用的代码片段。
3. **覆盖三大平台**:Windows、macOS、Linux 都要考虑到。
4. **尊重用户习惯**:用户只用 ESM,建议代码也用 ESM 语法。
5. **只读不写**:绝对不要修改用户的源代码文件,分析结果只通过对话回复。
6. **链接化路径**:所有提到的文件路径都用 markdown 链接格式给出。
```