Repository navigation
PX-651: 稳定 fnm Codex shim 路径并补充 connect readiness #6415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
andrew05060414
wants to merge
5
commits into
lidge-jun:dev
from
andrew05060414:agent/engineer-temperance/px-651-codex-shim
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2ce365d
fix(codex): stabilize fnm shim targets and connect readiness
andrew05060414 ddacc3d
fix(codex): preserve fnm entry and detect active shim
andrew05060414 0c598b8
fix(codex): let the injected path flavour govern the PATH scan and do…
andrew05060414 36e5871
fix(cli): tell operators a shadowed healthy shim needs PATH order, no…
andrew05060414 2edb1b3
Merge dev into px-651-codex-shim
andrew05060414 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs"; | ||
| import { delimiter, posix, win32 } from "node:path"; | ||
| import { isWslRuntime, wslAutomountRoot } from "./home"; | ||
| import { SHIM_MARKER } from "./shim-templates"; | ||
|
|
||
| /** | ||
| * A PATH entry that reaches Windows through WSL drive interop | ||
| * (`<automount-root>/<drive>/...`; root defaults to /mnt, configurable via | ||
| * /etc/wsl.conf [automount] root). | ||
| */ | ||
| export function isWindowsInteropDir(dir: string, automountRoot = "/mnt"): boolean { | ||
| const root = automountRoot.replace(/\/+$/, ""); | ||
| const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| return new RegExp(`^${escaped}/[a-z](/|$)`, "i").test(dir); | ||
| } | ||
|
|
||
| export type CodexPathScanDeps = { | ||
| pathValue?: string; | ||
| wsl?: boolean; | ||
| /** Treat PATH entries as POSIX paths (WSL context). Defaults to wsl || non-win32. */ | ||
| posixPaths?: boolean; | ||
| automountRoot?: string; | ||
| exists?: (path: string) => boolean; | ||
| isShimFile?: (path: string) => boolean; | ||
| isDirectory?: (path: string) => boolean; | ||
| realpath?: (path: string) => string; | ||
| }; | ||
|
|
||
| export type CodexPathCandidate = { | ||
| path: string; | ||
| isShim: boolean; | ||
| }; | ||
|
|
||
| export function realIsDirectory(path: string): boolean { | ||
| try { | ||
| return lstatSync(path).isDirectory(); | ||
| } catch { | ||
| return true; // unreadable -> treat as unusable | ||
| } | ||
| } | ||
|
|
||
| function isShimFile(path: string): boolean { | ||
| try { | ||
| return readFileSync(path, "utf8").includes(SHIM_MARKER); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| function commandNames(name: string): string[] { | ||
| if (process.platform !== "win32") return [name]; | ||
| const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD;.PS1").split(";").filter(Boolean); | ||
| return [name, ...exts.flatMap(ext => [`${name}${ext.toLowerCase()}`, `${name}${ext.toUpperCase()}`])]; | ||
| } | ||
|
|
||
| /** The shell-local bin directory that fnm creates for one interactive shell. */ | ||
| export function isFnmMultishellPath(path: string, posixPaths: boolean): boolean { | ||
| const normalized = (posixPaths | ||
| ? posix.normalize(path) | ||
| : win32.normalize(path).replace(/\\/g, "/")).toLowerCase(); | ||
| return /(?:^|\/)fnm_multishells?(?:\/|$)/.test(normalized); | ||
| } | ||
|
|
||
| /** Find the command the shell would actually use, including an OpenCodex shim. */ | ||
| export function findFirstCodexOnPath(deps: CodexPathScanDeps = {}): CodexPathCandidate | null { | ||
| const exists = deps.exists ?? existsSync; | ||
| const shimFile = deps.isShimFile ?? isShimFile; | ||
| const isDir = deps.isDirectory ?? realIsDirectory; | ||
| const wsl = deps.wsl ?? (process.platform === "linux" && isWslRuntime()); | ||
| const usePosix = deps.posixPaths ?? (wsl || process.platform !== "win32"); | ||
| const joinPath = usePosix ? posix.join : win32.join; | ||
| const pathSep = usePosix ? ":" : delimiter; | ||
| const automountRoot = deps.automountRoot ?? (wsl ? wslAutomountRoot() : "/mnt"); | ||
| const interopNames = ["codex", "codex.exe", "codex.cmd", "codex.ps1"]; | ||
|
|
||
| for (const dir of (deps.pathValue ?? process.env.PATH ?? "").split(pathSep).filter(Boolean)) { | ||
| if (wsl && isWindowsInteropDir(dir, automountRoot)) continue; | ||
| const names = isWindowsInteropDir(dir, automountRoot) ? interopNames : commandNames("codex"); | ||
| for (const name of names) { | ||
| const path = joinPath(dir, name); | ||
| if (!exists(path) || isDir(path)) continue; | ||
| return { path, isShim: shimFile(path) }; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Resolve a Codex command discovered through fnm's temporary PATH entry. | ||
| * | ||
| * A temporary path is safe to use only when its physical target is outside the | ||
| * temporary tree. Returning null is deliberate: callers must not wrap the | ||
| * shell-local path or silently choose a lower-priority Codex installation. | ||
| */ | ||
| export function resolveStableFnmCodexPath( | ||
| path: string, | ||
| posixPaths: boolean, | ||
| realpath: ((path: string) => string) | undefined = realpathSync.native, | ||
| ): string | null { | ||
| if (!isFnmMultishellPath(path, posixPaths)) return path; | ||
| try { | ||
| // fnm normally links the temporary directory to the durable installation, | ||
| // while npm may link the command file again into its package internals. | ||
| // Resolve only the directory so the command basename remains the stable | ||
| // installation entry that the installer is allowed to replace. | ||
| const pathTools = posixPaths ? posix : win32; | ||
| const resolvedParent = realpath(pathTools.dirname(path)); | ||
| if (!resolvedParent || isFnmMultishellPath(resolvedParent, posixPaths) || !pathTools.isAbsolute(resolvedParent)) return null; | ||
| return pathTools.join(resolvedParent, pathTools.basename(path)); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.