UNPKG

eslint_d

Version:

Speed up eslint to accelerate your development workflow

156 lines (143 loc) 3.67 kB
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 { forwardCommandToDaemon } from './forwarder.js'; import { SHUTDOWN_COMMAND } from './commands.js'; /** * @import { Config } from './config.js' * @import { Resolver } from './resolver.js' */ const log = debug('eslint_d:launcher'); /** * @param {Resolver} resolver * @param {string} hash * @param {boolean} is_debug_mode * @returns {Promise<Config | null>} */ export async function launchDaemon(resolver, hash, is_debug_mode) { let config = null; let error; try { const ppid = getPPID(); const idle = getIDLE(ppid); const base = resolver.base; const daemon = resolver.require.resolve('./daemon.js'); log('Launching daemon %o', { daemon, ppid, idle, base, hash }); const daemon_process = child_process.spawn( process.argv0, [daemon, ppid, idle, base, hash], { detached: !is_debug_mode, stdio: is_debug_mode ? 'inherit' : 'ignore', env: Object.assign({}, process.env, { DEBUG: is_debug_mode ? process.env.DEBUG : '', DEBUG_COLORS: 1 }) } ); if (is_debug_mode) { process.on('SIGINT', () => { log('Process SIGINT'); removeConfig(resolver); }); } else { daemon_process.unref(); } await waitForConfig(resolver.base); config = await loadConfig(resolver); } catch (err) { error = err; } if (!config) { console.error(`eslint_d: Failed to start daemon – ${error || 'No config'}`); // eslint-disable-next-line require-atomic-updates process.exitCode = 1; } return config; } /** * @param {Resolver} resolver * @param {Config} config */ export async function stopDaemon(resolver, config) { log('Stopping daemon %o', config); try { await Promise.all([ waitForConfig(resolver.base), platformAwareStopDaemon(config) ]); } catch (err) { console.error(`eslint_d: ${err} - removing config`); await removeConfig(resolver); } } /** * @param {Config} config * @returns {Promise<void>} */ function platformAwareStopDaemon(config) { if (os.platform() === 'win32') { return forwardCommandToDaemon(config, SHUTDOWN_COMMAND); } process.kill(config.pid, 'SIGTERM'); return Promise.resolve(); } /** * @returns {string} */ function getPPID() { const env = process.env.ESLINT_D_PPID; if (!env) { return '0'; } if (env === 'auto') { return String(process.ppid); } if (String(Number(env)) !== env) { throw new Error('ESLINT_D_PPID must be a number or "auto"'); } return env; } /** * @param {string} ppid * @returns {string} */ function getIDLE(ppid) { const idle = process.env.ESLINT_D_IDLE; if (!idle) { return Number(ppid) ? '0' : '15'; } if (String(Number(idle)) !== idle) { throw new Error('ESLINT_D_IDLE must be a number'); } return idle; } /** * @param {string} base * @returns {Promise<void>} */ function waitForConfig(base) { return new Promise((resolve, reject) => { let timeout = null; const watcher = fs .watch(base) .on('change', (type, filename) => { if (type === 'rename' && filename === '.eslint_d') { clearTimeout(timeout); watcher.close(); resolve(); } }) .on('error', (err) => { clearTimeout(timeout); reject(err); }); timeout = setTimeout(() => { watcher.close(); timeout = null; reject(new Error('Timed out waiting for config')); }, 2000); }); }