( wrapper: WrapperLocation | null )
| 234 | } |
| 235 | |
| 236 | async function findExistingInstall( |
| 237 | wrapper: WrapperLocation | null |
| 238 | ): Promise<ExistingInstall | null> { |
| 239 | for (const dir of candidateDirs()) { |
| 240 | const candidate = path.join(dir, CLI_NAME) |
| 241 | try { |
| 242 | const linkTarget = await fsp.readlink(candidate) |
| 243 | const resolved = path.isAbsolute(linkTarget) |
| 244 | ? linkTarget |
| 245 | : path.resolve(dir, linkTarget) |
| 246 | const byUs = wrapper ? sameFile(resolved, wrapper.wrapperPath) : looksLikeOurInstall(resolved) |
| 247 | return { linkPath: candidate, installedByThisApp: byUs } |
| 248 | } catch (err) { |
| 249 | if ((err as NodeJS.ErrnoException).code === 'EINVAL') { |
| 250 | // Real file, not a symlink. Treat as a foreign install we |
| 251 | // refuse to manage. |
| 252 | try { |
| 253 | await fsp.access(candidate) |
| 254 | return { linkPath: candidate, installedByThisApp: false } |
| 255 | } catch { |
| 256 | /* fall through */ |
| 257 | } |
| 258 | } |
| 259 | // ENOENT or permission errors — keep searching. |
| 260 | } |
| 261 | } |
| 262 | return null |
| 263 | } |
| 264 | |
| 265 | function sameFile(a: string, b: string): boolean { |
| 266 | try { |
no test coverage detected