Skip to content

Commit

Permalink
Add support for --debug
Browse files Browse the repository at this point in the history
  • Loading branch information
mantoni committed Oct 19, 2024
1 parent 4af421a commit 5e99a06
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 65 deletions.
14 changes: 11 additions & 3 deletions bin/eslint_d.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
#!/usr/bin/env node

import debug from 'debug';
import { loadConfig, removeConfig } from '../lib/config.js';
import { createResolver } from '../lib/resolver.js';
import { forwardToDaemon, isAlive } from '../lib/forwarder.js';
import { launchDaemon, stopDaemon } from '../lib/launcher.js';
import { filesHash } from '../lib/hash.js';

const is_debug_mode = process.argv.includes('--debug');
if (is_debug_mode) {
debug.enable(
process.env.DEBUG || 'eslint_d:*,eslint:*,-eslint:code-path,eslintrc:*'
);
}

const command = process.argv[2];

(async () => {
Expand Down Expand Up @@ -40,7 +48,7 @@ const command = process.argv[2];
await removeConfig(resolver);
}

await launchDaemon(resolver, hash);
await launchDaemon(resolver, hash, is_debug_mode);

return;
case 'stop':
Expand All @@ -54,7 +62,7 @@ const command = process.argv[2];
if (config) {
await stopDaemon(resolver, config);
}
await launchDaemon(resolver, hash);
await launchDaemon(resolver, hash, is_debug_mode);
return;
case 'status':
(await import('../lib/status.js')).status(resolver, config);
Expand All @@ -65,7 +73,7 @@ const command = process.argv[2];
config = null;
}
if (!config) {
config = await launchDaemon(resolver, hash);
config = await launchDaemon(resolver, hash, false);
if (!config) {
return;
}
Expand Down
15 changes: 13 additions & 2 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import debug from 'debug';
import fs from 'node:fs/promises';

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

/**
* @import { Resolver} from './resolver.js'
*/
Expand All @@ -18,6 +21,8 @@ import fs from 'node:fs/promises';
*/
export async function loadConfig(resolver) {
const filename = configFile(resolver);
log('Reading config from %s', filename);

try {
let raw = await fs.readFile(filename, 'utf8');
if (!raw) {
Expand All @@ -27,6 +32,7 @@ export async function loadConfig(resolver) {
const [token, port, pid, hash] = raw.split(' ');
return { token, port: Number(port), pid: Number(pid), hash };
} catch {
log('Config not found');
return null;
}
}
Expand All @@ -36,16 +42,21 @@ export async function loadConfig(resolver) {
* @param {Config} config
*/
export async function writeConfig(resolver, config) {
const filename = configFile(resolver);
log('Writing config to %s', filename);

const { token, port, pid, hash } = config;
await fs.writeFile(configFile(resolver), `${token} ${port} ${pid} ${hash}`);
await fs.writeFile(filename, `${token} ${port} ${pid} ${hash}`);
}

/**
* @param {Resolver} resolver
*/
export async function removeConfig(resolver) {
const filename = configFile(resolver);
log('Removing config at %s', filename);
try {
await fs.unlink(configFile(resolver));
await fs.unlink(filename);
} catch {
// ignore
}
Expand Down
39 changes: 32 additions & 7 deletions lib/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import net from 'node:net';
import path from 'node:path';
import crypto from 'node:crypto';
import { createRequire } from 'node:module';
import debug from 'debug';
import { configFile, writeConfig, removeConfig } from './config.js';
import { createService } from './service.js';

Expand All @@ -18,6 +19,8 @@ const resolver = {
};
const hash = argv[3];

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

const token = crypto.randomBytes(8).toString('hex');

let ppid_interval = null;
Expand All @@ -27,29 +30,37 @@ let watcher = null;

let service = createService(resolver, token, shutdown);
if (idle) {
log('Using idle %d', idle);
service = watchConnection(service, idle * 60000);
}
const server = net.createServer({ allowHalfOpen: true }, service);

const server = net.createServer({ allowHalfOpen: true }, service);
server.listen(0, '127.0.0.1', () => {
const port = server.address()?.['port'];
log('Listening on port %d', port);
writeConfig(resolver, { token, port, pid: process.pid, hash })
.then(() => {
watchConfig();
})
.catch((err) => {
log('Error writing config: %o', err);
console.error(`eslint_d: ${err}`);
shutdown();
});
});

process.on('SIGTERM', shutdown);
process.on('SIGTERM', () => {
log('Shutting down due to SIGTERM');
shutdown();
});

if (ppid) {
log('Using ppid %d', ppid);
ppid_interval = setInterval(() => {
try {
process.kill(ppid, 0);
} catch {
log('Shutting down due to parent exit');
shutdown();
}
}, 1000);
Expand All @@ -61,16 +72,24 @@ fs.readFile(project_pkg, 'utf8', (err, data) => {
try {
const { name } = JSON.parse(data);
if (name) {
process.title = `eslint_d - ${name === 'eslint_d' ? 'global' : name}`;
setProcessTitle(`eslint_d - ${name === 'eslint_d' ? 'global' : name}`);
return;
}
} catch {
// Fall through
}
}
process.title = `eslint_d - ${process.cwd()}`;
setProcessTitle(`eslint_d - ${process.cwd()}`);
});

/**
* @param {string} title
*/
function setProcessTitle(title) {
log('Setting process title to "%s"', process.title);
process.title = title;
}

function shutdown() {
if (idle_timeout) {
clearTimeout(idle_timeout);
Expand All @@ -84,23 +103,29 @@ function shutdown() {
watcher.close();
watcher = null;
}
server.close(() =>
server.close((err) => {
if (err) {
log('Error closing server: %o', err);
}
removeConfig(resolver).then(() => {
log('Shutdown complete');
// eslint-disable-next-line n/no-process-exit
process.exit(0);
})
);
});
});
}

function watchConfig() {
watcher = fs
.watch(configFile(resolver), { persistent: false })
.on('change', (type) => {
if (type === 'rename') {
log('Shutting down due to config removal');
shutdown();
}
})
.on('error', (err) => {
log('Error watching config: %o', err);
console.error(`eslint_d: ${err}`);
shutdown();
});
Expand Down
37 changes: 30 additions & 7 deletions lib/forwarder.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import debug from 'debug';
import net from 'node:net';
import supportsColor from 'supports-color';
import { removeConfig } from '../lib/config.js';
Expand All @@ -12,6 +13,8 @@ import { launchDaemon } from './launcher.js';
const EXIT_TOKEN_REGEXP = new RegExp(/EXIT([0-9]{3})/);
const EXIT_TOKEN_LENGTH = 7;

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

/**
* @param {Config | null} config
* @returns {Promise<boolean>}
Expand All @@ -29,15 +32,16 @@ export function isAlive(config) {
*/
export async function forwardCommandToDaemon(config, command) {
if (!config) {
return Promise.reject(new Error('config not found'));
return Promise.reject(new Error('Config not found'));
}

const socket = await connectToDaemon(config);
log('Request: %o', { token: config.token, command });
socket.write(JSON.stringify([config.token, command]));
socket.end();

return new Promise((resolve, reject) => {
socket.on('end', () => resolve()).on('error', (err) => reject(err));
socket.on('end', resolve).on('error', reject);
});
}

Expand Down Expand Up @@ -79,15 +83,28 @@ export async function forwardToDaemon(resolver, config) {
return;
}

const color_level = stdout ? stdout.level : 0;
const cwd = process.cwd();
const DEBUG = process.env.DEBUG || '';
log('Request: %o', {
token: config.token,
command: LINT_COMMAND,
color_level,
cwd,
args: eslint_args,
DEBUG
});
const args = [
config.token,
LINT_COMMAND,
stdout ? stdout.level : 0,
process.cwd(),
eslint_args
color_level,
cwd,
eslint_args,
DEBUG
];
socket.write(JSON.stringify(args));
if (text) {
log('Request stdin: %d', text.length);
socket.write('\n');
socket.write(text);
}
Expand Down Expand Up @@ -116,14 +133,17 @@ export async function forwardToDaemon(resolver, config) {
}

if (!fix_to_stdout) {
process.exitCode = Number(match[1]);
const exit_code = Number(match[1]);
log('Exit %d', exit_code);
process.exitCode = exit_code;
return;
}

try {
const { output } = JSON.parse(flushMessage())[0];
process.stdout.write(output || text);
// Exit code from eslint is deliberately ignored in this case
log('Exit %d', 0);
process.exitCode = 0;
} catch (err) {
process.stdout.write(text);
Expand All @@ -144,13 +164,15 @@ export async function forwardToDaemon(resolver, config) {
try {
return await connectToDaemon(config);
} catch (/** @type {any} */ err) {
log('Failed to connect %o', err);

if (err['code'] === 'ECONNREFUSED' && retry) {
await removeConfig(resolver);
// @ts-expect-error we check for nullability in the line below
// eslint-disable-next-line require-atomic-updates
config = await launchDaemon(resolver, config.hash);
if (!config) {
throw new Error('unable to start daemon');
throw new Error('Unable to start daemon');
}
return createSocket(false);
}
Expand Down Expand Up @@ -182,6 +204,7 @@ export async function forwardToDaemon(resolver, config) {
* @returns {Promise<net.Socket>}
*/
function connectToDaemon(config) {
log('Connecting to daemon 127.0.0.1:%d', config.port);
return new Promise((resolve, reject) => {
const socket = net.connect(config.port, '127.0.0.1');
socket
Expand Down
19 changes: 17 additions & 2 deletions lib/forwarder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,22 @@ describe('lib/forwarder', () => {

assert.calledOnceWith(
socket.write,
`["token","${LINT_COMMAND}",${color_level},"the/cwd",[${JSON.stringify(process.argv0)},"eslint_d"]]`
`["token","${LINT_COMMAND}",${color_level},"the/cwd",[${JSON.stringify(process.argv0)},"eslint_d"],""]`
);
assert.calledOnce(socket.end);
});

it('writes DEBUG env to socket', async () => {
sinon.replace(process, 'cwd', sinon.fake.returns('the/cwd'));
sinon.define(process.env, 'DEBUG', 'eslint_d:*');

forwardToDaemon(resolver, config);
socket.on.getCall(0).callback(); // connect
await new Promise(setImmediate);

assert.calledOnceWith(
socket.write,
`["token","${LINT_COMMAND}",${color_level},"the/cwd",[${JSON.stringify(process.argv0)},"eslint_d"],"eslint_d:*"]`
);
assert.calledOnce(socket.end);
});
Expand Down Expand Up @@ -389,7 +404,7 @@ describe('lib/forwarder', () => {
assert.calledThrice(socket.write);
assert.calledWith(
socket.write,
`["token","${LINT_COMMAND}",${color_level},"cwd",[${JSON.stringify(process.argv0)},"eslint_d","--stdin","--fix-dry-run","--format","json","--other","--options"]]`
`["token","${LINT_COMMAND}",${color_level},"cwd",[${JSON.stringify(process.argv0)},"eslint_d","--stdin","--fix-dry-run","--format","json","--other","--options"],""]`
);
assert.calledWith(socket.write, '\n');
assert.calledWith(socket.write, 'text from stdin');
Expand Down
Loading

0 comments on commit 5e99a06

Please sign in to comment.