@zenfs/core
Advanced tools
Comparing version 1.5.1 to 1.6.0
import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js'; | ||
import { type Device, type DeviceDriver } from './devices.js'; | ||
/** | ||
@@ -88,2 +89,3 @@ * Configuration for a specific mount point | ||
export declare function configureSingle<T extends Backend>(configuration: MountConfiguration<T>): Promise<void>; | ||
export declare function addDevice(driver: DeviceDriver, options?: object): Device; | ||
/** | ||
@@ -90,0 +92,0 @@ * Configures ZenFS with `configuration` |
@@ -7,2 +7,3 @@ import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js'; | ||
import * as fs from './emulation/index.js'; | ||
import { mounts } from './emulation/shared.js'; | ||
import { Errno, ErrnoError } from './error.js'; | ||
@@ -84,2 +85,8 @@ import { FileSystem } from './filesystem.js'; | ||
} | ||
export function addDevice(driver, options) { | ||
const devfs = mounts.get('/dev'); | ||
if (!(devfs instanceof DeviceFS)) | ||
throw new ErrnoError(Errno.ENOTSUP, '/dev does not exist or is not a device file system'); | ||
return devfs._createDevice(driver, options); | ||
} | ||
/** | ||
@@ -86,0 +93,0 @@ * Configures ZenFS with `configuration` |
@@ -11,3 +11,3 @@ import type { FileReadResult } from 'node:fs/promises'; | ||
* @privateRemarks | ||
* UUIDs were considered, however they don't make sense without an easy mechanism for persistance | ||
* UUIDs were considered, however they don't make sense without an easy mechanism for persistence | ||
*/ | ||
@@ -26,3 +26,2 @@ export interface Device<TData = any> { | ||
* This is meant to be used by device drivers. | ||
* @experimental | ||
*/ | ||
@@ -32,3 +31,2 @@ data: TData; | ||
* Major device number | ||
* @experimental | ||
*/ | ||
@@ -38,3 +36,2 @@ major: number; | ||
* Minor device number | ||
* @experimental | ||
*/ | ||
@@ -52,2 +49,7 @@ minor: number; | ||
/** | ||
* If true, only a single device can exist per device FS. | ||
* Note that if this is unset or false, auto-named devices will have a number suffix | ||
*/ | ||
singleton?: boolean; | ||
/** | ||
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device) | ||
@@ -60,8 +62,8 @@ * @default false | ||
* @returns `Device.data` | ||
* @experimental | ||
*/ | ||
init?(ino: bigint): { | ||
init?(ino: bigint, options: object): { | ||
data?: TData; | ||
minor?: number; | ||
major?: number; | ||
name?: string; | ||
}; | ||
@@ -72,3 +74,3 @@ /** | ||
*/ | ||
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number; | ||
read(file: DeviceFile<TData>, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number; | ||
/** | ||
@@ -78,3 +80,3 @@ * Synchronously write to the device | ||
*/ | ||
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number; | ||
write(file: DeviceFile<TData>, buffer: Uint8Array, offset: number, length: number, position?: number): number; | ||
/** | ||
@@ -84,3 +86,3 @@ * Sync the device | ||
*/ | ||
sync?(file: DeviceFile): void; | ||
sync?(file: DeviceFile<TData>): void; | ||
/** | ||
@@ -90,3 +92,3 @@ * Close the device | ||
*/ | ||
close?(file: DeviceFile): void; | ||
close?(file: DeviceFile<TData>): void; | ||
} | ||
@@ -99,8 +101,8 @@ /** | ||
*/ | ||
export declare class DeviceFile extends File { | ||
export declare class DeviceFile<TData = any> extends File { | ||
fs: DeviceFS; | ||
readonly device: Device; | ||
readonly device: Device<TData>; | ||
position: number; | ||
constructor(fs: DeviceFS, path: string, device: Device); | ||
get driver(): DeviceDriver; | ||
constructor(fs: DeviceFS, path: string, device: Device<TData>); | ||
get driver(): DeviceDriver<TData>; | ||
protected get stats(): Partial<StatsLike>; | ||
@@ -135,5 +137,11 @@ stat(): Promise<Stats>; | ||
* Creates a new device at `path` relative to the `DeviceFS` root. | ||
* @deprecated | ||
*/ | ||
createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>>; | ||
createDevice<TData = any>(path: string, driver: DeviceDriver<TData>, options?: object): Device<TData | Record<string, never>>; | ||
protected devicesWithDriver(driver: DeviceDriver<unknown> | string, forceIdentity?: boolean): Device[]; | ||
/** | ||
* @internal | ||
*/ | ||
_createDevice<TData = any>(driver: DeviceDriver<TData>, options?: object): Device<TData | Record<string, never>>; | ||
/** | ||
* Adds default devices | ||
@@ -168,3 +176,3 @@ */ | ||
* - Writes discard data, advancing the file position. | ||
* @experimental | ||
* @internal | ||
*/ | ||
@@ -180,3 +188,3 @@ export declare const nullDevice: DeviceDriver; | ||
* - Provides basic file metadata, treating it as a character device. | ||
* @experimental | ||
* @internal | ||
*/ | ||
@@ -188,3 +196,3 @@ export declare const zeroDevice: DeviceDriver; | ||
* - Writes always fail with ENOSPC (no space left on device). | ||
* @experimental | ||
* @internal | ||
*/ | ||
@@ -196,3 +204,3 @@ export declare const fullDevice: DeviceDriver; | ||
* - Writes discard data, advancing the file position. | ||
* @experimental | ||
* @internal | ||
*/ | ||
@@ -202,5 +210,4 @@ export declare const randomDevice: DeviceDriver; | ||
* Shortcuts for importing. | ||
* @experimental | ||
*/ | ||
declare const _default: { | ||
export declare const devices: { | ||
null: DeviceDriver<any>; | ||
@@ -210,3 +217,5 @@ zero: DeviceDriver<any>; | ||
random: DeviceDriver<any>; | ||
console: DeviceDriver<{ | ||
output: (text: string) => unknown; | ||
}>; | ||
}; | ||
export default _default; |
@@ -63,2 +63,3 @@ /* | ||
import { basename, dirname } from './emulation/path.js'; | ||
import { decodeUTF8 } from './utils.js'; | ||
/** | ||
@@ -158,4 +159,5 @@ * The base class for device files | ||
* Creates a new device at `path` relative to the `DeviceFS` root. | ||
* @deprecated | ||
*/ | ||
createDevice(path, driver) { | ||
createDevice(path, driver, options = {}) { | ||
if (this.existsSync(path)) { | ||
@@ -173,3 +175,3 @@ throw ErrnoError.With('EEXIST', path, 'mknod'); | ||
major: 0, | ||
...driver.init?.(ino), | ||
...driver.init?.(ino, options), | ||
}; | ||
@@ -179,10 +181,47 @@ this.devices.set(path, dev); | ||
} | ||
devicesWithDriver(driver, forceIdentity) { | ||
if (forceIdentity && typeof driver == 'string') { | ||
throw new ErrnoError(Errno.EINVAL, 'Can not fetch devices using only a driver name'); | ||
} | ||
const devs = []; | ||
for (const device of this.devices.values()) { | ||
if (forceIdentity && device.driver != driver) | ||
continue; | ||
const name = typeof driver == 'string' ? driver : driver.name; | ||
if (name == device.driver.name) | ||
devs.push(device); | ||
} | ||
return devs; | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
_createDevice(driver, options = {}) { | ||
let ino = 1n; | ||
while (this.store.has(ino)) | ||
ino++; | ||
const dev = { | ||
driver, | ||
ino, | ||
data: {}, | ||
minor: 0, | ||
major: 0, | ||
...driver.init?.(ino, options), | ||
}; | ||
const path = '/' + (dev.name || driver.name) + (driver.singleton ? '' : this.devicesWithDriver(driver).length); | ||
if (this.existsSync(path)) { | ||
throw ErrnoError.With('EEXIST', path, 'mknod'); | ||
} | ||
this.devices.set(path, dev); | ||
return dev; | ||
} | ||
/** | ||
* Adds default devices | ||
*/ | ||
addDefaults() { | ||
this.createDevice('/null', nullDevice); | ||
this.createDevice('/zero', zeroDevice); | ||
this.createDevice('/full', fullDevice); | ||
this.createDevice('/random', randomDevice); | ||
this._createDevice(nullDevice); | ||
this._createDevice(zeroDevice); | ||
this._createDevice(fullDevice); | ||
this._createDevice(randomDevice); | ||
this._createDevice(consoleDevice); | ||
} | ||
@@ -358,6 +397,7 @@ constructor() { | ||
* - Writes discard data, advancing the file position. | ||
* @experimental | ||
* @internal | ||
*/ | ||
export const nullDevice = { | ||
name: 'null', | ||
singleton: true, | ||
init() { | ||
@@ -379,6 +419,7 @@ return { major: 1, minor: 3 }; | ||
* - Provides basic file metadata, treating it as a character device. | ||
* @experimental | ||
* @internal | ||
*/ | ||
export const zeroDevice = { | ||
name: 'zero', | ||
singleton: true, | ||
init() { | ||
@@ -401,6 +442,7 @@ return { major: 1, minor: 5 }; | ||
* - Writes always fail with ENOSPC (no space left on device). | ||
* @experimental | ||
* @internal | ||
*/ | ||
export const fullDevice = { | ||
name: 'full', | ||
singleton: true, | ||
init() { | ||
@@ -425,6 +467,7 @@ return { major: 1, minor: 7 }; | ||
* - Writes discard data, advancing the file position. | ||
* @experimental | ||
* @internal | ||
*/ | ||
export const randomDevice = { | ||
name: 'random', | ||
singleton: true, | ||
init() { | ||
@@ -444,6 +487,25 @@ return { major: 1, minor: 8 }; | ||
/** | ||
* Simulates the `/dev/console` device. | ||
* @experimental @internal | ||
*/ | ||
const consoleDevice = { | ||
name: 'console', | ||
singleton: true, | ||
init(ino, { output = console.log } = {}) { | ||
return { major: 5, minor: 1, data: { output } }; | ||
}, | ||
read() { | ||
return 0; | ||
}, | ||
write(file, buffer, offset, length) { | ||
const text = decodeUTF8(buffer.slice(offset, offset + length)); | ||
file.device.data.output(text); | ||
file.position += length; | ||
return length; | ||
}, | ||
}; | ||
/** | ||
* Shortcuts for importing. | ||
* @experimental | ||
*/ | ||
export default { | ||
export const devices = { | ||
null: nullDevice, | ||
@@ -453,2 +515,3 @@ zero: zeroDevice, | ||
random: randomDevice, | ||
console: consoleDevice, | ||
}; |
import { Buffer } from 'buffer'; | ||
import type * as fs from 'node:fs'; | ||
import { ErrnoError } from '../error.js'; | ||
import type { FileContents } from '../filesystem.js'; | ||
@@ -312,2 +313,10 @@ import { BigIntStats, type Stats } from '../stats.js'; | ||
export declare function openAsBlob(this: V_Context, path: fs.PathLike, options?: fs.OpenAsBlobOptions): Promise<Blob>; | ||
type GlobCallback<Args extends unknown[]> = (e: ErrnoError | null, ...args: Args) => unknown; | ||
/** | ||
* Retrieves the files matching the specified pattern. | ||
*/ | ||
export declare function glob(this: V_Context, pattern: string | string[], callback: GlobCallback<[string[]]>): void; | ||
export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptionsWithFileTypes, callback: GlobCallback<[Dirent[]]>): void; | ||
export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptionsWithoutFileTypes, callback: GlobCallback<[string[]]>): void; | ||
export declare function glob(this: V_Context, pattern: string | string[], options: fs.GlobOptions, callback: GlobCallback<[Dirent[] | string[]]>): void; | ||
export {}; |
@@ -12,2 +12,12 @@ import { Buffer } from 'buffer'; | ||
/** | ||
* Helper to collect an async iterator into an array | ||
*/ | ||
async function collectAsyncIterator(it) { | ||
const results = []; | ||
for await (const result of it) { | ||
results.push(result); | ||
} | ||
return results; | ||
} | ||
/** | ||
* Asynchronous rename. No arguments other than a possible exception are given to the completion callback. | ||
@@ -560,1 +570,9 @@ */ | ||
openAsBlob; | ||
export function glob(pattern, options, callback = nop) { | ||
callback = typeof options == 'function' ? options : callback; | ||
const it = promises.glob.call(this, pattern, typeof options === 'function' ? undefined : options); | ||
collectAsyncIterator(it) | ||
.then(results => callback(null, results ?? [])) | ||
.catch((e) => callback(e)); | ||
} | ||
glob; |
@@ -7,3 +7,4 @@ export * from './async.js'; | ||
export * from './dir.js'; | ||
export { mountObject, mounts, mount, umount, _synced, chroot } from './shared.js'; | ||
export { mount, umount, chroot, mountObject } from './shared.js'; | ||
export { /** @deprecated security */ mounts } from './shared.js'; | ||
export { Stats, StatsFs, BigIntStatsFs } from '../stats.js'; |
@@ -7,3 +7,4 @@ export * from './async.js'; | ||
export * from './dir.js'; | ||
export { mountObject, mounts, mount, umount, _synced, chroot } from './shared.js'; | ||
export { mount, umount, chroot, mountObject } from './shared.js'; | ||
export { /** @deprecated security */ mounts } from './shared.js'; | ||
export { Stats, StatsFs, BigIntStatsFs } from '../stats.js'; |
@@ -13,4 +13,4 @@ import { Buffer } from 'buffer'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { type InternalOptions, type ReaddirOptions } from './shared.js'; | ||
import { ReadStream, WriteStream } from './streams.js'; | ||
import type { InternalOptions, NullEnc, ReaddirOptions, ReaddirOptsI, ReaddirOptsU } from './types.js'; | ||
export * as constants from './constants.js'; | ||
@@ -75,3 +75,5 @@ export declare class FileHandle implements promises.FileHandle { | ||
*/ | ||
read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<TBuffer>>; | ||
read<T extends NodeJS.ArrayBufferView>(buffer: T, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<T>>; | ||
read<T extends NodeJS.ArrayBufferView = Buffer>(buffer: T, options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>; | ||
read<T extends NodeJS.ArrayBufferView = Buffer>(options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>; | ||
/** | ||
@@ -95,5 +97,2 @@ * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically. | ||
* User code must still call the `fileHandle.close()` method. | ||
* | ||
* @since v17.0.0 | ||
* @experimental | ||
*/ | ||
@@ -119,14 +118,10 @@ readableWebStream(options?: promises.ReadableWebStreamOptions): TReadableStream<Uint8Array>; | ||
*/ | ||
write(data: FileContents, posOrOff?: number | null, lenOrEnc?: BufferEncoding | number, position?: number | null): Promise<{ | ||
write<T extends FileContents>(data: T, options?: number | null | { | ||
offset?: number; | ||
length?: number; | ||
position?: number; | ||
}, lenOrEnc?: BufferEncoding | number | null, position?: number | null): Promise<{ | ||
bytesWritten: number; | ||
buffer: FileContents; | ||
buffer: T; | ||
}>; | ||
write<TBuffer extends Uint8Array>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<{ | ||
bytesWritten: number; | ||
buffer: TBuffer; | ||
}>; | ||
write(data: string, position?: number, encoding?: BufferEncoding): Promise<{ | ||
bytesWritten: number; | ||
buffer: string; | ||
}>; | ||
/** | ||
@@ -263,15 +258,15 @@ * Asynchronously writes data to a file, replacing the file if it already exists. The underlying file will _not_ be closed automatically. | ||
*/ | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{ | ||
withFileTypes?: false; | ||
}) | BufferEncoding | null): Promise<string[]>; | ||
}> | NullEnc): Promise<string[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & { | ||
withFileTypes?: false; | ||
}): Promise<Buffer[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{ | ||
withFileTypes?: false; | ||
}) | BufferEncoding | null): Promise<string[] | Buffer[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & { | ||
}> | NullEnc): Promise<string[] | Buffer[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{ | ||
withFileTypes: true; | ||
}): Promise<Dirent[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): Promise<string[] | Dirent[] | Buffer[]>; | ||
}>): Promise<Dirent[]>; | ||
export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): Promise<string[] | Dirent[] | Buffer[]>; | ||
export declare function link(this: V_Context, targetPath: fs.PathLike, linkPath: fs.PathLike): Promise<void>; | ||
@@ -365,1 +360,9 @@ /** | ||
export declare function statfs(this: V_Context, path: fs.PathLike, opts?: fs.StatFsOptions): Promise<fs.StatsFs | fs.BigIntStatsFs>; | ||
/** | ||
* Retrieves the files matching the specified pattern. | ||
* @todo Implement | ||
*/ | ||
export declare function glob(this: V_Context, pattern: string | string[]): NodeJS.AsyncIterator<string>; | ||
export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptionsWithFileTypes): NodeJS.AsyncIterator<Dirent>; | ||
export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptionsWithoutFileTypes): NodeJS.AsyncIterator<string>; | ||
export declare function glob(this: V_Context, pattern: string | string[], opt: fs.GlobOptions): NodeJS.AsyncIterator<Dirent | string>; |
@@ -150,15 +150,19 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
} | ||
/** | ||
* Asynchronously reads data from the file. | ||
* The `FileHandle` must have been opened for reading. | ||
* @param buffer The buffer that the data will be written to. | ||
* @param offset The offset in the buffer at which to start writing. | ||
* @param length The number of bytes to read. | ||
* @param position The offset from the beginning of the file from which data should be read. If `null`, data will be read from the current position. | ||
*/ | ||
read(buffer, offset, length, position) { | ||
async read(buffer, offset, length, position) { | ||
if (typeof offset == 'object' && offset != null) { | ||
position = offset.position; | ||
length = offset.length; | ||
offset = offset.offset; | ||
} | ||
if (!ArrayBuffer.isView(buffer) && typeof buffer == 'object') { | ||
position = buffer.position; | ||
length = buffer.length; | ||
offset = buffer.offset; | ||
buffer = buffer.buffer; | ||
} | ||
if (isNaN(+position)) { | ||
position = this.file.position; | ||
} | ||
return this.file.read(buffer, offset, length, position); | ||
buffer || (buffer = new Uint8Array((await this.file.stat()).size)); | ||
return this.file.read(buffer, offset ?? undefined, length ?? undefined, position ?? undefined); | ||
} | ||
@@ -184,5 +188,2 @@ async readFile(_options) { | ||
* User code must still call the `fileHandle.close()` method. | ||
* | ||
* @since v17.0.0 | ||
* @experimental | ||
*/ | ||
@@ -235,7 +236,17 @@ readableWebStream(options = {}) { | ||
} | ||
async write(data, posOrOff, lenOrEnc, position) { | ||
/** | ||
* Asynchronously writes `string` to the file. | ||
* The `FileHandle` must have been opened for writing. | ||
* It is unsafe to call `write()` multiple times on the same file without waiting for the `Promise` | ||
* to be resolved (or rejected). For this scenario, `fs.createWriteStream` is strongly recommended. | ||
*/ | ||
async write(data, options, lenOrEnc, position) { | ||
let buffer, offset, length; | ||
if (typeof options == 'object') { | ||
lenOrEnc = options?.length; | ||
position = options?.position; | ||
options = options?.offset; | ||
} | ||
if (typeof data === 'string') { | ||
// Signature 1: (fd, string, [position?, [encoding?]]) | ||
position = typeof posOrOff === 'number' ? posOrOff : null; | ||
position = typeof options === 'number' ? options : null; | ||
const encoding = typeof lenOrEnc === 'string' ? lenOrEnc : 'utf8'; | ||
@@ -247,5 +258,4 @@ offset = 0; | ||
else { | ||
// Signature 2: (fd, buffer, offset, length, position?) | ||
buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); | ||
offset = posOrOff; | ||
offset = options; | ||
length = lenOrEnc; | ||
@@ -257,3 +267,3 @@ position = typeof position === 'number' ? position : null; | ||
emitChange('change', this.file.path); | ||
return { buffer, bytesWritten }; | ||
return { buffer: data, bytesWritten }; | ||
} | ||
@@ -946,2 +956,5 @@ /** | ||
throw: cleanup, | ||
[Symbol.asyncDispose]() { | ||
return Promise.resolve(); | ||
}, | ||
}; | ||
@@ -1097,1 +1110,33 @@ }, | ||
} | ||
export function glob(pattern, opt) { | ||
pattern = Array.isArray(pattern) ? pattern : [pattern]; | ||
const { cwd = '/', withFileTypes = false, exclude = () => false } = opt || {}; | ||
// Escape special characters in pattern | ||
const regexPatterns = pattern.map(p => { | ||
p = p | ||
.replace(/([.?+^$(){}|[\]/])/g, '$1') | ||
.replace(/\*\*/g, '.*') | ||
.replace(/\*/g, '[^/]*') | ||
.replace(/\?/g, '.'); | ||
return new RegExp(`^${p}$`); | ||
}); | ||
async function* recursiveList(dir) { | ||
const entries = await readdir(dir, { withFileTypes, encoding: 'utf8' }); | ||
for (const entry of entries) { | ||
const fullPath = withFileTypes ? entry.path : dir + '/' + entry; | ||
if (exclude((withFileTypes ? entry : fullPath))) | ||
continue; | ||
/** | ||
* @todo it the pattern.source check correct? | ||
*/ | ||
if ((await stat(fullPath)).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) { | ||
yield* recursiveList(fullPath); | ||
} | ||
if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) { | ||
yield withFileTypes ? entry : fullPath.replace(/^\/+/g, ''); | ||
} | ||
} | ||
} | ||
return recursiveList(cwd); | ||
} | ||
glob; |
@@ -1,2 +0,2 @@ | ||
import type { BigIntStatsFs, StatsFs } from 'node:fs'; | ||
import type * as fs from 'node:fs'; | ||
import { type BoundContext, type V_Context } from '../context.js'; | ||
@@ -52,8 +52,2 @@ import { ErrnoError } from '../error.js'; | ||
/** | ||
* Wait for all file systems to be ready and synced. | ||
* May be removed at some point. | ||
* @experimental @internal | ||
*/ | ||
export declare function _synced(): Promise<void>; | ||
/** | ||
* Reverse maps the paths in text from the mounted FileSystem to the global path | ||
@@ -75,20 +69,4 @@ * @internal @hidden | ||
*/ | ||
export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? BigIntStatsFs : StatsFs; | ||
export declare function _statfs<const T extends boolean>(fs: FileSystem, bigint?: T): T extends true ? fs.BigIntStatsFs : fs.StatsFs; | ||
/** | ||
* Options used for caching, among other things. | ||
* @internal @hidden *UNSTABLE* | ||
*/ | ||
export interface InternalOptions { | ||
/** | ||
* If true, then this readdir was called from another function. | ||
* In this case, don't clear the cache when done. | ||
* @internal *UNSTABLE* | ||
*/ | ||
_isIndirect?: boolean; | ||
} | ||
export interface ReaddirOptions extends InternalOptions { | ||
withFileTypes?: boolean; | ||
recursive?: boolean; | ||
} | ||
/** | ||
* Change the root path | ||
@@ -95,0 +73,0 @@ * @param inPlace if true, this changes the root for the current context instead of creating a new one (if associated with a context). |
@@ -89,11 +89,2 @@ // Utilities and shared data | ||
/** | ||
* Wait for all file systems to be ready and synced. | ||
* May be removed at some point. | ||
* @experimental @internal | ||
*/ | ||
export async function _synced() { | ||
await Promise.all([...mounts.values()].map(m => m.ready())); | ||
return; | ||
} | ||
/** | ||
* Reverse maps the paths in text from the mounted FileSystem to the global path | ||
@@ -100,0 +91,0 @@ * @internal @hidden |
@@ -5,3 +5,3 @@ import type * as Node from 'node:fs'; | ||
export declare class ReadStream extends Readable implements Node.ReadStream { | ||
close(callback?: Callback): void; | ||
close(callback?: Callback<[void], null>): void; | ||
wrap(oldStream: NodeJS.ReadableStream): this; | ||
@@ -13,3 +13,3 @@ bytesRead: number; | ||
export declare class WriteStream extends Writable implements Node.WriteStream { | ||
close(callback?: Callback): void; | ||
close(callback?: Callback<[void], null>): void; | ||
bytesWritten: number; | ||
@@ -16,0 +16,0 @@ path: string | Buffer; |
@@ -8,3 +8,3 @@ import { Readable, Writable } from 'readable-stream'; | ||
super.emit('close'); | ||
callback(); | ||
callback(null); | ||
} | ||
@@ -25,3 +25,3 @@ catch (err) { | ||
super.emit('close'); | ||
callback(); | ||
callback(null); | ||
} | ||
@@ -28,0 +28,0 @@ catch (err) { |
@@ -6,4 +6,4 @@ import { Buffer } from 'buffer'; | ||
import { Dir, Dirent } from './dir.js'; | ||
import { type InternalOptions, type ReaddirOptions } from './shared.js'; | ||
import type { V_Context } from '../context.js'; | ||
import type { ReaddirOptsI, ReaddirOptsU, InternalOptions, ReaddirOptions, NullEnc } from './types.js'; | ||
export declare function renameSync(this: V_Context, oldPath: fs.PathLike, newPath: fs.PathLike): void; | ||
@@ -116,15 +116,15 @@ /** | ||
export declare function mkdirSync(this: V_Context, path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{ | ||
withFileTypes?: false; | ||
}) | BufferEncoding | null): string[]; | ||
}> | NullEnc): string[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & { | ||
withFileTypes?: false; | ||
}): Buffer[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & ReaddirOptions & { | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{ | ||
withFileTypes?: false; | ||
}) | BufferEncoding | null): string[] | Buffer[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & ReaddirOptions & { | ||
}> | NullEnc): string[] | Buffer[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{ | ||
withFileTypes: true; | ||
}): Dirent[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (ReaddirOptions & (fs.ObjectEncodingOptions | fs.BufferEncodingOption)) | BufferEncoding | null): string[] | Dirent[] | Buffer[]; | ||
}>): Dirent[]; | ||
export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): string[] | Dirent[] | Buffer[]; | ||
export declare function linkSync(this: V_Context, targetPath: fs.PathLike, linkPath: fs.PathLike): void; | ||
@@ -224,1 +224,8 @@ /** | ||
export declare function statfsSync(this: V_Context, path: fs.PathLike, options?: fs.StatFsOptions): fs.StatsFs | fs.BigIntStatsFs; | ||
/** | ||
* Retrieves the files matching the specified pattern. | ||
*/ | ||
export declare function globSync(pattern: string | string[]): string[]; | ||
export declare function globSync(pattern: string | string[], options: fs.GlobOptionsWithFileTypes): Dirent[]; | ||
export declare function globSync(pattern: string | string[], options: fs.GlobOptionsWithoutFileTypes): string[]; | ||
export declare function globSync(pattern: string | string[], options: fs.GlobOptions): Dirent[] | string[]; |
@@ -799,1 +799,35 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { | ||
} | ||
export function globSync(pattern, options = {}) { | ||
pattern = Array.isArray(pattern) ? pattern : [pattern]; | ||
const { cwd = '/', withFileTypes = false, exclude = () => false } = options; | ||
// Escape special characters in pattern | ||
const regexPatterns = pattern.map(p => { | ||
p = p | ||
.replace(/([.?+^$(){}|[\]/])/g, '\\$1') | ||
.replace(/\*\*/g, '.*') | ||
.replace(/\*/g, '[^/]*') | ||
.replace(/\?/g, '.'); | ||
return new RegExp(`^${p}$`); | ||
}); | ||
const results = []; | ||
function recursiveList(dir) { | ||
const entries = readdirSync(dir, { withFileTypes, encoding: 'utf8' }); | ||
for (const entry of entries) { | ||
const fullPath = withFileTypes ? entry.path : dir + '/' + entry; | ||
if (exclude((withFileTypes ? entry : fullPath))) | ||
continue; | ||
/** | ||
* @todo it the pattern.source check correct? | ||
*/ | ||
if (statSync(fullPath).isDirectory() && regexPatterns.some(pattern => pattern.source.includes('.*'))) { | ||
recursiveList(fullPath); | ||
} | ||
if (regexPatterns.some(pattern => pattern.test(fullPath.replace(/^\/+/g, '')))) { | ||
results.push(withFileTypes ? entry.path : fullPath.replace(/^\/+/g, '')); | ||
} | ||
} | ||
} | ||
recursiveList(cwd); | ||
return results; | ||
} | ||
globSync; |
@@ -15,3 +15,2 @@ export * from './error.js'; | ||
export * from './devices.js'; | ||
export { default as devices } from './devices.js'; | ||
export * from './file.js'; | ||
@@ -18,0 +17,0 @@ export * from './filesystem.js'; |
@@ -15,3 +15,2 @@ export * from './error.js'; | ||
export * from './devices.js'; | ||
export { default as devices } from './devices.js'; | ||
export * from './file.js'; | ||
@@ -18,0 +17,0 @@ export * from './filesystem.js'; |
@@ -52,3 +52,3 @@ import type * as fs from 'node:fs'; | ||
export declare function encodeDirListing(data: Record<string, bigint>): Uint8Array; | ||
export type Callback<Args extends unknown[] = []> = (e?: ErrnoError, ...args: OptionalTuple<Args>) => unknown; | ||
export type Callback<Args extends unknown[] = [], NoError = undefined | void> = (e: ErrnoError | NoError, ...args: OptionalTuple<Args>) => unknown; | ||
/** | ||
@@ -55,0 +55,0 @@ * Normalizes a mode |
{ | ||
"name": "@zenfs/core", | ||
"version": "1.5.1", | ||
"version": "1.6.0", | ||
"description": "A filesystem, anywhere", | ||
@@ -61,3 +61,4 @@ "funding": { | ||
"dev": "npm run build -- --watch", | ||
"prepublishOnly": "npm run build" | ||
"prepublishOnly": "npm run build", | ||
"postinstall": "patch-package" | ||
}, | ||
@@ -70,3 +71,3 @@ "lint-staged": { | ||
"dependencies": { | ||
"@types/node": "^20.16.10", | ||
"@types/node": "^22.10.1", | ||
"@types/readable-stream": "^4.0.10", | ||
@@ -88,2 +89,3 @@ "buffer": "^6.0.3", | ||
"lint-staged": "^15.2.7", | ||
"patch-package": "^8.0.0", | ||
"prettier": "^3.2.5", | ||
@@ -90,0 +92,0 @@ "tsx": "^4.19.1", |
@@ -177,2 +177,5 @@ # ZenFS | ||
name: 'custom_null', | ||
// only 1 can exist per DeviceFS | ||
singleton: true, | ||
// optional if false | ||
isBuffered: false, | ||
@@ -182,3 +185,5 @@ read() { | ||
}, | ||
write() {}, | ||
write() { | ||
return 0; | ||
}, | ||
}; | ||
@@ -189,17 +194,12 @@ ``` | ||
Finally, if you'd like to use your custom device with the file system, you can use so through the aptly named `DeviceFS`. | ||
Finally, if you'd like to use your custom device with the file system: | ||
```ts | ||
const devfs = fs.mounts.get('/dev') as DeviceFS; | ||
devfs.createDevice('/custom', customNullDevice); | ||
import { addDevice, fs } from '@zenfs/core'; | ||
addDevice(customNullDevice); | ||
fs.writeFileSync('/dev/custom', 'This gets discarded.'); | ||
``` | ||
In the above example, `createDevice` works relative to the `DeviceFS` mount point. | ||
Additionally, a type assertion (` as ...`) is used since `fs.mounts` does not keep track of which file system type is mapped to which mount point. Doing so would create significant maintenance costs due to the complexity of implementing it. | ||
If you would like to see a more intuitive way adding custom devices (e.g. `fs.mknod`), please feel free to open an issue for a feature request. | ||
## Using with bundlers | ||
@@ -206,0 +206,0 @@ |
@@ -16,4 +16,2 @@ import assert, { rejects } from 'node:assert'; | ||
await fs._synced(); | ||
suite('Dirent', () => { | ||
@@ -20,0 +18,0 @@ test('name and parentPath getters', async () => { |
@@ -21,4 +21,2 @@ import assert from 'node:assert'; | ||
await fs._synced(); | ||
suite('Directories', () => { | ||
@@ -25,0 +23,0 @@ test('mkdir', async () => { |
@@ -36,3 +36,3 @@ import assert from 'node:assert'; | ||
readStream.close(err => { | ||
assert.strictEqual(err, undefined); | ||
assert.equal(err, null); | ||
assert(closed); | ||
@@ -62,6 +62,6 @@ done(); | ||
readStream.close(err => { | ||
assert.strictEqual(err, undefined); | ||
assert.equal(err, null); | ||
// Call close again | ||
readStream.close(err2 => { | ||
assert.strictEqual(err2, undefined); | ||
assert.equal(err2, null); | ||
done(); | ||
@@ -99,3 +99,3 @@ }); | ||
writeStream.close(err => { | ||
assert.strictEqual(err, undefined); | ||
assert.equal(err, null); | ||
assert(closed); | ||
@@ -125,6 +125,6 @@ done(); | ||
writeStream.close(err => { | ||
assert.strictEqual(err, undefined); | ||
assert.equal(err, null); | ||
// Call close again | ||
writeStream.close(err2 => { | ||
assert.strictEqual(err2, undefined); | ||
assert.equal(err2, null); | ||
done(); | ||
@@ -131,0 +131,0 @@ }); |
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
584891
14812
12
1
+ Added@types/node@22.10.10(transitive)
+ Addedundici-types@6.20.0(transitive)
- Removed@types/node@20.17.16(transitive)
- Removedundici-types@6.19.8(transitive)
Updated@types/node@^22.10.1