isexe@3.1.1源码阅读
发布日期 2023 年 8 月 3 日
isexe 是跨平台检查文件是否为「可执行文件」的专用工具包,核心解决「Windows 和 Unix 系统判断可执行文件的规则完全不同」的问题。
unix系统根据文件权限判断;window系统根据文件扩展名判断。
入口文件 index.js
isexe-3.1.1/src/index.ts
import * as posix from './posix.js' // 导入 POSIX 系统(Linux/macOS 等)的实现
import * as win32 from './win32.js' // 导入 Windows 系统的实现
export * from './options.js' // 导出配置选项类型(如 IsexeOptions)
export { win32, posix } // 允许直接访问特定平台的实现
const platform = process.env._ISEXE_TEST_PLATFORM_ || process.platform
const impl = platform === 'win32' ? win32 : posix
/**
* Determine whether a path is executable on the current platform.
*/
export const isexe = impl.isexe
/**
* Synchronously determine whether a path is executable on the
* current platform.
*/
export const sync = impl.sync
posix.isexe(异步)
isexe-3.1.1/src/posix.ts
const isexe = async (
path: string, // 要检查的文件路径(比如 "/usr/bin/node" 或 "C:\\node.exe")
options: IsexeOptions = {} // 配置项,默认空对象
): Promise<boolean> => {
const { ignoreErrors = false } = options
try {
// await stat(path):获取文件状态
// checkStat(statResult, options):判断是否可执行
return checkStat(await stat(path), options)
} catch (e) {
// 把错误转为 Node.js 标准错误类型(带错误码)
const er = e as NodeJS.ErrnoException
if (ignoreErrors || er.code === 'EACCES') return false
throw er // 非预期错误,向上抛出
}
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'
checkStat
const checkStat = (stat: Stats, options: IsexeOptions) =>
stat.isFile() && checkMode(stat, options)
checkMode
const checkMode = (
// 文件的 Stats 对象(通常由 fs.stat 或 fs.lstat 获取)
// 包含文件的权限位(mode)、所有者 ID(uid)、所属组 ID(gid)等元数据。
stat: Stats,
// 配置对象,允许自定义用户 ID(uid)、组 ID(gid)、用户所属组列表(groups),默认使用当前进程的用户信息。
options: IsexeOptions
) => {
// 1、获取用户与组信息
// 当前用户的 ID(优先使用 options.uid,否则调用 process.getuid() 获取当前进程的用户 ID)。
const myUid = options.uid ?? process.getuid?.()
// 当前用户所属的组 ID 列表(优先使用 options.groups,否则调用 process.getgroups() 获取)。
const myGroups = options.groups ?? process.getgroups?.() ?? []
// 当前用户的主组 ID(优先使用 options.gid,否则调用 process.getgid(),或从 myGroups 取第一个组 ID)。
const myGid = options.gid ?? process.getgid?.() ?? myGroups[0]
// 若无法获取 myUid 或 myGid,抛出错误(权限判断依赖这些信息)
if (myUid === undefined || myGid === undefined) {
throw new Error('cannot get uid or gid')
}
// 2、构建用户所属组集合
const groups = new Set([myGid, ...myGroups])
// 3、解析文件权限位与归属信息
const mod = stat.mode // 文件的权限位(整数,如 0o755 表示 rwxr-xr-x)
const uid = stat.uid // 文件所有者的用户 ID
const gid = stat.gid // 文件所属组的组 ID
// 4、定义权限位掩码
// 八进制 100 → 十进制 64 → 对应所有者的执行权限位(x)
const u = parseInt('100', 8)
// 八进制 010 → 十进制 8 → 对应所属组的执行权限位(x)
const g = parseInt('010', 8)
// 八进制 001 → 十进制 1 → 对应其他用户的执行权限位(x)
const o = parseInt('001', 8)
// 所有者和所属组的执行权限位掩码(64 | 8 = 72)
const ug = u | g
// 5、权限判断逻辑
return !!(
mod & o || // 1. 其他用户有执行权限
(mod & g && groups.has(gid)) || // 2. 所属组有执行权限,且当前用户属于该组
(mod & u && uid === myUid) || // 3. 所有者有执行权限,且当前用户是所有者
(mod & ug && myUid === 0) // 4. 所有者或组有执行权限,且当前用户是 root(UID=0)
)
}
mod (权限位) :Unix 系统中用 9 位二进制表示文件权限(分为所有者、所属组、其他用户三类,每类 3 位,分别控制读 r、写 w、执行 x 权限)。例如 0o755 对应二进制 111 101 101,表示:
- 所有者(
u):可读、可写、可执行(rwx)。 - 所属组(
g):可读、可执行(r-x)。 - 其他用户(
o):可读、可执行(r-x)。
posix.sync (同步)
isexe-3.1.1/src/posix.ts
const sync = (
path: string,
options: IsexeOptions = {}
): boolean => {
const { ignoreErrors = false } = options
try {
return checkStat(statSync(path), options)
} catch (e) {
const er = e as NodeJS.ErrnoException
if (ignoreErrors || er.code === 'EACCES') return false
throw er
}
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'
win32.isexe (异步)
isexe-3.1.1/src/win32.ts
const isexe = async (
path: string,
options: IsexeOptions = {}
): Promise<boolean> => {
const { ignoreErrors = false } = options
try {
return checkStat(await stat(path), path, options)
} catch (e) {
const er = e as NodeJS.ErrnoException
if (ignoreErrors || er.code === 'EACCES') return false
throw er
}
}
import { Stats, statSync } from 'fs'
import { stat } from 'fs/promises'
checkStat
const checkStat = (stat: Stats, path: string, options: IsexeOptions) =>
stat.isFile() && checkPathExt(path, options)
checkPathExt
isexe-3.1.1/src/win32.ts
const checkPathExt = (path: string, options: IsexeOptions) => {
// 获取可执行扩展名列表
const { pathExt = process.env.PATHEXT || '' } = options
const peSplit = pathExt.split(';')
// 特殊情况处理:空扩展名
// 空扩展名通常表示 “任何文件都视为可执行”,这是一种特殊配置
if (peSplit.indexOf('') !== -1) {
return true
}
// 检查文件扩展名是否匹配
for (let i = 0; i < peSplit.length; i++) {
// 转小写:避免大小写问题(比如.EXE和.exe视为同一个)
const p = peSplit[i].toLowerCase()
// 截取文件路径的最后N个字符(N是当前扩展名p的长度),也转小写
const ext = path.substring(path.length - p.length).toLowerCase()
// 匹配条件:扩展名非空 + 文件扩展名和列表中的扩展名完全一致
if (p && ext === p) {
return true
}
}
return false
}
win32.sync(同步)
isexe-3.1.1/src/win32.ts
const sync = (
path: string,
options: IsexeOptions = {}
): boolean => {
const { ignoreErrors = false } = options
try {
return checkStat(statSync(path), path, options)
} catch (e) {
const er = e as NodeJS.ErrnoException
if (ignoreErrors || er.code === 'EACCES') return false
throw er
}
}