@zenfs/core 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import type { RequiredKeys } from 'utilium';
2
- import type { FileSystem } from '../internal/filesystem.js';
2
+ import type { CaseFold, FileSystem } from '../internal/filesystem.js';
3
3
  type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | string | ((arg: any) => boolean) | {
4
4
  [Symbol.hasInstance](instance: any): boolean;
5
5
  toString(): string;
@@ -33,6 +33,10 @@ export interface SharedConfig {
33
33
  * If set, disables the sync cache and sync operations on async file systems.
34
34
  */
35
35
  disableAsyncCache?: boolean;
36
+ /**
37
+ * If set, sets case folding for the file system(s).
38
+ */
39
+ caseFold?: CaseFold;
36
40
  }
37
41
  /**
38
42
  * A backend
@@ -710,7 +710,7 @@ export class SingleBufferStore extends BufferView {
710
710
  return this._fs;
711
711
  }
712
712
  set fs(fs) {
713
- if (this.buffer instanceof SharedArrayBuffer)
713
+ if (this.buffer.constructor.name === 'SharedArrayBuffer')
714
714
  fs === null || fs === void 0 ? void 0 : fs.attributes.set('no_id_tables', true);
715
715
  this._fs = fs;
716
716
  }
package/dist/config.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import type { Backend, BackendConfiguration, FilesystemOf, SharedConfig } from './backends/backend.js';
2
2
  import type { Device, DeviceDriver } from './internal/devices.js';
3
3
  import { log } from 'kerium';
4
+ import { FileSystem } from './internal/filesystem.js';
5
+ /**
6
+ * Update the configuration of a file system.
7
+ * @category Backends and Configuration
8
+ */
9
+ export declare function configureFileSystem(fs: FileSystem, config: SharedConfig): void;
4
10
  /**
5
11
  * Configuration for a specific mount point
6
12
  * @category Backends and Configuration
package/dist/config.js CHANGED
@@ -7,6 +7,16 @@ import { FileSystem } from './internal/filesystem.js';
7
7
  import { _setAccessChecks } from './vfs/config.js';
8
8
  import * as fs from './vfs/index.js';
9
9
  import { mounts } from './vfs/shared.js';
10
+ /**
11
+ * Update the configuration of a file system.
12
+ * @category Backends and Configuration
13
+ */
14
+ export function configureFileSystem(fs, config) {
15
+ if (config.disableAsyncCache)
16
+ fs.attributes.set('no_async_preload');
17
+ if (config.caseFold)
18
+ fs.attributes.set('case_fold', config.caseFold);
19
+ }
10
20
  function isMountConfig(arg) {
11
21
  return isBackendConfig(arg) || isBackend(arg) || arg instanceof FileSystem;
12
22
  }
@@ -46,8 +56,7 @@ export async function resolveMountConfig(configuration, _depth = 0) {
46
56
  }
47
57
  checkOptions(backend, configuration);
48
58
  const mount = (await backend.create(configuration));
49
- if (configuration.disableAsyncCache)
50
- mount.attributes.set('no_async_preload');
59
+ configureFileSystem(mount, configuration);
51
60
  await mount.ready();
52
61
  return mount;
53
62
  }
@@ -98,10 +107,11 @@ export function addDevice(driver, options) {
98
107
  * @see Configuration
99
108
  */
100
109
  export async function configure(configuration) {
101
- var _a;
102
- const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
103
- const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
104
- Object.assign(defaultContext.credentials, createCredentials({ uid, gid }));
110
+ var _a, _b;
111
+ Object.assign(defaultContext.credentials, createCredentials({
112
+ uid: configuration.uid || 0,
113
+ gid: configuration.gid || 0,
114
+ }));
105
115
  _setAccessChecks(!configuration.disableAccessChecks);
106
116
  if (configuration.log)
107
117
  log.configure(configuration.log);
@@ -111,13 +121,17 @@ export async function configure(configuration) {
111
121
  const point = _point.startsWith('/') ? _point : '/' + _point;
112
122
  if (isBackendConfig(mountConfig)) {
113
123
  (_a = mountConfig.disableAsyncCache) !== null && _a !== void 0 ? _a : (mountConfig.disableAsyncCache = configuration.disableAsyncCache || false);
124
+ (_b = mountConfig.caseFold) !== null && _b !== void 0 ? _b : (mountConfig.caseFold = configuration.caseFold);
114
125
  }
115
126
  if (point == '/')
116
127
  fs.umount('/');
117
128
  await mount(point, await resolveMountConfig(mountConfig));
118
129
  }
119
130
  }
120
- if (configuration.addDevices) {
131
+ for (const fs of mounts.values()) {
132
+ configureFileSystem(fs, configuration);
133
+ }
134
+ if (configuration.addDevices && !mounts.has('/dev')) {
121
135
  const devfs = new DeviceFS();
122
136
  devfs.addDefaults();
123
137
  await devfs.ready();
package/dist/index.js CHANGED
@@ -10,4 +10,5 @@ export * from './vfs/index.js';
10
10
  export { fs };
11
11
  import * as fs from './vfs/index.js';
12
12
  export default fs;
13
- globalThis.__zenfs__ = fs;
13
+ import $pkg from '../package.json' with { type: 'json' };
14
+ globalThis.__zenfs__ = Object.assign(Object.create(fs), { _version: $pkg.version });
@@ -29,13 +29,14 @@ export interface UsageInfo {
29
29
  */
30
30
  freeNodes?: number;
31
31
  }
32
+ export type CaseFold = 'upper' | 'lower';
32
33
  /**
33
34
  * Attributes that control how the file system interacts with the VFS.
34
35
  * No options are set by default.
35
36
  * @category Internals
36
37
  * @internal
37
38
  */
38
- export type FileSystemAttributes = {
39
+ export interface FileSystemAttributes {
39
40
  /**
40
41
  * If set disables async file systems from preloading their contents.
41
42
  * This means *sync operations will not work* (unless the contents are cached)
@@ -79,7 +80,11 @@ export type FileSystemAttributes = {
79
80
  * @experimental
80
81
  */
81
82
  sync: void;
82
- };
83
+ /**
84
+ * If set, the VFS layer will convert paths to lower/upper case.
85
+ */
86
+ case_fold?: CaseFold;
87
+ }
83
88
  /**
84
89
  * Options used when creating files and directories.
85
90
  * This weird naming and such is to preserve backward compatibility.
@@ -39,8 +39,7 @@ export class FileSystem {
39
39
  this.attributes.set('default_stream_write');
40
40
  }
41
41
  toString() {
42
- var _a;
43
- return `${this.name} ${(_a = this.label) !== null && _a !== void 0 ? _a : ''} (${this._mountPoint ? 'mounted on ' + this._mountPoint : 'unmounted'})`;
42
+ return `${this.name} ${this.label ? JSON.stringify(this.label) : ''} (${this._mountPoint ? 'mounted on ' + this._mountPoint : 'unmounted'})`;
44
43
  }
45
44
  /**
46
45
  * Default implementation.
@@ -128,6 +128,7 @@ export class IndexFS extends FileSystem {
128
128
  size: 0,
129
129
  uid: parent.mode & S_ISUID ? parent.uid : options.uid,
130
130
  gid: parent.mode & S_ISGID ? parent.gid : options.gid,
131
+ nlink: 1,
131
132
  });
132
133
  this.index.set(path, inode);
133
134
  return inode;
@@ -1,5 +1,5 @@
1
1
  import type { FileSystem } from '../internal/filesystem.js';
2
- import type { _SyncFSKeys, AsyncFSMethods, Mixin } from './shared.js';
2
+ import { type _SyncFSKeys, type AsyncFSMethods, type Mixin } from './shared.js';
3
3
  /**
4
4
  * @internal
5
5
  * @category Internals
@@ -16,7 +16,13 @@ export interface AsyncMixin extends Pick<FileSystem, Exclude<_SyncFSKeys, 'exist
16
16
  * @internal @protected
17
17
  */
18
18
  _sync?: FileSystem;
19
+ /**
20
+ * @deprecated Use {@link sync | `sync`} instead
21
+ */
19
22
  queueDone(): Promise<void>;
23
+ /**
24
+ * @deprecated Use {@link sync | `sync`} instead
25
+ */
20
26
  ready(): Promise<void>;
21
27
  }
22
28
  /**
@@ -1,6 +1,6 @@
1
+ import { _asyncFSKeys } from './shared.js';
1
2
  import { withErrno } from 'kerium';
2
3
  import { crit, debug, err } from 'kerium/log';
3
- import { getAllPrototypes } from 'utilium';
4
4
  import { StoreFS } from '../backends/store/fs.js';
5
5
  import { isDirectory } from '../internal/inode.js';
6
6
  import { join } from '../path.js';
@@ -16,11 +16,17 @@ import { join } from '../path.js';
16
16
  */
17
17
  export function Async(FS) {
18
18
  class AsyncFS extends FS {
19
+ /**
20
+ * @deprecated Use {@link sync | `sync`} instead
21
+ */
19
22
  async done() {
20
- await this._promise;
23
+ return this.sync();
21
24
  }
25
+ /**
26
+ * @deprecated Use {@link sync | `sync`} instead
27
+ */
22
28
  queueDone() {
23
- return this.done();
29
+ return this.sync();
24
30
  }
25
31
  _async(promise) {
26
32
  this._promise = this._promise.then(() => promise);
@@ -91,8 +97,8 @@ export function Async(FS) {
91
97
  }
92
98
  unlinkSync(path) {
93
99
  this.checkSync();
94
- this._sync.unlinkSync(path);
95
100
  this._async(this.unlink(path));
101
+ this._sync.unlinkSync(path);
96
102
  }
97
103
  rmdirSync(path) {
98
104
  this.checkSync();
@@ -114,8 +120,8 @@ export function Async(FS) {
114
120
  this._async(this.link(srcpath, dstpath));
115
121
  }
116
122
  async sync() {
117
- this.checkSync();
118
- this._sync.syncSync();
123
+ if (!this.attributes.has('no_async_preload') && this._sync)
124
+ this._sync.syncSync();
119
125
  await this._promise;
120
126
  }
121
127
  syncSync() {
@@ -180,19 +186,19 @@ export function Async(FS) {
180
186
  * Patch all async methods to also call their synchronous counterparts unless called from themselves (either sync or async)
181
187
  */
182
188
  _patchAsync() {
183
- const methods = Array.from(getAllPrototypes(this))
184
- .flatMap(Object.getOwnPropertyNames)
185
- .filter(key => typeof this[key] == 'function' && `${key}Sync` in this);
186
- debug('Async: patching methods: ' + methods.join(', '));
187
- for (const key of methods) {
189
+ debug(`Async: patched ${_asyncFSKeys.length} methods`);
190
+ for (const key of _asyncFSKeys) {
188
191
  // TS does not narrow the union based on the key
189
- const originalMethod = this[key];
192
+ const originalMethod = this[key].bind(this);
190
193
  this[key] = async (...args) => {
191
- var _a, _b, _c;
192
- const result = await originalMethod.apply(this, args);
193
- const stack = (_a = new Error().stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(2).join('\n');
194
- // !stack == From the async queue
195
- if ((stack === null || stack === void 0 ? void 0 : stack.includes(`at <computed> [as ${key}]`)) || (stack === null || stack === void 0 ? void 0 : stack.includes(`${key}Sync `)) || !stack)
194
+ var _a, _b;
195
+ const result = await originalMethod(...args);
196
+ const stack = new Error().stack.split('\n').slice(2).join('\n');
197
+ // From the async queue
198
+ if (!stack
199
+ || stack.includes(`at <computed> [as ${key}]`)
200
+ || stack.includes(`at async <computed> [as ${key}]`)
201
+ || stack.includes(`${key}Sync `))
196
202
  return result;
197
203
  if (!this._isInitialized) {
198
204
  this._skippedCacheUpdates++;
@@ -200,10 +206,16 @@ export function Async(FS) {
200
206
  }
201
207
  try {
202
208
  // @ts-expect-error 2556 - The type of `args` is not narrowed
203
- (_c = (_b = this._sync) === null || _b === void 0 ? void 0 : _b[`${key}Sync`]) === null || _c === void 0 ? void 0 : _c.call(_b, ...args);
209
+ (_b = (_a = this._sync) === null || _a === void 0 ? void 0 : _a[`${key}Sync`]) === null || _b === void 0 ? void 0 : _b.call(_a, ...args);
204
210
  }
205
211
  catch (e) {
206
- throw err(withErrno(e.errno, e.message + ' (Out of sync!)'));
212
+ const stack = e.stack.split('\n').slice(3).join('\n');
213
+ if (stack.includes(`at <computed> [as ${key}]`)
214
+ || stack.includes(`at async <computed> [as ${key}]`)
215
+ || stack.includes(`${key}Sync `))
216
+ return result;
217
+ e.message += ' (Out of sync!)';
218
+ throw err(e);
207
219
  }
208
220
  return result;
209
221
  };
@@ -30,7 +30,7 @@ export declare class _MutexedFS<T extends FileSystem> implements FileSystem {
30
30
  get name(): string;
31
31
  get label(): string | undefined;
32
32
  set label(value: string | undefined);
33
- get attributes(): import("utilium").ConstMap<import("../internal/filesystem.js").FileSystemAttributes, keyof import("../internal/filesystem.js").FileSystemAttributes, void> & Map<string, any>;
33
+ get attributes(): import("utilium").ConstMap<import("../internal/filesystem.js").FileSystemAttributes, keyof import("../internal/filesystem.js").FileSystemAttributes, void | import("../internal/filesystem.js").CaseFold | undefined> & Map<string, any>;
34
34
  get _uuid(): UUID;
35
35
  set _uuid(value: UUID);
36
36
  get uuid(): UUID;
@@ -18,15 +18,18 @@ export type _SyncFSKeys = Exclude<Extract<keyof FileSystem, `${string}Sync`>, '_
18
18
  export type _AsyncFSKeys = {
19
19
  [K in _SyncFSKeys]: K extends `${infer T}Sync` ? T : never;
20
20
  }[_SyncFSKeys];
21
+ export declare const _asyncFSKeys: ["rename", "stat", "touch", "createFile", "unlink", "rmdir", "mkdir", "readdir", "exists", "link", "sync", "read", "write"];
21
22
  /**
22
23
  * Asynchronous `FileSystem` methods. This is a convenience type for all of the async operations.
23
24
  * @category Internals
24
25
  * @internal
25
26
  */
26
- export type AsyncFSMethods = Pick<FileSystem, _AsyncFSKeys>;
27
+ export interface AsyncFSMethods extends Pick<FileSystem, _AsyncFSKeys> {
28
+ }
27
29
  /**
28
30
  * Concrete `FileSystem`. This is a convenience type.
29
31
  * @category Internals
30
32
  * @internal
31
33
  */
32
- export type ConcreteFS = ExtractProperties<FileSystem, any>;
34
+ export interface ConcreteFS extends ExtractProperties<FileSystem, any> {
35
+ }
@@ -1,5 +1,19 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
1
+ /* eslint-disable @typescript-eslint/no-empty-object-type,@typescript-eslint/no-explicit-any */
2
2
  /*
3
3
  Code shared by various mixins
4
4
  */
5
- export {};
5
+ export const _asyncFSKeys = [
6
+ 'rename',
7
+ 'stat',
8
+ 'touch',
9
+ 'createFile',
10
+ 'unlink',
11
+ 'rmdir',
12
+ 'mkdir',
13
+ 'readdir',
14
+ 'exists',
15
+ 'link',
16
+ 'sync',
17
+ 'read',
18
+ 'write',
19
+ ];
package/dist/path.d.ts CHANGED
@@ -14,3 +14,4 @@ export declare function basename(path: string, suffix?: string): string;
14
14
  export declare function extname(path: string): string;
15
15
  export declare function format(pathObject: ParsedPath): string;
16
16
  export declare function parse(path: string): ParsedPath;
17
+ export declare function matchesGlob(pattern: string, str: string): boolean;
package/dist/path.js CHANGED
@@ -21,6 +21,7 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21
21
  USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
  import { defaultContext } from './internal/contexts.js';
24
+ import { globToRegex } from './utils.js';
24
25
  export const sep = '/';
25
26
  function validateObject(str, name) {
26
27
  if (typeof str != 'object') {
@@ -419,3 +420,6 @@ export function parse(path) {
419
420
  ret.dir = '/';
420
421
  return ret;
421
422
  }
423
+ export function matchesGlob(pattern, str) {
424
+ return globToRegex(pattern).test(str);
425
+ }
@@ -85,6 +85,7 @@ export declare class Interface extends EventEmitter<InterfaceEvents> implements
85
85
  */
86
86
  getMaxListeners(): number;
87
87
  [Symbol.asyncIterator](): AsyncIteratorObject<string>;
88
+ [Symbol.dispose](): void;
88
89
  [Symbol.asyncDispose](): Promise<void>;
89
90
  rawListeners(event: keyof InterfaceEvents): ((...args: any[]) => void)[];
90
91
  }
package/dist/readline.js CHANGED
@@ -365,6 +365,9 @@ export class Interface extends EventEmitter {
365
365
  },
366
366
  };
367
367
  }
368
+ [Symbol.dispose]() {
369
+ this.close();
370
+ }
368
371
  async [Symbol.asyncDispose]() {
369
372
  if (this._closed)
370
373
  return;
package/dist/utils.d.ts CHANGED
@@ -53,3 +53,8 @@ export declare function normalizeOptions(options: fs.WriteFileOptions | (fs.Enco
53
53
  flag: string;
54
54
  mode: number;
55
55
  };
56
+ /**
57
+ * Converts a glob pattern to a regular expression
58
+ * @internal
59
+ */
60
+ export declare function globToRegex(pattern: string): RegExp;
package/dist/utils.js CHANGED
@@ -96,3 +96,15 @@ export function normalizeOptions(options, encoding = 'utf8', flag, mode = 0) {
96
96
  mode: normalizeMode('mode' in options ? options === null || options === void 0 ? void 0 : options.mode : null, mode),
97
97
  };
98
98
  }
99
+ /**
100
+ * Converts a glob pattern to a regular expression
101
+ * @internal
102
+ */
103
+ export function globToRegex(pattern) {
104
+ pattern = pattern
105
+ .replace(/([.?+^$(){}|[\]/])/g, '$1')
106
+ .replace(/\*\*/g, '.*')
107
+ .replace(/\*/g, '[^/]*')
108
+ .replace(/\?/g, '.');
109
+ return new RegExp(`^${pattern}$`);
110
+ }
@@ -128,7 +128,7 @@ export declare class FileHandle implements promises.FileHandle {
128
128
  * Read file data using a `ReadableStream`.
129
129
  * The handle will not be closed automatically.
130
130
  */
131
- readableWebStream(options?: promises.ReadableWebStreamOptions & StreamOptions): NodeReadableStream<Uint8Array>;
131
+ readableWebStream(options?: StreamOptions): NodeReadableStream<Uint8Array>;
132
132
  /**
133
133
  * Not part of the Node.js API!
134
134
  *
@@ -136,7 +136,7 @@ export declare class FileHandle implements promises.FileHandle {
136
136
  * The handle will not be closed automatically.
137
137
  * @internal
138
138
  */
139
- writableWebStream(options?: promises.ReadableWebStreamOptions & StreamOptions): WritableStream;
139
+ writableWebStream(options?: StreamOptions): WritableStream;
140
140
  /**
141
141
  * Creates a readline Interface object that allows reading the file line by line
142
142
  * @param options Options for creating a read stream
@@ -55,10 +55,10 @@ import { Exception, rethrow, setUVMessage, UV } from 'kerium';
55
55
  import { decodeUTF8, pick } from 'utilium';
56
56
  import { defaultContext } from '../internal/contexts.js';
57
57
  import { hasAccess, InodeFlags, isBlockDevice, isCharacterDevice, isDirectory, isSymbolicLink } from '../internal/inode.js';
58
- import { dirname, join, parse, resolve } from '../path.js';
58
+ import { dirname, join, matchesGlob, parse, resolve } from '../path.js';
59
59
  import '../polyfills.js';
60
60
  import { createInterface } from '../readline.js';
61
- import { __assertType, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
61
+ import { __assertType, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
62
62
  import { checkAccess } from './config.js';
63
63
  import * as constants from './constants.js';
64
64
  import { Dir, Dirent } from './dir.js';
@@ -1219,19 +1219,12 @@ export function glob(pattern, opt) {
1219
1219
  pattern = Array.isArray(pattern) ? pattern : [pattern];
1220
1220
  const { cwd = '/', withFileTypes = false, exclude = () => false } = opt || {};
1221
1221
  // Escape special characters in pattern
1222
- const regexPatterns = pattern.map(p => {
1223
- p = p
1224
- .replace(/([.?+^$(){}|[\]/])/g, '$1')
1225
- .replace(/\*\*/g, '.*')
1226
- .replace(/\*/g, '[^/]*')
1227
- .replace(/\?/g, '.');
1228
- return new RegExp(`^${p}$`);
1229
- });
1222
+ const regexPatterns = pattern.map(globToRegex);
1230
1223
  async function* recursiveList(dir) {
1231
1224
  const entries = await readdir(dir, { withFileTypes, encoding: 'utf8' });
1232
1225
  for (const entry of entries) {
1233
1226
  const fullPath = withFileTypes ? entry.path : dir + '/' + entry;
1234
- if (exclude((withFileTypes ? entry : fullPath)))
1227
+ if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, fullPath)) : exclude((withFileTypes ? entry : fullPath)))
1235
1228
  continue;
1236
1229
  /**
1237
1230
  * @todo is the pattern.source check correct?
@@ -55,12 +55,17 @@ export function resolveMount(path, ctx) {
55
55
  const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
56
56
  for (const [mountPoint, fs] of sortedMounts) {
57
57
  // We know path is normalized, so it would be a substring of the mount point.
58
- if (_isParentOf(mountPoint, path)) {
59
- path = path.slice(mountPoint.length > 1 ? mountPoint.length : 0); // Resolve the path relative to the mount point
60
- if (path === '')
61
- path = root;
62
- return { fs, path, mountPoint, root };
63
- }
58
+ if (!_isParentOf(mountPoint, path))
59
+ continue;
60
+ path = path.slice(mountPoint.length > 1 ? mountPoint.length : 0); // Resolve the path relative to the mount point
61
+ if (path === '')
62
+ path = root;
63
+ const case_fold = fs.attributes.get('case_fold');
64
+ if (case_fold === 'lower')
65
+ path = path.toLowerCase();
66
+ if (case_fold === 'upper')
67
+ path = path.toUpperCase();
68
+ return { fs, path, mountPoint, root };
64
69
  }
65
70
  throw alert(new Exception(Errno.EIO, 'No file system for ' + path));
66
71
  }
package/dist/vfs/sync.js CHANGED
@@ -56,8 +56,8 @@ import { decodeUTF8, encodeUTF8 } from 'utilium';
56
56
  import { defaultContext } from '../internal/contexts.js';
57
57
  import { wrap } from '../internal/error.js';
58
58
  import { hasAccess, isDirectory, isSymbolicLink } from '../internal/inode.js';
59
- import { dirname, join, parse, resolve } from '../path.js';
60
- import { __assertType, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
59
+ import { dirname, join, matchesGlob, parse, resolve } from '../path.js';
60
+ import { __assertType, globToRegex, normalizeMode, normalizeOptions, normalizePath, normalizeTime } from '../utils.js';
61
61
  import { checkAccess } from './config.js';
62
62
  import * as constants from './constants.js';
63
63
  import { Dir, Dirent } from './dir.js';
@@ -860,20 +860,13 @@ export function globSync(pattern, options = {}) {
860
860
  pattern = Array.isArray(pattern) ? pattern : [pattern];
861
861
  const { cwd = '/', withFileTypes = false, exclude = () => false } = options;
862
862
  // Escape special characters in pattern
863
- const regexPatterns = pattern.map(p => {
864
- p = p
865
- .replace(/([.?+^$(){}|[\]/])/g, '\\$1')
866
- .replace(/\*\*/g, '.*')
867
- .replace(/\*/g, '[^/]*')
868
- .replace(/\?/g, '.');
869
- return new RegExp(`^${p}$`);
870
- });
863
+ const regexPatterns = pattern.map(globToRegex);
871
864
  const results = [];
872
865
  function recursiveList(dir) {
873
866
  const entries = readdirSync(dir, { withFileTypes, encoding: 'utf8' });
874
867
  for (const entry of entries) {
875
868
  const fullPath = withFileTypes ? entry.path : dir + '/' + entry;
876
- if (exclude((withFileTypes ? entry : fullPath)))
869
+ if (typeof exclude != 'function' ? exclude.some(p => matchesGlob(p, fullPath)) : exclude((withFileTypes ? entry : fullPath)))
877
870
  continue;
878
871
  /**
879
872
  * @todo is the pattern.source check correct?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -67,7 +67,7 @@
67
67
  "prepublishOnly": "npm run build"
68
68
  },
69
69
  "dependencies": {
70
- "@types/node": "^22.10.1 <22.13.7",
70
+ "@types/node": "^22.15.2",
71
71
  "buffer": "^6.0.3",
72
72
  "eventemitter3": "^5.0.1",
73
73
  "kerium": "^1.3.4",
@@ -81,10 +81,10 @@
81
81
  "@types/eslint__js": "^8.42.3",
82
82
  "c8": "^10.1.2",
83
83
  "eslint": "^9.15.0",
84
- "globals": "^15.9.0",
84
+ "globals": "^16.0.0",
85
85
  "prettier": "^3.2.5",
86
86
  "tsx": "^4.19.1",
87
- "typedoc": "^0.27.1",
87
+ "typedoc": "^0.28.0",
88
88
  "typescript": "^5.7.2",
89
89
  "typescript-eslint": "^8.16.0"
90
90
  }
package/readme.md CHANGED
@@ -1,23 +1,28 @@
1
1
  # ZenFS
2
2
 
3
- ZenFS is a cross-platform library that emulates the [NodeJS filesystem API](http://nodejs.org/api/fs.html). It works using a system of backends, which are used by ZenFS to store and retrieve data. ZenFS can also integrate with other tools.
3
+ ZenFS is a cross-platform library that emulates the [Node.js filesystem API](http://nodejs.org/api/fs.html).
4
+ It works using a system of backends, which are used by ZenFS to store and retrieve data.
5
+ ZenFS can also integrate with other tools.
4
6
 
5
7
  ## Backends
6
8
 
7
- ZenFS is modular and extensible. The core includes some built-in backends:
9
+ ZenFS is modular and easily extended. The core includes some built-in backends:
8
10
 
9
11
  - `InMemory`: Stores files in-memory. This is cleared when the runtime ends (e.g. a user navigating away from a web page or a Node process exiting)
10
- - `CopyOnWrite`: Use readable and writable file systems with ([copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write)).
12
+ - `CopyOnWrite`: Use readable and writable file systems with [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write).
11
13
  - `Fetch`: Downloads files over HTTP with the `fetch` API
12
14
  - `Port`: Interacts with a remote over a `MessagePort`-like interface (e.g. a worker)
13
15
  - `Passthrough`: Use an existing `node:fs` interface with ZenFS
14
16
  - `SingleBuffer`: A backend contained within a single buffer. Can be used for synchronous multi-threaded operations using `SharedArrayBuffer`
15
17
 
16
- ZenFS supports a number of other backends. Many are provided as separate packages under `@zenfs`. More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
18
+ ZenFS supports a number of other backends.
19
+ Many are provided as separate packages under `@zenfs`.
20
+ More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
17
21
 
18
22
  You can find all of the packages available over on [NPM](https://www.npmjs.com/org/zenfs).
19
23
 
20
- As an added bonus, all ZenFS backends support synchronous operations. All of the backends included with the core are cross-platform.
24
+ As an added bonus, all ZenFS backends support synchronous operations.
25
+ Additionally, all of the backends included with the core are cross-platform.
21
26
 
22
27
  For more information, see the [docs](https://zenfs.dev/core).
23
28
 
@@ -27,7 +32,9 @@ For more information, see the [docs](https://zenfs.dev/core).
27
32
  npm install @zenfs/core
28
33
  ```
29
34
 
30
- If you're using ZenFS, especially for big projects, please consider supporting the project. Thousands of hours have been dedicated to its development. Your acknowledgment or financial support would go a long way toward improving ZenFS and its community.
35
+ If you're using ZenFS, especially for big projects, please consider supporting the project.
36
+ Thousands of hours have been dedicated to its development.
37
+ Your acknowledgment or financial support would go a long way toward improving ZenFS and its community.
31
38
 
32
39
  ## Usage
33
40
 
@@ -149,7 +156,7 @@ fs.umount('/mnt/zip'); // finished using the zip
149
156
 
150
157
  ### Devices and device files
151
158
 
152
- ZenFS includes experimental support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use. You can automatically add some normal devices with the `addDevices` configuration option:
159
+ ZenFS includes support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use. You can automatically add some normal devices with the `addDevices` configuration option:
153
160
 
154
161
  ```ts
155
162
  await configure({
@@ -208,9 +215,9 @@ A huge thank you to [![Deco.cx logo](https://avatars.githubusercontent.com/deco-
208
215
 
209
216
  ## Building
210
217
 
211
- - Make sure you have Node and NPM installed. You must have Node v18 or newer.
218
+ - Make sure you have Node and NPM installed. You must have Node v22 or newer.
212
219
  - Install dependencies with `npm install`
213
- - Build using `npm run build`
220
+ - Build using `npx tsc` or `npm run build`
214
221
  - You can find the built code in `dist`.
215
222
 
216
223
  ### Testing
@@ -0,0 +1,19 @@
1
+ import assert from 'node:assert/strict';
2
+ import { suite, test } from 'node:test';
3
+ import { mounts, configure, fs } from '../../dist/index.js';
4
+
5
+ suite('Case folding', () => {
6
+ test('Configuration', async () => {
7
+ await configure({ caseFold: 'lower' });
8
+ assert.equal(mounts.get('/')?.attributes.get('case_fold'), 'lower');
9
+ });
10
+
11
+ test('Write', () => {
12
+ fs.writeFileSync('/Test.txt', 'test');
13
+ assert(fs.existsSync('/test.txt'));
14
+ });
15
+
16
+ test('Read', () => {
17
+ assert.equal(fs.readFileSync('/TEST.txt', 'utf8'), 'test');
18
+ });
19
+ });
@@ -21,7 +21,7 @@ suite('Directories', () => {
21
21
  test('mkdir', async () => {
22
22
  await fs.promises.mkdir('/one', 0o755);
23
23
  assert(await fs.promises.exists('/one'));
24
- await assert.rejects(fs.promises.mkdir('/one', 0o755), /EEXIST/);
24
+ await assert.rejects(fs.promises.mkdir('/one', 0o755), { code: 'EEXIST' });
25
25
  });
26
26
 
27
27
  test('mkdirSync', async () => await fs.promises.mkdir('/two', 0o000));