@zenfs/core 2.2.1 → 2.2.3

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.
@@ -486,6 +486,7 @@ let SuperBlock = (() => {
486
486
  * @returns the new metadata block
487
487
  */
488
488
  rotateMetadata() {
489
+ this.used_bytes += this.used_bytes % BigInt(4);
489
490
  const metadata = new MetadataBlock(this.buffer, Number(this.used_bytes));
490
491
  metadata.previous_offset = this.metadata_offset;
491
492
  this.metadata = metadata;
@@ -28,3 +28,9 @@ export interface CredentialsInit extends Partial<Credentials> {
28
28
  * @category Internals
29
29
  */
30
30
  export declare function createCredentials(source: CredentialsInit): Credentials;
31
+ /**
32
+ * Returns true if the credentials can be used for an operation that requires root privileges.
33
+ * @internal
34
+ * @category Internals
35
+ */
36
+ export declare function credentialsAllowRoot(cred?: Credentials): boolean;
@@ -11,3 +11,13 @@ export function createCredentials(source) {
11
11
  ...source,
12
12
  };
13
13
  }
14
+ /**
15
+ * Returns true if the credentials can be used for an operation that requires root privileges.
16
+ * @internal
17
+ * @category Internals
18
+ */
19
+ export function credentialsAllowRoot(cred) {
20
+ if (!cred)
21
+ return false;
22
+ return !cred.uid || !cred.gid || !cred.euid || !cred.egid || cred.groups.some(gid => !gid);
23
+ }
package/dist/polyfills.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /* node:coverage disable */
2
- var _a, _b, _c;
2
+ /* eslint-disable @typescript-eslint/unbound-method */
3
+ var _a, _b, _c, _d;
4
+ var _e;
3
5
  import { warn } from 'kerium/log';
4
- // eslint-disable-next-line @typescript-eslint/unbound-method
5
6
  (_a = Promise.withResolvers) !== null && _a !== void 0 ? _a : (Promise.withResolvers = (warn('Using a polyfill of Promise.withResolvers'),
6
7
  function () {
7
8
  let _resolve,
@@ -17,4 +18,12 @@ import { warn } from 'kerium/log';
17
18
  (_b = Symbol['dispose']) !== null && _b !== void 0 ? _b : (Symbol['dispose'] = (warn('Using a polyfill of Symbol.dispose'), Symbol('Symbol.dispose')));
18
19
  // @ts-expect-error 2540
19
20
  (_c = Symbol['asyncDispose']) !== null && _c !== void 0 ? _c : (Symbol['asyncDispose'] = (warn('Using a polyfill of Symbol.asyncDispose'), Symbol('Symbol.asyncDispose')));
21
+ function randomUUID() {
22
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
23
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
24
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
25
+ const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('');
26
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
27
+ }
28
+ (_d = (_e = globalThis.crypto).randomUUID) !== null && _d !== void 0 ? _d : (_e.randomUUID = (warn('Using a polyfill of crypto.randomUUID'), randomUUID));
20
29
  /* node:coverage enable */
package/dist/utils.d.ts CHANGED
@@ -48,8 +48,7 @@ export declare function normalizePath(p: fs.PathLike, noResolve?: boolean): stri
48
48
  */
49
49
  export declare function normalizeOptions(options: fs.WriteFileOptions | (fs.EncodingOption & {
50
50
  flag?: fs.OpenMode;
51
- }) | undefined, encoding: (BufferEncoding | null) | undefined, flag: string, mode?: number): {
52
- encoding?: BufferEncoding | null;
51
+ }) | undefined, encoding: (BufferEncoding | null) | undefined, flag: string, mode?: number): fs.ObjectEncodingOptions & {
53
52
  flag: string;
54
53
  mode: number;
55
54
  };
package/dist/vfs/dir.d.ts CHANGED
@@ -2,11 +2,12 @@ import type { Dir as _Dir, Dirent as _Dirent } from 'node:fs';
2
2
  import type { V_Context } from '../context.js';
3
3
  import type { InodeLike } from '../internal/inode.js';
4
4
  import type { Callback } from '../utils.js';
5
- export declare class Dirent implements _Dirent {
5
+ export declare class Dirent<Name extends string | Buffer = string> implements _Dirent<Name> {
6
6
  path: string;
7
7
  protected stats: InodeLike;
8
- get name(): string;
9
- constructor(path: string, stats: InodeLike);
8
+ protected readonly encoding?: (BufferEncoding | "buffer" | null) | undefined;
9
+ get name(): Name;
10
+ constructor(path: string, stats: InodeLike, encoding?: (BufferEncoding | "buffer" | null) | undefined);
10
11
  get parentPath(): string;
11
12
  isFile(): boolean;
12
13
  isDirectory(): boolean;
package/dist/vfs/dir.js CHANGED
@@ -5,11 +5,13 @@ import { readdir } from './promises.js';
5
5
  import { readdirSync } from './sync.js';
6
6
  export class Dirent {
7
7
  get name() {
8
- return basename(this.path);
8
+ const name = Buffer.from(basename(this.path));
9
+ return (this.encoding == 'buffer' ? name : name.toString(this.encoding));
9
10
  }
10
- constructor(path, stats) {
11
+ constructor(path, stats, encoding) {
11
12
  this.path = path;
12
13
  this.stats = stats;
14
+ this.encoding = encoding;
13
15
  }
14
16
  get parentPath() {
15
17
  return this.path;
@@ -1,3 +1,4 @@
1
+ import type { Abortable } from 'node:events';
1
2
  import type * as fs from 'node:fs';
2
3
  import type * as promises from 'node:fs/promises';
3
4
  import type { Stream } from 'node:stream';
@@ -6,7 +7,7 @@ import type { V_Context } from '../context.js';
6
7
  import type { FileSystem, StreamOptions } from '../internal/filesystem.js';
7
8
  import type { InodeLike } from '../internal/inode.js';
8
9
  import type { Interface as ReadlineInterface } from '../readline.js';
9
- import type { FileContents, NullEnc, ReaddirOptions, ReaddirOptsI, ReaddirOptsU } from './types.js';
10
+ import type { FileContents, ReaddirOptions } from './types.js';
10
11
  import { Buffer } from 'buffer';
11
12
  import '../polyfills.js';
12
13
  import { Dir, Dirent } from './dir.js';
@@ -114,16 +115,13 @@ export declare class FileHandle implements promises.FileHandle {
114
115
  read<T extends NodeJS.ArrayBufferView>(buffer: T, offset?: number, length?: number, position?: number | null): Promise<promises.FileReadResult<T>>;
115
116
  read<T extends NodeJS.ArrayBufferView = Buffer>(buffer: T, options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>;
116
117
  read<T extends NodeJS.ArrayBufferView = Buffer>(options?: promises.FileReadOptions<T>): Promise<promises.FileReadResult<T>>;
117
- /**
118
- * Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
119
- * The `FileHandle` must have been opened for reading.
120
- * @param _options An object that may contain an optional flag.
121
- * If a flag is not provided, it defaults to `'r'`.
122
- */
123
- readFile(_options?: {
124
- flag?: fs.OpenMode;
125
- }): Promise<Buffer>;
126
- readFile(_options: (fs.ObjectEncodingOptions & promises.FlagAndOpenMode) | BufferEncoding): Promise<string>;
118
+ readFile(options?: ({
119
+ encoding?: null;
120
+ } & Abortable) | null): Promise<Buffer>;
121
+ readFile(options: ({
122
+ encoding: BufferEncoding;
123
+ } & Abortable) | BufferEncoding): Promise<string>;
124
+ readFile(_options?: (fs.ObjectEncodingOptions & Abortable) | BufferEncoding | null): Promise<string | Buffer>;
127
125
  /**
128
126
  * Read file data using a `ReadableStream`.
129
127
  * The handle will not be closed automatically.
@@ -254,15 +252,15 @@ export declare function open(this: V_Context, path: fs.PathLike, flag?: fs.OpenM
254
252
  * @option flag Defaults to `'r'`.
255
253
  * @returns the file data
256
254
  */
257
- export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, options?: {
255
+ export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, options?: ({
258
256
  encoding?: null;
259
257
  flag?: fs.OpenMode;
260
- } | null): Promise<Buffer>;
261
- export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, options: {
258
+ } & Abortable) | null): Promise<Buffer>;
259
+ export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, options: ({
262
260
  encoding: BufferEncoding;
263
261
  flag?: fs.OpenMode;
264
- } | BufferEncoding): Promise<string>;
265
- export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, options?: (fs.ObjectEncodingOptions & {
262
+ } & Abortable) | BufferEncoding): Promise<string>;
263
+ export declare function readFile(this: V_Context, path: fs.PathLike | promises.FileHandle, _options?: (fs.ObjectEncodingOptions & Abortable & {
266
264
  flag?: fs.OpenMode;
267
265
  }) | BufferEncoding | null): Promise<string | Buffer>;
268
266
  /**
@@ -309,19 +307,29 @@ export declare function mkdir(this: V_Context, path: fs.PathLike, options?: fs.M
309
307
  * @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
310
308
  * @param options The encoding (or an object specifying the encoding), used as the encoding of the result. If not provided, `'utf8'`.
311
309
  */
312
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
310
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
313
311
  withFileTypes?: false;
314
- }> | NullEnc): Promise<string[]>;
315
- export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
312
+ recursive?: boolean;
313
+ }) | BufferEncoding | null): Promise<string[]>;
314
+ export declare function readdir(this: V_Context, path: fs.PathLike, options: {
315
+ encoding: 'buffer';
316
316
  withFileTypes?: false;
317
- }): Promise<Buffer[]>;
318
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
317
+ recursive?: boolean;
318
+ } | 'buffer'): Promise<Buffer[]>;
319
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
319
320
  withFileTypes?: false;
320
- }> | NullEnc): Promise<string[] | Buffer[]>;
321
- export declare function readdir(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{
321
+ recursive?: boolean;
322
+ }) | BufferEncoding | null): Promise<string[] | Buffer[]>;
323
+ export declare function readdir(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & {
324
+ withFileTypes: true;
325
+ recursive?: boolean;
326
+ }): Promise<Dirent[]>;
327
+ export declare function readdir(this: V_Context, path: fs.PathLike, options: {
328
+ encoding: 'buffer';
322
329
  withFileTypes: true;
323
- }>): Promise<Dirent[]>;
324
- export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): Promise<string[] | Dirent[] | Buffer[]>;
330
+ recursive?: boolean;
331
+ }): Promise<Dirent<Buffer>[]>;
332
+ export declare function readdir(this: V_Context, path: fs.PathLike, options?: ReaddirOptions): Promise<string[] | Dirent<any>[] | Buffer[]>;
325
333
  export declare function link(this: V_Context, path: fs.PathLike, dest: fs.PathLike): Promise<void>;
326
334
  /**
327
335
  * `symlink`.
@@ -737,7 +737,7 @@ export async function mkdir(path, options) {
737
737
  }
738
738
  mkdir;
739
739
  export async function readdir(path, options) {
740
- options = typeof options === 'object' ? options : { encoding: options };
740
+ const opt = typeof options === 'object' && options != null ? options : { encoding: options, withFileTypes: false, recursive: false };
741
741
  path = await realpath.call(this, path);
742
742
  const { fs, path: resolved } = resolveMount(path, this);
743
743
  const $ex = { syscall: 'readdir', path };
@@ -752,7 +752,7 @@ export async function readdir(path, options) {
752
752
  const values = [];
753
753
  const addEntry = async (entry) => {
754
754
  let entryStats;
755
- if ((options === null || options === void 0 ? void 0 : options.recursive) || (options === null || options === void 0 ? void 0 : options.withFileTypes)) {
755
+ if (opt.recursive || opt.withFileTypes) {
756
756
  entryStats = await fs.stat(join(resolved, entry)).catch((e) => {
757
757
  if (e.code == 'ENOENT')
758
758
  return;
@@ -761,18 +761,18 @@ export async function readdir(path, options) {
761
761
  if (!entryStats)
762
762
  return;
763
763
  }
764
- if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
765
- values.push(new Dirent(entry, entryStats));
764
+ if (opt.withFileTypes) {
765
+ values.push(new Dirent(entry, entryStats, opt.encoding));
766
766
  }
767
- else if ((options === null || options === void 0 ? void 0 : options.encoding) == 'buffer') {
767
+ else if (opt.encoding == 'buffer') {
768
768
  values.push(Buffer.from(entry));
769
769
  }
770
770
  else {
771
771
  values.push(entry);
772
772
  }
773
- if (!(options === null || options === void 0 ? void 0 : options.recursive) || !isDirectory(entryStats))
773
+ if (!opt.recursive || !isDirectory(entryStats))
774
774
  return;
775
- for (const subEntry of await readdir.call(this, join(path, entry), options)) {
775
+ for (const subEntry of await readdir.call(this, join(path, entry), opt)) {
776
776
  if (subEntry instanceof Dirent) {
777
777
  subEntry.path = join(entry, subEntry.path);
778
778
  values.push(subEntry);
@@ -1102,7 +1102,7 @@ export async function rm(path, options) {
1102
1102
  switch (stats.mode & constants.S_IFMT) {
1103
1103
  case constants.S_IFDIR:
1104
1104
  if (options === null || options === void 0 ? void 0 : options.recursive) {
1105
- for (const entry of (await readdir.call(this, path))) {
1105
+ for (const entry of await readdir.call(this, path)) {
1106
1106
  await rm.call(this, join(path, entry), options);
1107
1107
  }
1108
1108
  }
@@ -6,6 +6,7 @@ import { defaultContext } from '../internal/contexts.js';
6
6
  import { join, resolve } from '../path.js';
7
7
  import { normalizePath } from '../utils.js';
8
8
  import { size_max } from './constants.js';
9
+ import { credentialsAllowRoot } from '../internal/credentials.js';
9
10
  /**
10
11
  * The map of mount points
11
12
  * @category Backends and Configuration
@@ -59,7 +60,7 @@ export function resolveMount(path, ctx) {
59
60
  continue;
60
61
  path = path.slice(mountPoint.length > 1 ? mountPoint.length : 0); // Resolve the path relative to the mount point
61
62
  if (path === '')
62
- path = root;
63
+ path = '/';
63
64
  const case_fold = fs.attributes.get('case_fold');
64
65
  if (case_fold === 'lower')
65
66
  path = path.toLowerCase();
@@ -90,13 +91,13 @@ export function _statfs(fs, bigint) {
90
91
  * @category Backends and Configuration
91
92
  */
92
93
  export function chroot(path) {
93
- var _a, _b, _c, _d, _e, _f, _g;
94
+ var _a, _b, _c;
94
95
  const $ = this !== null && this !== void 0 ? this : defaultContext;
95
- if (((_a = $.credentials) === null || _a === void 0 ? void 0 : _a.uid) !== 0 && ((_b = $.credentials) === null || _b === void 0 ? void 0 : _b.gid) !== 0 && ((_c = $.credentials) === null || _c === void 0 ? void 0 : _c.euid) !== 0 && ((_d = $.credentials) === null || _d === void 0 ? void 0 : _d.egid) !== 0)
96
+ if (!credentialsAllowRoot($.credentials))
96
97
  throw withErrno('EPERM', 'Can not chroot() as non-root user');
97
- (_e = $.root) !== null && _e !== void 0 ? _e : ($.root = '/');
98
+ (_a = $.root) !== null && _a !== void 0 ? _a : ($.root = '/');
98
99
  const newRoot = join($.root, path);
99
- for (const handle of (_g = (_f = $.descriptors) === null || _f === void 0 ? void 0 : _f.values()) !== null && _g !== void 0 ? _g : []) {
100
+ for (const handle of (_c = (_b = $.descriptors) === null || _b === void 0 ? void 0 : _b.values()) !== null && _c !== void 0 ? _c : []) {
100
101
  if (!handle.path.startsWith($.root))
101
102
  throw UV('EBUSY', 'chroot', handle.path);
102
103
  handle.path = handle.path.slice($.root.length);
@@ -1,6 +1,6 @@
1
1
  import type * as fs from 'node:fs';
2
2
  import type { V_Context } from '../context.js';
3
- import type { FileContents, NullEnc, ReaddirOptions, ReaddirOptsI, ReaddirOptsU } from './types.js';
3
+ import type { FileContents, ReaddirOptions } from './types.js';
4
4
  import { Buffer } from 'buffer';
5
5
  import { Dir, Dirent } from './dir.js';
6
6
  import { BigIntStats, Stats } from './stats.js';
@@ -47,7 +47,7 @@ export declare function lopenSync(this: V_Context, path: fs.PathLike, flag: stri
47
47
  */
48
48
  export declare function readFileSync(this: V_Context, path: fs.PathOrFileDescriptor, options?: {
49
49
  flag?: string;
50
- } | null): Buffer;
50
+ } | null): NonSharedBuffer;
51
51
  export declare function readFileSync(this: V_Context, path: fs.PathOrFileDescriptor, options?: (fs.EncodingOption & {
52
52
  flag?: string;
53
53
  }) | BufferEncoding | null): string;
@@ -112,19 +112,30 @@ export declare function mkdirSync(this: V_Context, path: fs.PathLike, options?:
112
112
  recursive?: false;
113
113
  }) | null): void;
114
114
  export declare function mkdirSync(this: V_Context, path: fs.PathLike, options?: fs.Mode | fs.MakeDirectoryOptions | null): string | undefined;
115
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
115
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: {
116
+ encoding: BufferEncoding | null;
116
117
  withFileTypes?: false;
117
- }> | NullEnc): string[];
118
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.BufferEncodingOption & ReaddirOptions & {
118
+ recursive?: boolean;
119
+ } | BufferEncoding | null): string[];
120
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options: {
121
+ encoding: 'buffer';
119
122
  withFileTypes?: false;
120
- }): Buffer[];
121
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsI<{
123
+ recursive?: boolean;
124
+ } | 'buffer'): Buffer[];
125
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: (fs.ObjectEncodingOptions & {
122
126
  withFileTypes?: false;
123
- }> | NullEnc): string[] | Buffer[];
124
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options: ReaddirOptsI<{
127
+ recursive?: boolean;
128
+ }) | BufferEncoding | null): string[] | Buffer[];
129
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options: fs.ObjectEncodingOptions & {
125
130
  withFileTypes: true;
126
- }>): Dirent[];
127
- export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptsU<fs.BufferEncodingOption> | NullEnc): string[] | Dirent[] | Buffer[];
131
+ recursive?: boolean;
132
+ }): Dirent[];
133
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options: {
134
+ encoding: 'buffer';
135
+ withFileTypes: true;
136
+ recursive?: boolean;
137
+ }): Dirent<Buffer>[];
138
+ export declare function readdirSync(this: V_Context, path: fs.PathLike, options?: ReaddirOptions): string[] | Dirent<any>[] | Buffer[];
128
139
  export declare function linkSync(this: V_Context, targetPath: fs.PathLike, linkPath: fs.PathLike): void;
129
140
  /**
130
141
  * Synchronous `symlink`.
package/dist/vfs/sync.js CHANGED
@@ -496,7 +496,7 @@ export function readdirSync(path, options) {
496
496
  continue;
497
497
  }
498
498
  if (options === null || options === void 0 ? void 0 : options.withFileTypes) {
499
- values.push(new Dirent(entry, entryStat));
499
+ values.push(new Dirent(entry, entryStat, options.encoding));
500
500
  }
501
501
  else if ((options === null || options === void 0 ? void 0 : options.encoding) == 'buffer') {
502
502
  values.push(Buffer.from(entry));
@@ -15,15 +15,10 @@ export interface OpenOptions {
15
15
  */
16
16
  allowDirectory?: boolean;
17
17
  }
18
- export interface ReaddirOptions {
18
+ export type ReaddirOptions = {
19
19
  withFileTypes?: boolean;
20
20
  recursive?: boolean;
21
- }
21
+ encoding?: BufferEncoding | 'buffer' | null;
22
+ } | BufferEncoding | 'buffer' | null;
22
23
  /** Helper union @hidden */
23
24
  export type GlobOptionsU = fs.GlobOptionsWithFileTypes | fs.GlobOptionsWithoutFileTypes | fs.GlobOptions;
24
- /** Helper with union @hidden */
25
- export type ReaddirOptsU<T> = (ReaddirOptions & (fs.ObjectEncodingOptions | T)) | NullEnc;
26
- /** Helper with intersection @hidden */
27
- export type ReaddirOptsI<T> = ReaddirOptions & fs.ObjectEncodingOptions & T;
28
- /** @hidden */
29
- export type NullEnc = BufferEncoding | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -71,7 +71,7 @@
71
71
  "buffer": "^6.0.3",
72
72
  "eventemitter3": "^5.0.1",
73
73
  "kerium": "^1.3.4",
74
- "memium": "^0.2.0",
74
+ "memium": "^0.2.1",
75
75
  "readable-stream": "^4.5.2",
76
76
  "utilium": "^2.3.3"
77
77
  },
@@ -3,6 +3,7 @@ import { suite, test } from 'node:test';
3
3
  import { canary } from 'utilium';
4
4
  import { bindContext } from '../../dist/context.js';
5
5
  import * as fs from '../../dist/vfs/index.js';
6
+ import { configure, InMemory } from '../../dist/index.js';
6
7
 
7
8
  fs.mkdirSync('/ctx');
8
9
  const { fs: ctx } = bindContext({ root: '/ctx' });
@@ -58,4 +59,17 @@ suite('Context', () => {
58
59
  await watcher.return!();
59
60
  await promise;
60
61
  });
62
+
63
+ test('Path resolution of / with context root and mount point being the same', async () => {
64
+ // @zenfs/core#226
65
+ await configure({
66
+ mounts: { '/bananas': InMemory },
67
+ });
68
+
69
+ const bananas = bindContext({ root: '/bananas' });
70
+
71
+ fs.writeFileSync('/bananas/yellow', 'true');
72
+
73
+ assert.deepEqual(bananas.fs.readdirSync('/'), ['yellow']);
74
+ });
61
75
  });