Repository: mantoni/eslint_d.js (js)

Issue:
Can't use eslint_d in multiple projects
I have eslint_d working in 1 project. When trying to use it in another one, I get:

```
$ eslint_d src/app/Content.jsx 
node:net:1297
    validatePort(port);
    ^

RangeError [ERR_SOCKET_BAD_PORT]: Port should be >= 0 and < 65536. Received type number (NaN).
    at lookupAndConnect (node:net:1297:5)
    at Socket.connect (node:net:1254:5)
    at Object.connect (node:net:237:17)
    at forwardToDaemon (file:///nix/store/727cpwihd52lvn098wpqhar4r322pgwq-eslint_d-14.0.0/lib/node_modules/eslint_d/lib/forwarder.js:18:22)
    at file:///nix/store/727cpwihd52lvn098wpqhar4r322pgwq-eslint_d-14.0.0/lib/node_modules/eslint_d/bin/eslint_d.js:64:13 {
  code: 'ERR_SOCKET_BAD_PORT'
}

Node.js v20.14.0
```

I'm using eslint_d v14.

Pull request:
Fix hash when changing directories
The filenames where resolved relative to `process.cwd()` which means they're missing when calling `eslint_d` from a sub-directory. This would unnecessarily restart the server.

Fixes #296

Patch:
diff --git a/bin/eslint_d.js b/bin/eslint_d.js
index e967a8a..7355fd9 100755
--- a/bin/eslint_d.js
+++ b/bin/eslint_d.js
@@ -25,7 +25,10 @@ const command = process.argv[2];
   }
 
   // eslint-disable-next-line prefer-const
-  let [config, hash] = await Promise.all([loadConfig(resolver), filesHash()]);
+  let [config, hash] = await Promise.all([
+    loadConfig(resolver),
+    filesHash(resolver.base)
+  ]);
   switch (command) {
     case 'start':
       if (config) {
diff --git a/lib/hash.js b/lib/hash.js
index 2dc215b..1d1e009 100644
--- a/lib/hash.js
+++ b/lib/hash.js
@@ -11,12 +11,13 @@ const files = [
 ];
 
 /**
+ * @param {string} base
  * @returns {Promise<string>}
  */
-export async function filesHash() {
-  const cwd = process.cwd();
+export async function filesHash(base) {
+  const project = path.resolve(base, '../..');
   const results = await Promise.allSettled(
-    files.map((file) => fs.readFile(path.join(cwd, file)))
+    files.map((file) => fs.readFile(path.join(project, file)))
   );
   const hash = crypto.createHash('md5');
   for (const result of results) {


Test patch:
diff --git a/lib/hash.test.js b/lib/hash.test.js
index d09bfc2..8ea1688 100644
--- a/lib/hash.test.js
+++ b/lib/hash.test.js
@@ -4,24 +4,26 @@ import { filesHash } from './hash.js';
 
 describe('lib/hash', () => {
   context('filesHash', () => {
+    const base = 'some/dir/node_modules/eslint';
+    const cwd = process.cwd();
+
     it('reads files from cwd', () => {
-      sinon.replace(process, 'cwd', sinon.fake.returns('some/dir'));
       sinon.replace(fs, 'readFile', sinon.fake.returns(sinon.promise()));
 
-      filesHash();
+      filesHash(base);
 
       assert.callCount(fs.readFile, 5);
-      assert.calledWith(fs.readFile, 'some/dir/package.json');
-      assert.calledWith(fs.readFile, 'some/dir/package-lock.json');
-      assert.calledWith(fs.readFile, 'some/dir/npm-shrinkwrap.json');
-      assert.calledWith(fs.readFile, 'some/dir/yarn.lock');
-      assert.calledWith(fs.readFile, 'some/dir/pnpm-lock.yaml');
+      assert.calledWith(fs.readFile, `${cwd}/some/dir/package.json`);
+      assert.calledWith(fs.readFile, `${cwd}/some/dir/package-lock.json`);
+      assert.calledWith(fs.readFile, `${cwd}/some/dir/npm-shrinkwrap.json`);
+      assert.calledWith(fs.readFile, `${cwd}/some/dir/yarn.lock`);
+      assert.calledWith(fs.readFile, `${cwd}/some/dir/pnpm-lock.yaml`);
     });
 
     it('resolves with hash of all files', async () => {
       sinon.replace(fs, 'readFile', sinon.fake.resolves('file content'));
 
-      const promise = filesHash();
+      const promise = filesHash(base);
 
       await assert.resolves(promise, 'Jd1/4sH1IanXsNIo1eM+Jw==');
     });
@@ -29,7 +31,7 @@ describe('lib/hash', () => {
     it('resolves with null hash if none of the files can be resolved', async () => {
       sinon.replace(fs, 'readFile', sinon.fake.rejects());
 
-      const promise = filesHash();
+      const promise = filesHash(base);
 
       await assert.resolves(promise, '1B2M2Y8AsgTpgAmY7PhCfg==');
     });