Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import debug from 'debug';
import { createHash } from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';

const log = debug('eslint_d:config');

Expand Down Expand Up @@ -67,5 +70,9 @@ export async function removeConfig(resolver) {
* @returns {string}
*/
export function configFile(resolver) {
return `${resolver.base}/.eslint_d`;
const hash = createHash('sha256');

hash.update(path.resolve(resolver.base).replace('\\', '/'));

return path.join(os.tmpdir(), `${hash.digest('hex')}.eslint_d`);
}
24 changes: 20 additions & 4 deletions lib/config.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createHash } from 'node:crypto';
import fs from 'node:fs/promises';
import { createRequire } from 'node:module';
import os from 'node:os';
import path from 'node:path';
import { assert, sinon } from '@sinonjs/referee-sinon';
import { loadConfig, writeConfig, removeConfig } from './config.js';
import { configFile, loadConfig, writeConfig, removeConfig } from './config.js';

describe('lib/config', () => {
const resolver = {
Expand All @@ -16,7 +19,7 @@ describe('lib/config', () => {

loadConfig(resolver);

assert.calledOnceWith(fs.readFile, 'some/base/.eslint_d', 'utf8');
assert.calledOnceWith(fs.readFile, configFile(resolver), 'utf8');
});

it('returns parsed config from file content', async () => {
Expand Down Expand Up @@ -75,7 +78,7 @@ describe('lib/config', () => {
await assert.resolves(promise);
assert.calledOnceWith(
fs.writeFile,
'some/base/.eslint_d',
configFile(resolver),
'token 123 456 hash'
);
});
Expand All @@ -88,7 +91,20 @@ describe('lib/config', () => {
const promise = removeConfig(resolver);

await assert.resolves(promise);
assert.calledOnceWith(fs.unlink, 'some/base/.eslint_d');
assert.calledOnceWith(fs.unlink, configFile(resolver));
});
});

context('configFile', () => {
it('returns unique config file path inside temp dir', () => {
const hash = createHash('sha256');

hash.update(path.resolve(resolver.base).replace('\\', '/'));

assert.equals(
configFile(resolver),
path.join(os.tmpdir(), `${hash.digest('hex')}.eslint_d`)
);
});
});
});
30 changes: 23 additions & 7 deletions lib/daemon.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { normalize, resolve } from 'node:path';
import crypto from 'node:crypto';
import net from 'node:net';
import { assert, refute, match, sinon } from '@sinonjs/referee-sinon';
import { configFile } from './config.js';

/**
* @import {Resolver} from './resolver.js'
*/

describe('lib/daemon', () => {
const randome_bytes = Buffer.from('token');
Expand All @@ -15,6 +20,15 @@ describe('lib/daemon', () => {
let watcher;
let argv;

/**
* @type {Resolver}
*/
const resolverMock = {
base,
bundled: false,
require: sinon.stub()
};

function returnThis() {
// @ts-ignore
return this;
Expand Down Expand Up @@ -59,7 +73,7 @@ describe('lib/daemon', () => {

assert.calledOnceWith(
fs_promises.writeFile,
`${base}/.eslint_d`,
configFile(resolverMock),
`${randome_bytes.toString('hex')} 1234 ${process.pid} hash`
);
refute.called(fs.watch);
Expand All @@ -72,7 +86,9 @@ describe('lib/daemon', () => {
fs_write_promise.resolve();
await new Promise(setImmediate);

assert.calledOnceWith(fs.watch, `${base}/.eslint_d`, { persistent: false });
assert.calledOnceWith(fs.watch, configFile(resolverMock), {
persistent: false
});
});

it('logs error runs shutdown if config file write fails', async () => {
Expand All @@ -85,7 +101,7 @@ describe('lib/daemon', () => {
refute.called(fs.watch);
assert.calledOnceWith(console.error, 'eslint_d: Error: Oh noes!');
assert.calledOnceWith(server.close, match.func);
assert.calledOnceWith(fs_promises.unlink, `${base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolverMock));
assert.calledOnceWith(process.exit, 0);
});

Expand All @@ -102,7 +118,7 @@ describe('lib/daemon', () => {

assert.calledOnce(watcher.close);
assert.calledOnceWith(server.close, match.func);
assert.calledOnceWith(fs_promises.unlink, `${base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolverMock));
assert.calledOnceWith(process.exit, 0);
});

Expand Down Expand Up @@ -147,7 +163,7 @@ describe('lib/daemon', () => {
await new Promise(setImmediate);

assert.calledOnceWith(server.close, match.func);
assert.calledOnceWith(fs_promises.unlink, `${base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolverMock));
assert.calledOnceWith(process.exit, 0);
server.close.resetHistory();
fs_promises.unlink['resetHistory']();
Expand Down Expand Up @@ -188,7 +204,7 @@ describe('lib/daemon', () => {
await new Promise(setImmediate);

assert.calledOnceWith(server.close, match.func);
assert.calledOnceWith(fs_promises.unlink, `${base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolverMock));
assert.calledOnceWith(process.exit, 0);
});

Expand All @@ -214,7 +230,7 @@ describe('lib/daemon', () => {
await new Promise(setImmediate);

assert.calledOnceWith(server.close, match.func);
assert.calledOnceWith(fs_promises.unlink, `${base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolverMock));
assert.calledOnceWith(process.exit, 0);
});
});
Expand Down
13 changes: 9 additions & 4 deletions lib/forwarder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import net from 'node:net';
import child_process from 'node:child_process';
import EventEmitter from 'node:events';
import { PassThrough } from 'node:stream';
import path from 'node:path';
import fs from 'node:fs';
import fs_promises from 'node:fs/promises';
import supportsColor from 'supports-color';
import { assert, refute, match, sinon } from '@sinonjs/referee-sinon';
import { configFile } from './config.js';
import { forwardToDaemon, isAlive } from './forwarder.js';
import { createResolver } from './resolver.js';
import { LINT_COMMAND } from './commands.js';
Expand Down Expand Up @@ -191,6 +193,7 @@ describe('lib/forwarder', () => {
});

it('launch daemon on first connection refused', async () => {
const resolverConfigFile = configFile(resolver);
const chunks = ['response ', 'from daemon'];
sinon.replace(
socket,
Expand All @@ -207,12 +210,12 @@ describe('lib/forwarder', () => {
await new Promise(setImmediate);

// removeConfig
assert.calledOnceWith(fs_promises.unlink, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, resolverConfigFile);
await new Promise(setImmediate);
// spawn
assert.calledOnce(child_process.spawn);
// watch config
watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(resolverConfigFile));
await new Promise(setImmediate);
// read config
read_file_promise.resolve(JSON.stringify(config));
Expand Down Expand Up @@ -346,6 +349,8 @@ describe('lib/forwarder', () => {
});

it('logs error on second ECONNREFUSED', async () => {
const resolverConfigFile = configFile(resolver);

sinon.replace(fs_promises, 'unlink', sinon.fake.resolves());

forwardToDaemon(resolver, config);
Expand All @@ -355,12 +360,12 @@ describe('lib/forwarder', () => {
await new Promise(setImmediate);

// removeConfig
assert.calledOnceWith(fs_promises.unlink, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, resolverConfigFile);
await new Promise(setImmediate);
// spawn
assert.calledOnce(child_process.spawn);
// watch config
watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(resolverConfigFile));
await new Promise(setImmediate);
// read config
read_file_promise.resolve(JSON.stringify(config));
Expand Down
16 changes: 9 additions & 7 deletions lib/launcher.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import child_process from 'node:child_process';
import debug from 'debug';
import { loadConfig, removeConfig } from './config.js';
import { configFile, loadConfig, removeConfig } from './config.js';
import { forwardCommandToDaemon } from './forwarder.js';
import { SHUTDOWN_COMMAND } from './commands.js';

Expand Down Expand Up @@ -51,7 +52,7 @@ export async function launchDaemon(resolver, hash, is_debug_mode) {
daemon_process.unref();
}

await waitForConfig(resolver.base);
await waitForConfig(configFile(resolver));
config = await loadConfig(resolver);
} catch (err) {
error = err;
Expand All @@ -72,7 +73,7 @@ export async function stopDaemon(resolver, config) {
log('Stopping daemon %o', config);
try {
await Promise.all([
waitForConfig(resolver.base),
waitForConfig(configFile(resolver)),
platformAwareStopDaemon(config)
]);
} catch (err) {
Expand Down Expand Up @@ -127,16 +128,17 @@ function getIDLE(ppid) {
}

/**
* @param {string} base
* @param {string} resolverConfigFile
* @returns {Promise<void>}
*/
function waitForConfig(base) {
function waitForConfig(resolverConfigFile) {
return new Promise((resolve, reject) => {
const configBasename = path.basename(resolverConfigFile);
let timeout = null;
const watcher = fs
.watch(base)
.watch(path.dirname(resolverConfigFile))
.on('change', (type, filename) => {
if (type === 'rename' && filename === '.eslint_d') {
if (type === 'rename' && filename === configBasename) {
clearTimeout(timeout);
watcher.close();
resolve();
Expand Down
31 changes: 18 additions & 13 deletions lib/launcher.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import path from 'node:path';
import fs from 'node:fs';
import fs_promises from 'node:fs/promises';
import os from 'node:os';
import net from 'node:net';
import child_process from 'node:child_process';
import EventEmitter from 'node:events';
import { assert, refute, match, sinon } from '@sinonjs/referee-sinon';
import { configFile } from './config.js';
import { createResolver } from './resolver.js';
import { launchDaemon, stopDaemon } from './launcher.js';
import { strictEqual } from 'node:assert';
Expand Down Expand Up @@ -169,7 +171,7 @@ describe('lib/launcher', () => {
assert.calledWith(process.on, 'SIGINT', match.func);
process.on['callback']();

assert.calledOnceWith(fs_promises.unlink, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolver));
});

it('fails if idle is not a number', () => {
Expand All @@ -186,14 +188,15 @@ describe('lib/launcher', () => {

it('waits for config file to appear and resolves', async () => {
const promise = launchDaemon(resolver, 'hash', false);
const resolverConfigFile = configFile(resolver);

assert.calledOnceWith(fs.watch, resolver.base);
assert.calledOnceWith(fs.watch, path.dirname(resolverConfigFile));

watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(resolverConfigFile));

assert.calledOnceWith(watcher.close);
await Promise.resolve();
assert.calledOnceWith(fs_promises.readFile, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.readFile, configFile(resolver));

read_file_promise.resolve('token 123 456 hash');

Expand Down Expand Up @@ -223,7 +226,7 @@ describe('lib/launcher', () => {
const promise = launchDaemon(resolver, 'hash', false);
const error = new Error('read error');

watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(configFile(resolver)));
read_file_promise.reject(error);

await assert.resolves(promise, null);
Expand Down Expand Up @@ -275,12 +278,13 @@ describe('lib/launcher', () => {

it('waits for the config to be removed and resolves', async () => {
const promise = stopDaemon(resolver, config);
const resolverConfigFile = configFile(resolver);

await new Promise(setImmediate);

assert.calledOnceWith(fs.watch, resolver.base);
assert.calledOnceWith(fs.watch, path.dirname(resolverConfigFile));

watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(resolverConfigFile));

assert.calledOnceWith(watcher.close);
await assert.resolves(promise, undefined);
Expand All @@ -301,13 +305,13 @@ describe('lib/launcher', () => {
console.error,
'eslint_d: Error: kill error - removing config'
);
assert.calledOnceWith(fs_promises.unlink, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolver));
});

it('does not wait for the config file removal', async () => {
const promise = stopDaemon(resolver, config);

assert.calledOnceWith(fs.watch, resolver.base);
assert.calledOnceWith(fs.watch, path.dirname(configFile(resolver)));
await assert.resolves(promise, undefined);
});
});
Expand Down Expand Up @@ -366,16 +370,17 @@ describe('lib/launcher', () => {

it('waits for the config to be removed and resolves', async () => {
const promise = stopDaemon(resolver, config);
const resolverConfigFile = configFile(resolver);

await new Promise((resolve) => {
server.on('connection', (socket) => {
socket.on('data', () => {}).on('end', () => resolve(''));
});
});

assert.calledOnceWith(fs.watch, resolver.base);
assert.calledOnceWith(fs.watch, path.dirname(resolverConfigFile));

watcher.emit('change', 'rename', '.eslint_d');
watcher.emit('change', 'rename', path.basename(resolverConfigFile));

assert.calledOnceWith(watcher.close);
await assert.resolves(promise, undefined);
Expand All @@ -396,13 +401,13 @@ describe('lib/launcher', () => {
console.error,
'eslint_d: Error: kill error - removing config'
);
assert.calledOnceWith(fs_promises.unlink, `${resolver.base}/.eslint_d`);
assert.calledOnceWith(fs_promises.unlink, configFile(resolver));
});

it('does not wait for the config file removal', async () => {
const promise = stopDaemon(resolver, config);

assert.calledOnceWith(fs.watch, resolver.base);
assert.calledOnceWith(fs.watch, path.dirname(configFile(resolver)));
await assert.resolves(promise, undefined);
});
});
Expand Down
Loading