mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-29 04:21:39 -04:00
194 lines
6.8 KiB
TypeScript
194 lines
6.8 KiB
TypeScript
// cspell:ignore ents
|
|
import fs from 'fs'
|
|
import { readV8FileStrictSync } from '@pnpm/fs.v8-file'
|
|
import { getIndexFilePathInCafs, getFilePathByModeInCafs, type PackageFilesIndex } from '@pnpm/store.cafs'
|
|
import { type LockfileObject, readWantedLockfile, type PackageSnapshot, type TarballResolution } from '@pnpm/lockfile.fs'
|
|
import {
|
|
nameVerFromPkgSnapshot,
|
|
} from '@pnpm/lockfile.utils'
|
|
import { type DepPath } from '@pnpm/types'
|
|
import schemas from 'hyperdrive-schemas'
|
|
import Fuse from 'fuse-native'
|
|
import * as cafsExplorer from './cafsExplorer.js'
|
|
import { makeVirtualNodeModules } from './makeVirtualNodeModules.js'
|
|
|
|
const TIME = new Date()
|
|
const STAT_DEFAULT = {
|
|
mtime: TIME,
|
|
atime: TIME,
|
|
ctime: TIME,
|
|
nlink: 1,
|
|
uid: process.getuid ? process.getuid() : 0,
|
|
gid: process.getgid ? process.getgid() : 0,
|
|
}
|
|
|
|
export interface FuseHandlers {
|
|
open: (p: string, flags: string | number, cb: (exitCode: number, fd?: number) => void) => void
|
|
release: (p: string, fd: number, cb: (exitCode: number) => void) => void
|
|
read: (p: string, fd: number, buffer: Buffer, length: number, position: number, cb: (readBytes: number) => void) => void
|
|
readlink: (p: string, cb: (returnCode: number, target?: string) => void) => void
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
getattr: (p: string, cb: (returnCode: number, files?: any) => void) => void
|
|
readdir: (p: string, cb: (returnCode: number, files?: string[]) => void) => void
|
|
}
|
|
|
|
export async function createFuseHandlers (lockfileDir: string, storeDir: string): Promise<FuseHandlers> {
|
|
const lockfile = await readWantedLockfile(lockfileDir, { ignoreIncompatible: true })
|
|
if (lockfile == null) throw new Error('Cannot generate a .pnp.cjs without a lockfile')
|
|
return createFuseHandlersFromLockfile(lockfile, storeDir)
|
|
}
|
|
|
|
export function createFuseHandlersFromLockfile (lockfile: LockfileObject, storeDir: string): FuseHandlers {
|
|
const pkgSnapshotCache = new Map<string, { name: string, version: string, pkgSnapshot: PackageSnapshot, index: PackageFilesIndex }>()
|
|
const virtualNodeModules = makeVirtualNodeModules(lockfile)
|
|
return {
|
|
open (p: string, flags: string | number, cb: (exitCode: number, fd?: number) => void) {
|
|
const dirEnt = getDirEnt(p)
|
|
if (dirEnt?.entryType !== 'index') {
|
|
cb(-1)
|
|
return
|
|
}
|
|
const fileInfo = dirEnt.index.files[dirEnt.subPath]
|
|
if (!fileInfo) {
|
|
cb(-1)
|
|
return
|
|
}
|
|
const filePathInStore = getFilePathByModeInCafs(storeDir, fileInfo.integrity, fileInfo.mode)
|
|
fs.open(filePathInStore, flags, (err, fd) => {
|
|
if (err != null) {
|
|
cb(-1)
|
|
return
|
|
}
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, fd)
|
|
})
|
|
},
|
|
release (p: string, fd: number, cb: (exitCode: number) => void) {
|
|
fs.close(fd, (err) => {
|
|
cb((err != null) ? -1 : 0)
|
|
})
|
|
},
|
|
read (p: string, fd: number, buffer: Buffer, length: number, position: number, cb: (readBytes: number) => void) {
|
|
fs.read(fd, buffer, 0, length, position, (err, bytesRead) => {
|
|
if (err != null) {
|
|
cb(-1)
|
|
return
|
|
}
|
|
cb(bytesRead)
|
|
})
|
|
},
|
|
readlink (p: string, cb: (returnCode: number, target?: string) => void) {
|
|
const dirEnt = getDirEnt(p)
|
|
if (dirEnt?.entryType !== 'symlink') {
|
|
cb(Fuse.ENOENT)
|
|
return
|
|
}
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, dirEnt.target)
|
|
},
|
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
getattr (p: string, cb: (returnCode: number, files?: any) => void) {
|
|
const dirEnt = getDirEnt(p)
|
|
if (dirEnt == null) {
|
|
cb(Fuse.ENOENT)
|
|
return
|
|
}
|
|
if (dirEnt.entryType === 'directory' || dirEnt.entryType === 'index' && !dirEnt.subPath) {
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, schemas.Stat.directory({
|
|
...STAT_DEFAULT,
|
|
size: 1,
|
|
}))
|
|
return
|
|
}
|
|
if (dirEnt.entryType === 'symlink') {
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, schemas.Stat.symlink({
|
|
...STAT_DEFAULT,
|
|
size: 1,
|
|
}))
|
|
return
|
|
}
|
|
if (dirEnt.entryType === 'index') {
|
|
switch (cafsExplorer.dirEntityType(dirEnt.index, dirEnt.subPath)) {
|
|
case 'file': {
|
|
const { size, mode } = dirEnt.index.files[dirEnt.subPath]
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, schemas.Stat.file({
|
|
...STAT_DEFAULT,
|
|
mode,
|
|
size,
|
|
}))
|
|
return
|
|
}
|
|
case 'directory':
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, schemas.Stat.directory({
|
|
...STAT_DEFAULT,
|
|
size: 1,
|
|
}))
|
|
return
|
|
default:
|
|
cb(Fuse.ENOENT)
|
|
return
|
|
}
|
|
}
|
|
cb(Fuse.ENOENT)
|
|
},
|
|
readdir,
|
|
}
|
|
function readdir (p: string, cb: (returnCode: number, files?: string[]) => void) {
|
|
const dirEnt = getDirEnt(p)
|
|
if (dirEnt?.entryType === 'index') {
|
|
const dirEnts = cafsExplorer.readdir(dirEnt.index, dirEnt.subPath)
|
|
if (dirEnts.length === 0) {
|
|
cb(Fuse.ENOENT)
|
|
return
|
|
}
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, dirEnts)
|
|
return
|
|
}
|
|
if ((dirEnt == null) || dirEnt.entryType !== 'directory') {
|
|
cb(Fuse.ENOENT)
|
|
return
|
|
}
|
|
// eslint-disable-next-line n/no-callback-literal
|
|
cb(0, Object.keys(dirEnt.entries))
|
|
}
|
|
function getDirEnt (p: string) {
|
|
let currentDirEntry = virtualNodeModules
|
|
const parts = p === '/' ? [] : p.split('/')
|
|
parts.shift()
|
|
while ((parts.length > 0) && currentDirEntry && currentDirEntry.entryType === 'directory') {
|
|
currentDirEntry = currentDirEntry.entries[parts.shift()!]
|
|
}
|
|
if (currentDirEntry?.entryType === 'index') {
|
|
const pkg = getPkgInfo(currentDirEntry.depPath, storeDir)
|
|
if (pkg == null) {
|
|
return null
|
|
}
|
|
return {
|
|
...currentDirEntry,
|
|
index: pkg.index,
|
|
subPath: parts.join('/'),
|
|
}
|
|
}
|
|
return currentDirEntry
|
|
}
|
|
function getPkgInfo (depPath: string, storeDir: string) {
|
|
if (!pkgSnapshotCache.has(depPath)) {
|
|
const pkgSnapshot = lockfile.packages?.[depPath as DepPath]
|
|
if (pkgSnapshot == null) return undefined
|
|
const nameVer = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
|
|
const indexPath = getIndexFilePathInCafs(storeDir, (pkgSnapshot.resolution as TarballResolution).integrity!, `${nameVer.name}@${nameVer.version}`)
|
|
pkgSnapshotCache.set(depPath, {
|
|
...nameVer,
|
|
pkgSnapshot,
|
|
index: readV8FileStrictSync<PackageFilesIndex>(indexPath), // TODO: maybe make it async?
|
|
})
|
|
}
|
|
return pkgSnapshotCache.get(depPath)
|
|
}
|
|
}
|