Repository: streamich/memfs (ts)

Issue:
realpathSync('/') returns invalid result (edge case bug)
This is easy to reproduce:

```js
> memfs = require('memfs');
> memfs.vol.fromJSON({'./a':'a'}, '/');
> memfs.vol.realpathSync('/')
''
```
Notice that the output is an empty string, which isn't a valid path.  This bug is caused by getPath() in node.ts:
```ts
  getPath(): string {
    return this.steps.join(SEP);
  }
```
The problem is the edge case of join when `this.steps` is `['']`, i.e., a list with a single empty string.

The following is what I would expect as output in this case:
```js
> require('fs').realpathSync('/')
'/'
```

Perhaps a good fix would be:

```ts
  getPath(): string {
    const path = this.steps.join(SEP);
    return path ? path : "/";
  }
```


Pull request description:
fix: return `/` when calling `realpathSync` with `/`
-  https://github.com/streamich/memfs/issues/863

Resolves #863

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23cfdf79..a5e9bdc0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## [3.4.10](https://github.com/streamich/memfs/compare/v3.4.9...v3.4.10) (2022-11-03)
+
+
+### Bug Fixes
+
+* support calling `chmod` on a directory ([#870](https://github.com/streamich/memfs/issues/870)) ([7c5999c](https://github.com/streamich/memfs/commit/7c5999c88e58527861557fdfddc4069aedba3eef)), closes [#558](https://github.com/streamich/memfs/issues/558)
+
 ## [3.4.9](https://github.com/streamich/memfs/compare/v3.4.8...v3.4.9) (2022-10-29)
 
 
diff --git a/package.json b/package.json
index c8f3f969..403ea430 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "memfs",
-  "version": "3.4.9",
+  "version": "3.4.10",
   "description": "In-memory file-system with Node's fs API.",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
diff --git a/src/volume.ts b/src/volume.ts
index 18cbf780..cc098c67 100644
--- a/src/volume.ts
+++ b/src/volume.ts
@@ -1493,7 +1493,7 @@ export class Volume {
     const realLink = this.getResolvedLink(steps);
     if (!realLink) throw createError(ENOENT, 'realpath', filename);
 
-    return strToEncoding(realLink.getPath(), encoding);
+    return strToEncoding(realLink.getPath() || '/', encoding);
   }
 
   realpathSync(path: PathLike, options?: IRealpathOptions | string): TDataOut {
@@ -2066,7 +2066,7 @@ export class Volume {
   }
 
   private chmodBase(filename: string, modeNum: number) {
-    const fd = this.openSync(filename, 'r+');
+    const fd = this.openSync(filename, 'r');
     try {
       this.fchmodBase(fd, modeNum);
     } finally {


Test patch:
diff --git a/src/__tests__/volume.test.ts b/src/__tests__/volume.test.ts
index 7f393a31..317dfda0 100644
--- a/src/__tests__/volume.test.ts
+++ b/src/__tests__/volume.test.ts
@@ -1111,6 +1111,24 @@ describe('volume', () => {
         }, 1);
       });
     });
+    describe('.chmodSync(path, mode)', () => {
+      it('works with directories', () => {
+        const vol = new Volume();
+        vol.mkdirSync('/dir');
+        vol.chmodSync('/dir', 0o666);
+        expect(vol.statSync('/dir').mode.toString(8)).toBe('40666');
+        vol.chmodSync('/dir', 0o777);
+        expect(vol.statSync('/dir').mode.toString(8)).toBe('40777');
+      });
+      it('works with files', () => {
+        const vol = new Volume();
+        vol.writeFileSync('/file', 'contents');
+        vol.chmodSync('/file', 0o666);
+        expect(vol.statSync('/file').mode.toString(8)).toBe('100666');
+        vol.chmodSync('/file', 0o777);
+        expect(vol.statSync('/file').mode.toString(8)).toBe('100777');
+      });
+    });
     describe('.promises', () => {
       it('Have a promises property', () => {
         const vol = new Volume();
diff --git a/src/__tests__/volume/realpathSync.test.ts b/src/__tests__/volume/realpathSync.test.ts
index 58d77468..95465c15 100644
--- a/src/__tests__/volume/realpathSync.test.ts
+++ b/src/__tests__/volume/realpathSync.test.ts
@@ -1,6 +1,6 @@
 import { create } from '../util';
 
-describe('.realpath(...)', () => {
+describe('.realpathSync(...)', () => {
   it('works with symlinks, #463', () => {
     const vol = create({});
     vol.mkdirSync('/a');
@@ -11,4 +11,8 @@ describe('.realpath(...)', () => {
     const path = vol.realpathSync('/a/b/index.js');
     expect(path).toBe('/c/index.js');
   });
+  it('returns the root correctly', () => {
+    const vol = create({ './a': 'a' });
+    expect(vol.realpathSync('/')).toBe('/');
+  });
 });