@ahoo-wang/godex 0.0.1 → 0.0.4

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/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  OpenAI Responses API gateway — translates `/v1/responses` into upstream Chat Completions API calls, so **any LLM provider can drive Codex**.
4
4
 
5
+ [![codecov](https://codecov.io/gh/Ahoo-Wang/Godex/graph/badge.svg?token=dJQrmUAiXu)](https://codecov.io/gh/Ahoo-Wang/Godex)
5
6
  [![Bun](https://img.shields.io/badge/runtime-bun-f9f1e0?logo=bun)](https://bun.sh)
6
7
  [![TypeScript](https://img.shields.io/badge/lang-typescript-3178c6?logo=typescript)](https://www.typescriptlang.org/)
7
8
 
@@ -0,0 +1,473 @@
1
+ # Godex
2
+
3
+ OpenAI Responses API 网关 — 将 `/v1/responses` 请求转换为上游 Chat Completions API 调用,让**任何 LLM 提供商都能驱动 Codex**。
4
+
5
+ [![codecov](https://codecov.io/gh/Ahoo-Wang/Godex/graph/badge.svg?token=dJQrmUAiXu)](https://codecov.io/gh/Ahoo-Wang/Godex)
6
+ [![Bun](https://img.shields.io/badge/runtime-bun-f9f1e0?logo=bun)](https://bun.sh)
7
+ [![TypeScript](https://img.shields.io/badge/lang-typescript-3178c6?logo=typescript)](https://www.typescriptlang.org/)
8
+
9
+ ## 架构
10
+
11
+ ```mermaid
12
+ C4Context
13
+ title Godex — 系统上下文
14
+
15
+ Person(user, "开发者 / Codex CLI", "通过 OpenAI 兼容端点<br/>发送 Responses API 请求")
16
+ System(godex_svr, "Godex 服务器", "转换 Responses API → Chat Completions API<br/>基于 Bun HTTP 服务器,端口可配置")
17
+ SystemDb(sessions, "会话存储", "存储响应历史,用于<br/>previous_response_id 链式解析<br/>SQLite(持久化)或内存")
18
+ System_Ext(zhipu, "智谱 (Zhipu)", "Chat Completions API 提供商")
19
+ System_Ext(openai, "OpenAI", "Chat Completions API 提供商")
20
+ System_Ext(other, "自定义提供商", "任何 Chat Completions<br/>兼容后端")
21
+
22
+ Rel(user, godex_svr, "POST /v1/responses, GET /v1/models, GET /health", "HTTP/SSE")
23
+ Rel(godex_svr, sessions, "保存 / 解析链")
24
+ Rel(godex_svr, zhipu, "POST /chat/completions", "HTTPS")
25
+ Rel(godex_svr, openai, "POST /chat/completions", "HTTPS")
26
+ Rel(godex_svr, other, "POST /chat/completions", "HTTPS")
27
+ ```
28
+
29
+ ## 请求流程
30
+
31
+ ```mermaid
32
+ sequenceDiagram
33
+ actor C as 客户端 (Codex CLI)
34
+ participant R as Router 路由
35
+ participant AC as ApplicationContext 应用上下文
36
+ participant RC as ResponsesContext 响应上下文
37
+ participant MR as ModelResolver 模型解析器
38
+ participant SS as SessionStore 会话存储
39
+ participant REG as Registrar 注册器
40
+ participant A as Adapter 适配器 (DefaultAdapter)
41
+ participant PM as ProviderMapper 提供商映射器
42
+ participant CC as ChatClient 聊天客户端
43
+ participant UP as 上游 API
44
+
45
+ C->>R: POST /v1/responses
46
+ R->>RC: ResponsesContext.create(app, body)
47
+
48
+ activate RC
49
+ RC->>MR: resolve(model)
50
+ MR-->>RC: { provider, model }
51
+ RC->>RC: 验证提供商配置
52
+
53
+ opt previous_response_id
54
+ RC->>SS: resolveChain(id)
55
+ SS-->>RC: 会话快照
56
+ end
57
+
58
+ RC->>REG: resolve(provider)
59
+ REG-->>RC: Provider 实例
60
+ deactivate RC
61
+
62
+ alt stream = true
63
+ R->>A: adapter.stream(ctx)
64
+ activate A
65
+ A->>PM: request.map(ctx)
66
+ PM-->>A: 上游请求
67
+ A->>CC: streamChat(req)
68
+ CC->>UP: POST (SSE)
69
+ UP-->>CC: SSE 数据块
70
+ CC-->>A: ReadableStream<SSE>
71
+ A->>A: pipeTransform → ProviderEventToResponseTransformer
72
+ A->>A: pipeTransform → ResponseSessionPersistenceTransformer
73
+ A-->>R: ReadableStream<ResponseStreamEvent>
74
+ deactivate A
75
+ R->>R: pipeTransform → ResponseSseEncodeTransformer
76
+ R-->>C: SSE 字节流
77
+ else stream = false
78
+ R->>A: adapter.request(ctx)
79
+ activate A
80
+ A->>PM: request.map(ctx)
81
+ PM-->>A: 上游请求
82
+ A->>CC: chat(req)
83
+ CC->>UP: POST
84
+ UP-->>CC: JSON 响应
85
+ CC-->>A: 上游响应
86
+ A->>PM: response.map(ctx, res)
87
+ PM-->>A: ResponseObject
88
+ A->>SS: save(session)
89
+ A-->>R: ResponseObject
90
+ deactivate A
91
+ R-->>C: JSON 响应
92
+ end
93
+ ```
94
+
95
+ ## 组件模型
96
+
97
+ ```mermaid
98
+ classDiagram
99
+ direction TB
100
+
101
+ class ApplicationContext {
102
+ +config: GodexConfig
103
+ +logger: Logger
104
+ +resolver: ModelResolver
105
+ +registrar: Registrar
106
+ +adapter: Adapter
107
+ +sessionStore: ResponseSessionStore
108
+ }
109
+
110
+ class ResponsesContext {
111
+ +app: ApplicationContext
112
+ +request: ResponseCreateRequest
113
+ +session: ResponseSessionSnapshot
114
+ +resolved: ResolvedModel
115
+ +provider: Provider
116
+ +responseId: string
117
+ +requestId: string
118
+ +logger: Logger
119
+ +create(app, body)$ Promise~ResponsesContext~
120
+ }
121
+
122
+ class ModelResolver {
123
+ -defaultProvider: string
124
+ -providerConfigs: Record
125
+ +resolve(model) ResolvedModel
126
+ }
127
+
128
+ class Registrar {
129
+ -factories: Map~string, ProviderFactory~
130
+ +registerFactory(name, factory)
131
+ +build(providers)
132
+ +resolve(name) Provider
133
+ +list() string[]
134
+ }
135
+
136
+ class Adapter {
137
+ <<interface>>
138
+ +request(ctx) Promise~ResponseObject~
139
+ +stream(ctx) Promise~ReadableStream~
140
+ }
141
+
142
+ class DefaultAdapter {
143
+ +request(ctx) Promise~ResponseObject~
144
+ +stream(ctx) Promise~ReadableStream~
145
+ }
146
+
147
+ class Provider {
148
+ <<interface>>
149
+ +name: string
150
+ +mapper: ProviderMapper
151
+ +chatClient: ChatClient
152
+ +capabilities: ProviderCapabilities
153
+ }
154
+
155
+ class ProviderMapper {
156
+ <<interface>>
157
+ +request: RequestMapper
158
+ +response: ResponseMapper
159
+ +stream: StreamMapper
160
+ }
161
+
162
+ class ChatClient {
163
+ <<interface>>
164
+ +chat(req) Promise~TRes~
165
+ +streamChat(req) Promise~ReadableStream~
166
+ }
167
+
168
+ class RequestMapper {
169
+ <<interface>>
170
+ +map(ctx) TReq
171
+ }
172
+
173
+ class ResponseMapper {
174
+ <<interface>>
175
+ +map(ctx, result) ResponseObject
176
+ }
177
+
178
+ class StreamMapper {
179
+ <<interface>>
180
+ +map(ctx, event) ResponseStreamEvent[]
181
+ +buildResponseObject(ctx, state) ResponseObject
182
+ }
183
+
184
+ class ResponseSessionStore {
185
+ <<interface>>
186
+ +get(id) StoredResponseSession
187
+ +save(session, opts)
188
+ +resolveChain(id, opts) ResponseSessionSnapshot
189
+ +delete(id)
190
+ +close()
191
+ }
192
+
193
+ class Router {
194
+ -routes: Route[]
195
+ +register(route)
196
+ +dispatch(req) Promise~Response~
197
+ }
198
+
199
+ ApplicationContext --> ResponsesContext : 创建
200
+ ApplicationContext --> ModelResolver
201
+ ApplicationContext --> Registrar
202
+ ApplicationContext --> Adapter
203
+ ApplicationContext --> ResponseSessionStore
204
+ ResponsesContext --> Provider : 使用
205
+ Provider --> ProviderMapper
206
+ Provider --> ChatClient
207
+ ProviderMapper --> RequestMapper
208
+ ProviderMapper --> ResponseMapper
209
+ ProviderMapper --> StreamMapper
210
+ Adapter <|.. DefaultAdapter
211
+ DefaultAdapter --> ProviderMapper : 调用
212
+ DefaultAdapter --> ChatClient : 调用
213
+ DefaultAdapter --> ResponseSessionStore : 保存
214
+ Router --> ResponsesContext : 分发至
215
+ ```
216
+
217
+ ## 流式管道
218
+
219
+ ```mermaid
220
+ flowchart LR
221
+ subgraph upstream["上游提供商"]
222
+ SSE["SSE 数据块<br/>(JsonServerSentEvent)"]
223
+ end
224
+
225
+ subgraph godex["Godex 流式管道"]
226
+ T1["ProviderEventTo<br/>ResponseTransformer"]
227
+ T2["ResponseSession<br/>PersistenceTransformer"]
228
+ T3["ResponseSse<br/>EncodeTransformer"]
229
+ end
230
+
231
+ subgraph client["客户端"]
232
+ BYTES["SSE 字节流<br/>(text/event-stream)"]
233
+ end
234
+
235
+ SSE -->|"pipeThrough(TransformStream)"| T1
236
+ T1 -->|"逐事件 map()<br/>SSE 数据块 → ResponseStreamEvent[]"| T2
237
+ T2 -->|"累积 StreamState<br/>拦截终止事件<br/>buildResponseObject()<br/>保存会话"| T3
238
+ T3 -->|"序列化为 SSE 传输格式<br/>event: xxx\ndata: {...}\n\n"| BYTES
239
+
240
+ style upstream fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
241
+ style godex fill:#0f3460,stroke:#16213e,color:#e0e0e0
242
+ style client fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
243
+ ```
244
+
245
+ ### Transformer 职责
246
+
247
+ | 阶段 | Transformer | 输入 | 输出 | 副作用 |
248
+ |------|------------|------|------|--------|
249
+ | 1 | `ProviderEventToResponseTransformer` | `JsonServerSentEvent<TChunk>` | `ResponseStreamEvent` | 逐事件调用 `StreamMapper.map()` |
250
+ | 2 | `ResponseSessionPersistenceTransformer` | `ResponseStreamEvent` | `ResponseStreamEvent` | 累积 `StreamState`,终止事件时调用 `buildResponseObject()` 并保存会话(`store=false` 时跳过) |
251
+ | 3 | `ResponseSseEncodeTransformer` | `ResponseStreamEvent` | `Uint8Array`(SSE 传输格式) | 序列化为 `event:` / `data:` 行 |
252
+
253
+ ## 错误体系
254
+
255
+ ```mermaid
256
+ classDiagram
257
+ direction TB
258
+
259
+ class GodexError {
260
+ +name: string
261
+ +code: string
262
+ +status: number
263
+ +context: object
264
+ +toLogEntry() object
265
+ }
266
+
267
+ class ServerError {
268
+ +status: 400-499
269
+ +context: object
270
+ }
271
+
272
+ class AdapterError {
273
+ +status: 400-499
274
+ +context: 不支持的参数 / 工具 / 输入项
275
+ }
276
+
277
+ class ProviderError {
278
+ +status: 502
279
+ +context: 上游状态码 / 响应体 / 响应头
280
+ }
281
+
282
+ class SessionError {
283
+ +status: 400-409
284
+ +context: 链元数据
285
+ }
286
+
287
+ GodexError <|-- ServerError
288
+ GodexError <|-- AdapterError
289
+ GodexError <|-- ProviderError
290
+ GodexError <|-- SessionError
291
+
292
+ note for GodexError "基础错误,支持结构化日志。<br/>所有错误携带领域编码(如 server.request.invalid_json)。"
293
+ note for ProviderError "包装上游 HTTP 失败:<br/>速率限制、超时、5xx。"
294
+ note for SessionError "链式解析失败:<br/>未找到、循环、深度超限。"
295
+ ```
296
+
297
+ ## 项目结构
298
+
299
+ ```
300
+ src/
301
+ ├── cli/ Commander CLI(serve、配置检查、初始化)
302
+ ├── config/ godex.yaml 配置模式、环境变量插值、默认值
303
+ ├── context/ ApplicationContext(DI 容器)、ResponsesContext(每请求)
304
+ ├── adapter/ Adapter 接口、DefaultAdapter、流式 Transformer
305
+ │ ├── mapper/ RequestMapper / ResponseMapper / StreamMapper 契约
306
+ │ └── transformers/ ProviderEvent → Response → SSE 编码管道
307
+ ├── providers/ Provider 注册表 + 内置工厂
308
+ │ └── zhipu/ 参考提供商实现:映射器、聊天客户端、工具、消息
309
+ ├── resolver/ ModelResolver(模型选择器 → 提供商 + 模型)
310
+ ├── server/ Bun HTTP 服务器、Router、路由(/v1/responses、/health、/v1/models)
311
+ ├── session/ ResponseSessionStore(内存 + SQLite)、链式解析
312
+ ├── error/ GodexError 错误体系及领域编码
313
+ ├── protocol/openai/ OpenAI 兼容类型定义
314
+ ├── logger/ 结构化 JSON 日志
315
+ └── e2e/ 模拟上游的端到端测试
316
+ ```
317
+
318
+ ## 快速开始
319
+
320
+ ```bash
321
+ # 安装依赖
322
+ bun install
323
+
324
+ # 构建独立二进制文件(当前平台)
325
+ bun run build
326
+
327
+ # 交互式创建配置
328
+ bun run start -- init
329
+
330
+ # 启动服务器(默认端口 5678)
331
+ bun run dev
332
+
333
+ # 或直接运行编译后的二进制文件
334
+ ./platforms/darwin-arm64/bin/godex serve
335
+ ```
336
+
337
+ ### godex.yaml
338
+
339
+ ```yaml
340
+ server:
341
+ port: 5678
342
+
343
+ default_provider: zhipu
344
+
345
+ providers:
346
+ zhipu:
347
+ api_key: ${ZHIPU_API_KEY}
348
+ base_url: https://open.bigmodel.cn/api/paas/v4
349
+ models:
350
+ "gpt-4o": glm-4.7 # 模型名称映射
351
+ "*": glm-5.1 # 兜底映射
352
+
353
+ session:
354
+ backend: sqlite # 或 "memory"
355
+ sqlite:
356
+ path: ./data/sessions.db
357
+
358
+ logging:
359
+ level: info # trace | debug | info | warn | error
360
+ ```
361
+
362
+ ### 添加提供商
363
+
364
+ 在 `src/providers/<name>/` 中实现以下接口:
365
+
366
+ | 接口 | 用途 |
367
+ |------|------|
368
+ | `Provider<TReq, TRes, TChunk>` | 组合 mapper + chatClient + capabilities |
369
+ | `ProviderMapper<TReq, TRes, TChunk>` | request / response / stream 映射函数 |
370
+ | `ChatClient<TReq, TRes, TChunk>` | `chat()` 和 `streamChat()` HTTP 调用 |
371
+
372
+ 在 `src/providers/builtin.ts` 中注册工厂:
373
+
374
+ ```ts
375
+ registrar.registerFactory("myprovider", (config) =>
376
+ createMyProvider(config) as Provider<unknown, unknown, unknown>
377
+ );
378
+ ```
379
+
380
+ ## 使用
381
+
382
+ ```bash
383
+ # 安装 — 运行时无需 Bun
384
+ npm install -g @ahoo-wang/godex
385
+
386
+ # 交互式创建配置
387
+ godex init
388
+
389
+ # 启动网关
390
+ godex serve
391
+ ```
392
+
393
+ Godex 以**独立原生二进制文件**发布,零运行时依赖。npm 的 `postinstall` 脚本自动为您的平台选择正确的二进制文件。唯一前置条件是 Node.js >= 18(仅在 `npm install` 期间需要)。
394
+
395
+ Godex 在 `http://localhost:5678` 暴露**与 OpenAI 兼容的 Responses API**(端口可配置)。将任何使用 OpenAI 协议的工具指向此端点即可:
396
+
397
+ ### 搭配 Codex CLI
398
+
399
+ ```bash
400
+ export OPENAI_BASE_URL=http://localhost:5678/v1
401
+ export OPENAI_API_KEY=any-value # Godex 不验证此值,但必须设置
402
+ codex
403
+ ```
404
+
405
+ ### 搭配 OpenAI SDK
406
+
407
+ ```ts
408
+ import OpenAI from "openai";
409
+
410
+ const client = new OpenAI({
411
+ baseURL: "http://localhost:5678/v1",
412
+ apiKey: "any-value", // 透传,不验证
413
+ });
414
+
415
+ const response = await client.responses.create({
416
+ model: "gpt-4o", // 通过 godex.yaml 的 models 表映射为 glm-4.7
417
+ input: "Hello!",
418
+ });
419
+ ```
420
+
421
+ ### 模型选择
422
+
423
+ ```
424
+ model: "gpt-4o" → 通过 default_provider 的模型映射解析
425
+ model: "zhipu/glm-4.7" → 显式指定 provider/model 选择器
426
+ model: "openai/gpt-4o" → 路由到已配置的 openai 提供商
427
+ ```
428
+
429
+ `godex.yaml` 中的 `models` 映射表可将标准模型名称转换为提供商原生名称 — 客户端无需修改代码。
430
+
431
+ ### 健康检查
432
+
433
+ ```bash
434
+ curl http://localhost:5678/health
435
+ # {"status":"ok","providers":["zhipu"],"unsupported_providers":[]}
436
+ ```
437
+
438
+ ## 发布
439
+
440
+ 主包 `@ahoo-wang/godex` 是一个轻量外壳。原生二进制文件以平台特定的可选依赖发布:
441
+
442
+ ```
443
+ @ahoo-wang/godex(包装包,0 运行时依赖)
444
+ ├── engines: { node: ">=18.0.0" } ← 仅用于 postinstall
445
+ ├── postinstall: scripts/install.cjs ← 检测平台,链接二进制文件
446
+ └── optionalDependencies:
447
+ ├── @ahoo-wang/godex-darwin-arm64 ← macOS Apple Silicon
448
+ ├── @ahoo-wang/godex-darwin-x64 ← macOS Intel
449
+ ├── @ahoo-wang/godex-linux-x64 ← Linux x86_64
450
+ ├── @ahoo-wang/godex-linux-arm64 ← Linux ARM64
451
+ ├── @ahoo-wang/godex-win32-x64 ← Windows x86_64
452
+ └── @ahoo-wang/godex-win32-arm64 ← Windows ARM64
453
+
454
+ # 发布流程:
455
+ # 1. 将 GitHub 仓库设为公开,配置 NPM_TOKEN,然后推送发布提交。
456
+ # 2. 创建标签为 vX.Y.Z 的 GitHub Release。
457
+ # 3. Release 工作流构建所有平台二进制文件。
458
+ # 4. Release 工作流上传二进制压缩包和 SHA256SUMS 到 Release Assets。
459
+ # 5. Release 工作流先发布平台包,再发布 @ahoo-wang/godex。
460
+ ```
461
+
462
+ ## 命令
463
+
464
+ ```bash
465
+ bun run dev # 热重载开发服务器,端口 13145
466
+ bun run build # 为当前平台编译原生二进制
467
+ bun run compile:all # 本地交叉编译全部 6 个平台
468
+ bun run test # 单元 + 集成测试
469
+ bun run test:e2e # 模拟上游的端到端测试
470
+ bun run typecheck # tsc --noEmit
471
+ bun run lint # Biome 检查
472
+ bun run ci # 完整 CI 流水线
473
+ ```
package/bin/godex CHANGED
@@ -6,8 +6,19 @@ import { spawnSync } from "node:child_process";
6
6
  import { accessSync, constants } from "node:fs";
7
7
  import { join, dirname } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
+ import { createRequire } from "node:module";
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const require = createRequire(import.meta.url);
13
+
14
+ function detectPlatform() {
15
+ const plat = process.platform;
16
+ const arch = process.arch;
17
+ if (plat === "win32") return `win32-${arch}`;
18
+ if (plat === "darwin") return `darwin-${arch}`;
19
+ if (plat === "linux") return `linux-${arch}`;
20
+ return null;
21
+ }
11
22
 
12
23
  function findBinary() {
13
24
  // Binary is copied here by postinstall
@@ -16,8 +27,24 @@ function findBinary() {
16
27
  accessSync(nextToWrapper, constants.X_OK);
17
28
  return nextToWrapper;
18
29
  } catch {
19
- // not found, try dev fallback
30
+ // not found
20
31
  }
32
+
33
+ // Try resolving from the optional dependency (works with npm, yarn, and pnpm)
34
+ const platform = detectPlatform();
35
+ if (platform) {
36
+ const binaryName = platform.startsWith("win32") ? "godex.exe" : "godex";
37
+ const pkgName = `@ahoo-wang/godex-${platform}`;
38
+ try {
39
+ const pkgRoot = dirname(require.resolve(`${pkgName}/package.json`));
40
+ const binary = join(pkgRoot, "bin", binaryName);
41
+ accessSync(binary, constants.X_OK);
42
+ return binary;
43
+ } catch {
44
+ // platform package not installed or binary missing
45
+ }
46
+ }
47
+
21
48
  // Dev fallback — when running from source without postinstall
22
49
  try {
23
50
  const devPath = join(__dirname, "..", "dist", "index.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahoo-wang/godex",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "description": "Make every model a Codex engine through an OpenAI-compatible Responses API gateway",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,18 +38,19 @@
38
38
  "test:coverage": "bun test --path-ignore-patterns 'src/e2e/**' --coverage",
39
39
  "check": "bun run typecheck && bun run lint && bun run test",
40
40
  "ci": "bun run typecheck && biome ci src && bun run test && bun run test:e2e",
41
+ "version": "bun run scripts/version.ts",
41
42
  "postinstall": "node scripts/install.cjs"
42
43
  },
43
44
  "engines": {
44
45
  "node": ">=18.0.0"
45
46
  },
46
47
  "optionalDependencies": {
47
- "@ahoo-wang/godex-darwin-arm64": "0.0.1",
48
- "@ahoo-wang/godex-darwin-x64": "0.0.1",
49
- "@ahoo-wang/godex-linux-x64": "0.0.1",
50
- "@ahoo-wang/godex-linux-arm64": "0.0.1",
51
- "@ahoo-wang/godex-win32-x64": "0.0.1",
52
- "@ahoo-wang/godex-win32-arm64": "0.0.1"
48
+ "@ahoo-wang/godex-darwin-arm64": "0.0.4",
49
+ "@ahoo-wang/godex-darwin-x64": "0.0.4",
50
+ "@ahoo-wang/godex-linux-x64": "0.0.4",
51
+ "@ahoo-wang/godex-linux-arm64": "0.0.4",
52
+ "@ahoo-wang/godex-win32-x64": "0.0.4",
53
+ "@ahoo-wang/godex-win32-arm64": "0.0.4"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@biomejs/biome": "^2.4.15",
@@ -62,6 +63,6 @@
62
63
  "commander": "^14.0.3",
63
64
  "js-yaml": "^4.1.1",
64
65
  "nanoid": "^5.1.11",
65
- "typescript": "^5"
66
+ "typescript": "^6"
66
67
  }
67
68
  }
@@ -21,7 +21,7 @@ function detectPlatform() {
21
21
  function findBinary(pkgName, platform) {
22
22
  const binaryName = platform.startsWith("win32") ? "godex.exe" : "godex";
23
23
 
24
- // 1. Production: optional dep installed by npm
24
+ // 1. Production: optional dep installed by npm/pnpm/yarn
25
25
  try {
26
26
  const pkgRoot = path.dirname(
27
27
  require.resolve(pkgName + "/package.json", {