Repository navigation
fix(lsp): check a project-local pyright-langserver before PATH and bunx #148
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
Changes from all commits
0417c14
065b64e
0486614
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
|
|
||
| import type { ChildProcessWithoutNullStreams } from 'node:child_process' | ||
| import type { RootFunction } from './types' | ||
| import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync } from 'node:fs' | ||
| import fs from 'node:fs/promises' | ||
| import path from 'node:path' | ||
| import process from 'node:process' | ||
|
|
@@ -95,6 +96,88 @@ export async function runsVersion(bin: string, root: string): Promise<boolean> { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * On Windows an npm bin is a .cmd shim, which spawn can only run through a shell, | ||
| * so the path is quoted for it. | ||
| */ | ||
| export function prepareSpawnCommand( | ||
| command: string, | ||
| platform: NodeJS.Platform = process.platform, | ||
| ): { command: string, shell: boolean } { | ||
| const win32 = platform === 'win32' | ||
| return { command: win32 ? `"${command}"` : command, shell: win32 } | ||
| } | ||
|
|
||
| /** Resolve a package-owned bin declared by the package and exposed by its shared shim. */ | ||
| export function resolvePackageBin(packageRoot: string, name: string): string | undefined { | ||
| try { | ||
| const pkg = JSON.parse( | ||
| readFileSync(path.join(packageRoot, 'package.json'), 'utf8'), | ||
| ) as { bin?: string | Record<string, string> } | ||
| const relativeTarget = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.[name] | ||
| if (!relativeTarget) { return undefined } | ||
|
|
||
| const target = path.resolve(packageRoot, relativeTarget) | ||
| if (!existsSync(target)) { return undefined } | ||
| const ext = process.platform === 'win32' ? '.cmd' : '' | ||
| const packageDir = path.dirname(packageRoot) | ||
| const nodeModules = path.basename(packageDir).startsWith('@') | ||
| ? path.dirname(packageDir) | ||
| : packageDir | ||
| const shim = path.join(nodeModules, '.bin', `${name}${ext}`) | ||
| if (!existsSync(shim)) { return undefined } | ||
|
|
||
| // npm-style symlinks and package-manager-generated wrapper scripts are both | ||
| // valid shims. For wrappers, require a reference to the package's declared | ||
| // target so a stale or unrelated shared shim is never selected. | ||
| const stat = lstatSync(shim) | ||
| if (stat.isSymbolicLink()) { | ||
| const resolvedShim = path.resolve(path.dirname(shim), readlinkSync(shim)) | ||
| return existsSync(resolvedShim) | ||
| && realpathSync(resolvedShim) === realpathSync(target) | ||
| ? shim | ||
| : undefined | ||
| } | ||
| const normalizeShimPath = (value: string): string => { | ||
| const normalized = value.replaceAll('\\', '/') | ||
| return process.platform === 'win32' ? normalized.toLowerCase() : normalized | ||
| } | ||
| const wrapper = normalizeShimPath(readFileSync(shim, 'utf8')) | ||
| // pnpm wrappers reference the package's real path in its .pnpm store, not the node_modules link. | ||
| const binDir = path.dirname(shim) | ||
| const realTarget = realpathSync(target) | ||
| const references = [ | ||
| path.relative(binDir, target), | ||
| target, | ||
| path.relative(realpathSync(binDir), realTarget), | ||
| realTarget, | ||
| ].map(normalizeShimPath) | ||
| return references.some(reference => wrapper.includes(reference)) | ||
| ? shim | ||
| : undefined | ||
| } | ||
| catch { | ||
| return undefined | ||
| } | ||
| } | ||
|
Comment on lines
+112
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [MEDIUM] Cognitive Overload: Extract inline helper to module level Symptom — The const normalizeShimPath = (value: string): string => {
const normalized = value.replaceAll('\\', '/')
return process.platform === 'win32' ? normalized.toLowerCase() : normalized
}
/** Resolve a package-owned bin declared by the package and exposed by its shared shim. */
export function resolvePackageBin(packageRoot: string, name: string): string | undefined {
try {
const pkg = JSON.parse(
readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
) as { bin?: string | Record<string, string> }
const relativeTarget = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.[name]
if (!relativeTarget) { return undefined }
const target = path.resolve(packageRoot, relativeTarget)
if (!existsSync(target)) { return undefined }
const ext = process.platform === 'win32' ? '.cmd' : ''
const packageDir = path.dirname(packageRoot)
const nodeModules = path.basename(packageDir).startsWith('@')
? path.dirname(packageDir)
: packageDir
const shim = path.join(nodeModules, '.bin', `${name}${ext}`)
if (!existsSync(shim)) { return undefined }
// npm-style symlinks and package-manager-generated wrapper scripts are both
// valid shims. For wrappers, require a reference to the package's declared
// target so a stale or unrelated shared shim is never selected.
const stat = lstatSync(shim)
if (stat.isSymbolicLink()) {
const resolvedShim = path.resolve(path.dirname(shim), readlinkSync(shim))
return existsSync(resolvedShim)
&& realpathSync(resolvedShim) === realpathSync(target)
? shim
: undefined
}
const wrapper = normalizeShimPath(readFileSync(shim, 'utf8'))
// pnpm wrappers reference the package's real path in its .pnpm store, not the node_modules link.
const binDir = path.dirname(shim)
const realTarget = realpathSync(target)
const references = [
path.relative(binDir, target),
target,
path.relative(realpathSync(binDir), realTarget),
realTarget,
].map(normalizeShimPath)
return references.some(reference => wrapper.includes(reference))
? shim
: undefined
}
catch {
return undefined
}
}References
|
||
|
|
||
| export function findPackageBin( | ||
| start: string, | ||
| packageName: string, | ||
| name: string, | ||
| isUsable: (packageRoot: string) => boolean = () => true, | ||
| ): string | undefined { | ||
| let dir = path.resolve(start) | ||
| while (true) { | ||
| const packageRoot = path.join(dir, 'node_modules', ...packageName.split('/')) | ||
| const bin = resolvePackageBin(packageRoot, name) | ||
| if (bin && isUsable(packageRoot)) { return bin } | ||
| const parent = path.dirname(dir) | ||
| if (parent === dir) { return undefined } | ||
| dir = parent | ||
| } | ||
| } | ||
|
amondnet marked this conversation as resolved.
|
||
|
|
||
| // ============================================================================= | ||
| // Root Detection Utilities | ||
| // ============================================================================= | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.