jinzd-ai-cli 0.1.26 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +92 -1
- package/dist/index.js +98 -38
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -337,7 +337,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
337
337
|
## 已知待改进项
|
|
338
338
|
|
|
339
339
|
### 低优先级
|
|
340
|
-
- [
|
|
340
|
+
- [x] **macOS/Linux 完整测试**(2026-03-01):已在 Linux 环境完成测试,基本无问题。跨平台逻辑(`$SHELL` fallback、UTF-8 env vars)验证通过。
|
|
341
341
|
- [x] **Token 用量显示**:已通过 `/cost` 命令实现(v0.1.23),显示 session 累计 input/output/total tokens。
|
|
342
342
|
- [ ] **GitHub 仓库迁移**:当前在 gitee,npm 包受众需要 GitHub 托管。
|
|
343
343
|
- [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
|
|
@@ -802,3 +802,94 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
802
802
|
| run_interactive stdin 截断 | 通过 shell 包装 spawn 时 stdin pipe 被截断 | 直接 `spawn(pythonExe, args)` 不经过任何 shell |
|
|
803
803
|
| Ctrl+C 在工具确认时异常退出 | SIGINT handler 直接调用 `handleExit()`,未检查 `confirm()` 状态 | SIGINT handler 先检查 `confirming` 标志,是则 `cancelConfirm()` 取消而非退出 |
|
|
804
804
|
| 日期注入在 pkg exe 中显示错误 | `toLocaleDateString/toLocaleTimeString` 依赖完整 ICU,pkg 精简版不支持 | 改为手动数字拼接:`${year}年${month}月${day}日 ${weekday} ${HH}:${mm}:${ss}` |
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## 代码审查报告(2026-03-01)
|
|
809
|
+
|
|
810
|
+
> 全面扫描 src/ 目录 35+ 个 TypeScript 源文件,共发现 **4 高危 + 8 中危 + 7 低危** 问题。
|
|
811
|
+
|
|
812
|
+
### 🔴 高危问题(立即修复)
|
|
813
|
+
|
|
814
|
+
**H1 — MCP pendingRequests 内存泄漏**
|
|
815
|
+
- **文件**:`src/mcp/client.ts`
|
|
816
|
+
- **描述**:进程崩溃时 `pendingRequests.delete(id)` 可能不触发,timer 未清理;长期运行时内存持续增长
|
|
817
|
+
- **修复**:在 `sendRequest` 中用 `finally` 确保 `pendingRequests.delete(id)` + `clearTimeout(timer)` 总被执行
|
|
818
|
+
|
|
819
|
+
**H2 — readline 竞态条件:`confirming` 标志失效**
|
|
820
|
+
- **文件**:`src/tools/executor.ts`(行 58, 155, 307, 320-356)
|
|
821
|
+
- **描述**:`batchConfirm()` 提前返回 `'none'` 时 `confirming = true` 与实际状态不匹配;`once('line')` 在 edge case 下可能触发两次,误消费用户输入;`ask_user` 工具的 `askUserContext.prompting` 存在同样问题
|
|
822
|
+
- **修复**:使用 `completed` 布尔标志防止二次触发,或改用 Promise-based 状态机
|
|
823
|
+
|
|
824
|
+
**H3 — 工具执行错误语义混淆(isError 混用)**
|
|
825
|
+
- **文件**:`src/tools/executor.ts`(行 128, 160, 170)
|
|
826
|
+
- **描述**:权限 deny 和用户取消均返回 `{ isError: false }`,AI 无法区分"用户拒绝"与"工具正常返回",可能产生错误推理
|
|
827
|
+
- **修复**:统一为 `isError: true`,内容前缀加 `[User cancelled]` 或 `[Permission denied]`
|
|
828
|
+
|
|
829
|
+
**H4 — MCP 断开后无恢复机制**
|
|
830
|
+
- **文件**:`src/mcp/client.ts` + `src/mcp/manager.ts`
|
|
831
|
+
- **描述**:服务器子进程意外退出后,客户端对象仍留在 Map 中;工具列表可见但调用时 silent failure;用户需重启 CLI 才能恢复
|
|
832
|
+
- **修复**:`callTool()` 中检测 `isConnected`,返回友好错误提示;可选:实现指数退避自动重连
|
|
833
|
+
|
|
834
|
+
### 🟠 中危问题(近期修复)
|
|
835
|
+
|
|
836
|
+
| # | 文件 | 问题描述 | 修复方向 |
|
|
837
|
+
|---|------|---------|---------|
|
|
838
|
+
| M5 | `src/tools/builtin/bash.ts:76-86` | `cwd` 指向不存在目录时不报错,AI 误判 cd 成功 | `existsSync` 校验后再设置 `effectiveCwd` |
|
|
839
|
+
| M6 | `src/tools/builtin/web-fetch.ts` | SSRF:重定向链中间 URL 不检查(仅检查首尾) | 对每个中间重定向 URL 均调用 `resolveAndCheck()` |
|
|
840
|
+
| M7 | `src/tools/builtin/run-interactive.ts:131-144` | stdin 写入无背压处理,大量输入可能溢出 | 监听 `drain` 事件,写满后暂停直到缓冲区释放 |
|
|
841
|
+
| M8 | `src/session/session-manager.ts` | 会话 JSON 损坏时错误被吞噬,无日志,用户无法诊断 | `catch` 中输出 `stderr` 警告含文件名和错误信息 |
|
|
842
|
+
| M9 | `src/mcp/client.ts` | `close()` 时事件监听器未移除,长期运行积累僵尸监听器 | `killProcess()` 中调用 `removeAllListeners()` |
|
|
843
|
+
| M10 | `src/tools/permissions.ts` | `pathPattern` 直接 `new RegExp()`,恶意配置可触发 ReDoS | 加 try/catch 包裹正则编译,或限制正则复杂度 |
|
|
844
|
+
| M11 | `src/repl/repl.ts`(上下文加载) | `contextFile` 设为相对路径可能遍历项目外目录 | `resolve()` 后校验路径以项目目录为前缀 |
|
|
845
|
+
| M12 | `src/mcp/manager.ts` | MCP 工具 schema 扁平化时丢失嵌套结构,AI 传参可能失败 | `convertDefinition()` 支持递归转换嵌套 object/array |
|
|
846
|
+
|
|
847
|
+
### 🟡 低危问题(后续改进)
|
|
848
|
+
|
|
849
|
+
| # | 文件 | 问题描述 |
|
|
850
|
+
|---|------|---------|
|
|
851
|
+
| L1 | `src/tools/builtin/run-tests.ts:35` | `JSON.parse(package.json)` 无细粒度错误处理 |
|
|
852
|
+
| L2 | `src/tools/builtin/web-fetch.ts` | 恶意 HTML 大量 script 标签时正则性能下降 |
|
|
853
|
+
| L3 | `src/session/session.ts` | `new Date(d.created)` 非法字符串返回 Invalid Date,比较失败 |
|
|
854
|
+
| L4 | `src/tools/builtin/ask-user.ts` / `google-search.ts` | 模块级全局上下文,多会话架构下会串扰 |
|
|
855
|
+
| L5 | `src/config/schema.ts` | `modelParams.timeout` 与 provider 级 `timeout` 优先级不清晰 |
|
|
856
|
+
| L6 | `src/tools/executor.ts` | `call.arguments['path']` 未验证是否绝对路径,null 传入可能异常 |
|
|
857
|
+
| L7 | `src/repl/repl.ts` | 主循环 `line` handler 是 async 但无整体 try/catch,未捕获异常可能停响应 |
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## 与 Claude Code 功能对比差距(2026-03-01)
|
|
862
|
+
|
|
863
|
+
### ✅ ai-cli 已超越或持平 Claude Code 的功能
|
|
864
|
+
|
|
865
|
+
- 多 Provider 支持(Claude Code 仅支持 Claude 系列)
|
|
866
|
+
- 三层级上下文文件(Claude Code 为双层)
|
|
867
|
+
- Dev State 开发状态交接
|
|
868
|
+
- Agent Skills 系统
|
|
869
|
+
- 企业级权限规则 + Hooks 系统
|
|
870
|
+
|
|
871
|
+
### ❌ 缺失功能路线图(新增)
|
|
872
|
+
|
|
873
|
+
#### P0 — 核心竞争力缺口
|
|
874
|
+
- [ ] **中断生成**:`Escape` 键立即停止 AI 流式输出(当前无法中断,必须等待完成)
|
|
875
|
+
- [ ] **多模态输入(图片)**:支持粘贴/引用图片路径作为输入(当前纯文本)
|
|
876
|
+
- [ ] **`/add-dir` 命令**:运行时动态添加目录到上下文
|
|
877
|
+
- [ ] **并行工具调用**:AI 一次返回多个工具同时执行(当前为分组串行)
|
|
878
|
+
|
|
879
|
+
#### P1 — 重要差距
|
|
880
|
+
- [ ] **`/memory` 命令**:查看/编辑/清理 memory.md 内容(当前只能由工具写入)
|
|
881
|
+
- [ ] **`/doctor` 健康检查**:诊断 API Key、网络、配置问题
|
|
882
|
+
- [ ] **`/bug` 反馈命令**:一键提交 bug 报告
|
|
883
|
+
- [ ] **Vim 编辑模式**:命令行输入支持 vim 键绑定
|
|
884
|
+
- [ ] **桌面通知**:长任务完成时发送系统通知
|
|
885
|
+
- [ ] **`--allowedTools` / `--blockedTools` 启动参数**:启动时动态限制工具集
|
|
886
|
+
- [ ] **流式 JSON 输出**:`--output-format streaming-json` 逐行输出(当前仅支持一次性 `--json`)
|
|
887
|
+
|
|
888
|
+
#### P2 — 体验增强
|
|
889
|
+
- [ ] **项目级 `.mcp.json`**:项目根目录独立 MCP 配置,优先全局 config
|
|
890
|
+
- [ ] **IDE 集成**:VS Code / JetBrains 扩展(架构已准备就绪)
|
|
891
|
+
- [ ] **OAuth/浏览器登录**:无需手动填 API Key
|
|
892
|
+
- [ ] **`--resume <id>` 启动参数**:命令行直接恢复指定会话
|
|
893
|
+
- [ ] **Extended Thinking**:Claude 3.7 深度推理模式集成
|
|
894
|
+
- [ ] **Word wrap 配置**:终端输出折行宽度可配置
|
|
895
|
+
- [ ] **主题/颜色自定义**:dark/light/custom 主题
|
package/dist/index.js
CHANGED
|
@@ -1526,7 +1526,11 @@ var SessionManager = class {
|
|
|
1526
1526
|
updated: new Date(data.updated),
|
|
1527
1527
|
title: data.title
|
|
1528
1528
|
});
|
|
1529
|
-
} catch {
|
|
1529
|
+
} catch (err) {
|
|
1530
|
+
process.stderr.write(
|
|
1531
|
+
`[Warning] Skipping corrupted session file "${file}": ${err instanceof Error ? err.message : String(err)}
|
|
1532
|
+
`
|
|
1533
|
+
);
|
|
1530
1534
|
}
|
|
1531
1535
|
}
|
|
1532
1536
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
@@ -1578,7 +1582,11 @@ var SessionManager = class {
|
|
|
1578
1582
|
matches
|
|
1579
1583
|
});
|
|
1580
1584
|
}
|
|
1581
|
-
} catch {
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
process.stderr.write(
|
|
1587
|
+
`[Warning] Skipping corrupted session file "${filePath}": ${err instanceof Error ? err.message : String(err)}
|
|
1588
|
+
`
|
|
1589
|
+
);
|
|
1582
1590
|
}
|
|
1583
1591
|
}
|
|
1584
1592
|
return results.sort((a, b) => b.sessionMeta.updated.getTime() - a.sessionMeta.updated.getTime());
|
|
@@ -3455,12 +3463,13 @@ var bashTool = {
|
|
|
3455
3463
|
let effectiveCwd = persistentCwd;
|
|
3456
3464
|
if (cwdArg) {
|
|
3457
3465
|
const resolved = resolve2(persistentCwd, cwdArg);
|
|
3458
|
-
if (existsSync6(resolved)) {
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
effectiveCwd = resolved;
|
|
3466
|
+
if (!existsSync6(resolved)) {
|
|
3467
|
+
throw new Error(
|
|
3468
|
+
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
3469
|
+
);
|
|
3463
3470
|
}
|
|
3471
|
+
effectiveCwd = resolved;
|
|
3472
|
+
persistentCwd = resolved;
|
|
3464
3473
|
}
|
|
3465
3474
|
let actualCommand;
|
|
3466
3475
|
if (IS_WINDOWS) {
|
|
@@ -4300,10 +4309,13 @@ var runInteractiveTool = {
|
|
|
4300
4309
|
let lineIdx = 0;
|
|
4301
4310
|
const writeNextLine = () => {
|
|
4302
4311
|
if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4312
|
+
const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
|
|
4313
|
+
const canContinue = child.stdin.write(line);
|
|
4314
|
+
if (canContinue) {
|
|
4315
|
+
setTimeout(writeNextLine, 150);
|
|
4316
|
+
} else {
|
|
4317
|
+
child.stdin.once("drain", () => setTimeout(writeNextLine, 150));
|
|
4318
|
+
}
|
|
4307
4319
|
} else if (!child.stdin.destroyed) {
|
|
4308
4320
|
child.stdin.end();
|
|
4309
4321
|
}
|
|
@@ -4450,28 +4462,46 @@ var webFetchTool = {
|
|
|
4450
4462
|
let rawHtml;
|
|
4451
4463
|
let finalUrl;
|
|
4452
4464
|
let contentType;
|
|
4465
|
+
const MAX_REDIRECTS = 10;
|
|
4466
|
+
const FETCH_HEADERS = {
|
|
4467
|
+
"User-Agent": "Mozilla/5.0 (compatible; ai-cli/1.0; +https://github.com/ai-cli)",
|
|
4468
|
+
Accept: "text/html,application/xhtml+xml,text/plain,*/*",
|
|
4469
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
4470
|
+
};
|
|
4453
4471
|
try {
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
if (
|
|
4469
|
-
|
|
4472
|
+
let currentUrl = url;
|
|
4473
|
+
let resp = null;
|
|
4474
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
4475
|
+
const parsedHop = new URL(currentUrl);
|
|
4476
|
+
if (isPrivateHost(parsedHop.hostname)) {
|
|
4477
|
+
throw new Error(`Blocked: redirect to private/internal address "${currentUrl}".`);
|
|
4478
|
+
}
|
|
4479
|
+
await resolveAndCheck(parsedHop.hostname);
|
|
4480
|
+
const r = await fetch(currentUrl, {
|
|
4481
|
+
signal: controller.signal,
|
|
4482
|
+
headers: FETCH_HEADERS,
|
|
4483
|
+
redirect: "manual"
|
|
4484
|
+
// 手动控制重定向
|
|
4485
|
+
});
|
|
4486
|
+
if (r.status >= 300 && r.status < 400) {
|
|
4487
|
+
if (hop >= MAX_REDIRECTS) {
|
|
4488
|
+
throw new Error(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
4489
|
+
}
|
|
4490
|
+
const location = r.headers.get("Location");
|
|
4491
|
+
if (!location) {
|
|
4492
|
+
resp = r;
|
|
4493
|
+
break;
|
|
4494
|
+
}
|
|
4495
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
4496
|
+
continue;
|
|
4470
4497
|
}
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
if (e.message.startsWith("Blocked:")) throw e;
|
|
4498
|
+
resp = r;
|
|
4499
|
+
break;
|
|
4474
4500
|
}
|
|
4501
|
+
clearTimeout(timeoutId);
|
|
4502
|
+
if (!resp) throw new Error(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
4503
|
+
finalUrl = currentUrl;
|
|
4504
|
+
contentType = resp.headers.get("content-type") ?? "";
|
|
4475
4505
|
if (!resp.ok) {
|
|
4476
4506
|
throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
|
|
4477
4507
|
}
|
|
@@ -5592,7 +5622,7 @@ var ToolExecutor = class {
|
|
|
5592
5622
|
if (this.permissionRules.length > 0) {
|
|
5593
5623
|
const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
|
|
5594
5624
|
if (action === "deny") {
|
|
5595
|
-
return { callId: call.id, content: `Permission denied by rule for tool: ${call.name}`, isError:
|
|
5625
|
+
return { callId: call.id, content: `Permission denied by rule for tool: ${call.name}`, isError: true };
|
|
5596
5626
|
}
|
|
5597
5627
|
if (action === "auto-approve") {
|
|
5598
5628
|
this.printToolCall(call);
|
|
@@ -5619,7 +5649,7 @@ var ToolExecutor = class {
|
|
|
5619
5649
|
return {
|
|
5620
5650
|
callId: call.id,
|
|
5621
5651
|
content: "User cancelled the operation.",
|
|
5622
|
-
isError:
|
|
5652
|
+
isError: true
|
|
5623
5653
|
};
|
|
5624
5654
|
}
|
|
5625
5655
|
} else if (dangerLevel === "destructive") {
|
|
@@ -5628,7 +5658,7 @@ var ToolExecutor = class {
|
|
|
5628
5658
|
return {
|
|
5629
5659
|
callId: call.id,
|
|
5630
5660
|
content: "User cancelled the operation.",
|
|
5631
|
-
isError:
|
|
5661
|
+
isError: true
|
|
5632
5662
|
};
|
|
5633
5663
|
}
|
|
5634
5664
|
this.printToolCall(call);
|
|
@@ -5721,7 +5751,7 @@ var ToolExecutor = class {
|
|
|
5721
5751
|
}
|
|
5722
5752
|
} else {
|
|
5723
5753
|
console.log(chalk8.gray(` [${i + 1}] `) + chalk8.dim("rejected"));
|
|
5724
|
-
results.push({ callId: call.id, content: "User rejected the operation.", isError:
|
|
5754
|
+
results.push({ callId: call.id, content: "User rejected the operation.", isError: true });
|
|
5725
5755
|
}
|
|
5726
5756
|
}
|
|
5727
5757
|
return results;
|
|
@@ -5752,7 +5782,10 @@ var ToolExecutor = class {
|
|
|
5752
5782
|
this.confirming = false;
|
|
5753
5783
|
resolve5(result);
|
|
5754
5784
|
};
|
|
5785
|
+
let completed = false;
|
|
5755
5786
|
const onLine = (line) => {
|
|
5787
|
+
if (completed) return;
|
|
5788
|
+
completed = true;
|
|
5756
5789
|
const input2 = line.trim().toLowerCase();
|
|
5757
5790
|
if (input2 === "a" || input2 === "all" || input2 === "y") {
|
|
5758
5791
|
cleanup("all");
|
|
@@ -5768,6 +5801,8 @@ var ToolExecutor = class {
|
|
|
5768
5801
|
}
|
|
5769
5802
|
};
|
|
5770
5803
|
this.cancelConfirmFn = () => {
|
|
5804
|
+
if (completed) return;
|
|
5805
|
+
completed = true;
|
|
5771
5806
|
process.stdout.write(chalk8.gray("\n(cancelled)\n"));
|
|
5772
5807
|
cleanup("none");
|
|
5773
5808
|
};
|
|
@@ -5916,8 +5951,15 @@ var ToolExecutor = class {
|
|
|
5916
5951
|
this.confirming = false;
|
|
5917
5952
|
resolve5(answer === "y");
|
|
5918
5953
|
};
|
|
5919
|
-
|
|
5954
|
+
let completed = false;
|
|
5955
|
+
const onLine = (line) => {
|
|
5956
|
+
if (completed) return;
|
|
5957
|
+
completed = true;
|
|
5958
|
+
cleanup(line.trim().toLowerCase());
|
|
5959
|
+
};
|
|
5920
5960
|
this.cancelConfirmFn = () => {
|
|
5961
|
+
if (completed) return;
|
|
5962
|
+
completed = true;
|
|
5921
5963
|
process.stdout.write(chalk8.gray("\n(cancelled)\n"));
|
|
5922
5964
|
cleanup("n");
|
|
5923
5965
|
};
|
|
@@ -6525,7 +6567,9 @@ var McpClient = class {
|
|
|
6525
6567
|
// ══════════════════════════════════════════════════════════════════
|
|
6526
6568
|
ensureConnected() {
|
|
6527
6569
|
if (!this.connected) {
|
|
6528
|
-
throw new Error(
|
|
6570
|
+
throw new Error(
|
|
6571
|
+
`MCP server [${this.serverId}] is not connected` + (this.errorMessage ? `: ${this.errorMessage}` : "")
|
|
6572
|
+
);
|
|
6529
6573
|
}
|
|
6530
6574
|
}
|
|
6531
6575
|
/** Promise 超时包装 */
|
|
@@ -6551,9 +6595,13 @@ var McpClient = class {
|
|
|
6551
6595
|
}
|
|
6552
6596
|
this.pendingRequests.clear();
|
|
6553
6597
|
}
|
|
6554
|
-
/**
|
|
6598
|
+
/** 杀掉子进程,并移除所有事件监听器防止僵尸引用 */
|
|
6555
6599
|
killProcess() {
|
|
6556
6600
|
if (this.process) {
|
|
6601
|
+
this.process.removeAllListeners("error");
|
|
6602
|
+
this.process.removeAllListeners("exit");
|
|
6603
|
+
this.process.stdout?.removeAllListeners("data");
|
|
6604
|
+
this.process.stderr?.removeAllListeners("data");
|
|
6557
6605
|
try {
|
|
6558
6606
|
this.process.stdin?.end();
|
|
6559
6607
|
this.process.kill();
|
|
@@ -6661,6 +6709,11 @@ var McpManager = class {
|
|
|
6661
6709
|
return {
|
|
6662
6710
|
definition,
|
|
6663
6711
|
execute: async (args) => {
|
|
6712
|
+
if (!client.isConnected) {
|
|
6713
|
+
throw new Error(
|
|
6714
|
+
`MCP server [${serverId}] is disconnected` + (client.errorMessage ? `: ${client.errorMessage}` : "") + `. Use /mcp to check status, or restart ai-cli to reconnect.`
|
|
6715
|
+
);
|
|
6716
|
+
}
|
|
6664
6717
|
try {
|
|
6665
6718
|
const result = await client.callTool(mcpTool.name, args);
|
|
6666
6719
|
if (result.isError) {
|
|
@@ -7011,7 +7064,14 @@ var Repl = class {
|
|
|
7011
7064
|
if (setting === false) return { layers: [], mergedContent: "" };
|
|
7012
7065
|
const cwd = process.cwd();
|
|
7013
7066
|
if (setting !== "auto") {
|
|
7014
|
-
const fullPath =
|
|
7067
|
+
const fullPath = resolve4(cwd, String(setting));
|
|
7068
|
+
if (!fullPath.startsWith(resolve4(cwd))) {
|
|
7069
|
+
process.stderr.write(
|
|
7070
|
+
`[Warning] contextFile path "${setting}" is outside current directory, ignoring.
|
|
7071
|
+
`
|
|
7072
|
+
);
|
|
7073
|
+
return { layers: [], mergedContent: "" };
|
|
7074
|
+
}
|
|
7015
7075
|
if (existsSync18(fullPath)) {
|
|
7016
7076
|
const content = readFileSync13(fullPath, "utf-8").trim();
|
|
7017
7077
|
if (content) {
|