feat: add ESLint with error-handling rules
Add ESLint v9.35+ with typescript-eslint recommended config and
error-handling rules: preserve-caught-error (enforces { cause } when
re-throwing), no-unused-vars with caughtErrors:all, and
eslint-plugin-no-catch-all (warns on catch blocks that don't rethrow).
Fix existing violations: add error cause to container-runtime rethrow,
prefix unused vars with underscore, remove unused imports.
https://claude.ai/code/session_01JPjzhBp9PR5LtfLWVDrYrH
This commit is contained in:
32
eslint.config.js
Normal file
32
eslint.config.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import globals from 'globals'
|
||||||
|
import pluginJs from '@eslint/js'
|
||||||
|
import tseslint from 'typescript-eslint'
|
||||||
|
import noCatchAll from 'eslint-plugin-no-catch-all'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ ignores: ['node_modules/', 'dist/', 'container/', 'groups/'] },
|
||||||
|
{ files: ['src/**/*.{js,ts}'] },
|
||||||
|
{ languageOptions: { globals: globals.node } },
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
plugins: { 'no-catch-all': noCatchAll },
|
||||||
|
rules: {
|
||||||
|
'preserve-caught-error': ['error', { requireCatchParameter: true }],
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
args: 'all',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
caughtErrors: 'all',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
destructuredArrayIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-catch-all/no-catch-all': 'warn',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
1376
package-lock.json
generated
1376
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,8 @@
|
|||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"setup": "tsx setup/index.ts",
|
"setup": "tsx setup/index.ts",
|
||||||
"auth": "tsx src/whatsapp-auth.ts",
|
"auth": "tsx src/whatsapp-auth.ts",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"lint:fix": "eslint src/ --fix",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest"
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
@@ -27,13 +29,18 @@
|
|||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.35.0",
|
||||||
"@types/better-sqlite3": "^7.6.12",
|
"@types/better-sqlite3": "^7.6.12",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
|
"eslint": "^9.35.0",
|
||||||
|
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"tsx": "^4.19.0",
|
"tsx": "^4.19.0",
|
||||||
"typescript": "^5.7.0",
|
"typescript": "^5.7.0",
|
||||||
|
"typescript-eslint": "^8.35.0",
|
||||||
"vitest": "^4.0.18"
|
"vitest": "^4.0.18"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
registerChannel,
|
registerChannel,
|
||||||
|
|||||||
@@ -698,7 +698,7 @@ export function writeGroupsSnapshot(
|
|||||||
groupFolder: string,
|
groupFolder: string,
|
||||||
isMain: boolean,
|
isMain: boolean,
|
||||||
groups: AvailableGroup[],
|
groups: AvailableGroup[],
|
||||||
registeredJids: Set<string>,
|
_registeredJids: Set<string>,
|
||||||
): void {
|
): void {
|
||||||
const groupIpcDir = resolveGroupIpcPath(groupFolder);
|
const groupIpcDir = resolveGroupIpcPath(groupFolder);
|
||||||
fs.mkdirSync(groupIpcDir, { recursive: true });
|
fs.mkdirSync(groupIpcDir, { recursive: true });
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function ensureContainerRuntimeRunning(): void {
|
|||||||
console.error(
|
console.error(
|
||||||
'╚════════════════════════════════════════════════════════════════╝\n',
|
'╚════════════════════════════════════════════════════════════════╝\n',
|
||||||
);
|
);
|
||||||
throw new Error('Container runtime is required but failed to start');
|
throw new Error('Container runtime is required but failed to start', { cause: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('GroupQueue', () => {
|
|||||||
let concurrentCount = 0;
|
let concurrentCount = 0;
|
||||||
let maxConcurrent = 0;
|
let maxConcurrent = 0;
|
||||||
|
|
||||||
const processMessages = vi.fn(async (groupJid: string) => {
|
const processMessages = vi.fn(async (_groupJid: string) => {
|
||||||
concurrentCount++;
|
concurrentCount++;
|
||||||
maxConcurrent = Math.max(maxConcurrent, concurrentCount);
|
maxConcurrent = Math.max(maxConcurrent, concurrentCount);
|
||||||
// Simulate async work
|
// Simulate async work
|
||||||
@@ -69,7 +69,7 @@ describe('GroupQueue', () => {
|
|||||||
let maxActive = 0;
|
let maxActive = 0;
|
||||||
const completionCallbacks: Array<() => void> = [];
|
const completionCallbacks: Array<() => void> = [];
|
||||||
|
|
||||||
const processMessages = vi.fn(async (groupJid: string) => {
|
const processMessages = vi.fn(async (_groupJid: string) => {
|
||||||
activeCount++;
|
activeCount++;
|
||||||
maxActive = Math.max(maxActive, activeCount);
|
maxActive = Math.max(maxActive, activeCount);
|
||||||
await new Promise<void>((resolve) => completionCallbacks.push(resolve));
|
await new Promise<void>((resolve) => completionCallbacks.push(resolve));
|
||||||
@@ -104,7 +104,7 @@ describe('GroupQueue', () => {
|
|||||||
const executionOrder: string[] = [];
|
const executionOrder: string[] = [];
|
||||||
let resolveFirst: () => void;
|
let resolveFirst: () => void;
|
||||||
|
|
||||||
const processMessages = vi.fn(async (groupJid: string) => {
|
const processMessages = vi.fn(async (_groupJid: string) => {
|
||||||
if (executionOrder.length === 0) {
|
if (executionOrder.length === 0) {
|
||||||
// First call: block until we release it
|
// First call: block until we release it
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ export class GroupQueue {
|
|||||||
// via idle timeout or container timeout. The --rm flag cleans them up on exit.
|
// via idle timeout or container timeout. The --rm flag cleans them up on exit.
|
||||||
// This prevents WhatsApp reconnection restarts from killing working agents.
|
// This prevents WhatsApp reconnection restarts from killing working agents.
|
||||||
const activeContainers: string[] = [];
|
const activeContainers: string[] = [];
|
||||||
for (const [jid, state] of this.groups) {
|
for (const [_jid, state] of this.groups) {
|
||||||
if (state.process && !state.process.killed && state.containerName) {
|
if (state.process && !state.process.killed && state.containerName) {
|
||||||
activeContainers.push(state.containerName);
|
activeContainers.push(state.containerName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
getAllTasks,
|
getAllTasks,
|
||||||
getMessagesSince,
|
getMessagesSince,
|
||||||
getNewMessages,
|
getNewMessages,
|
||||||
getRegisteredGroup,
|
|
||||||
getRouterState,
|
getRouterState,
|
||||||
initDatabase,
|
initDatabase,
|
||||||
setRegisteredGroup,
|
setRegisteredGroup,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('remote-control', () => {
|
|||||||
let readFileSyncSpy: ReturnType<typeof vi.spyOn>;
|
let readFileSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let writeFileSyncSpy: ReturnType<typeof vi.spyOn>;
|
let writeFileSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let unlinkSyncSpy: ReturnType<typeof vi.spyOn>;
|
let unlinkSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let mkdirSyncSpy: ReturnType<typeof vi.spyOn>;
|
let _mkdirSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let openSyncSpy: ReturnType<typeof vi.spyOn>;
|
let openSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
let closeSyncSpy: ReturnType<typeof vi.spyOn>;
|
let closeSyncSpy: ReturnType<typeof vi.spyOn>;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ describe('remote-control', () => {
|
|||||||
stdoutFileContent = '';
|
stdoutFileContent = '';
|
||||||
|
|
||||||
// Default fs mocks
|
// Default fs mocks
|
||||||
mkdirSyncSpy = vi
|
_mkdirSyncSpy = vi
|
||||||
.spyOn(fs, 'mkdirSync')
|
.spyOn(fs, 'mkdirSync')
|
||||||
.mockImplementation(() => undefined as any);
|
.mockImplementation(() => undefined as any);
|
||||||
writeFileSyncSpy = vi
|
writeFileSyncSpy = vi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
|
||||||
import { _initTestDatabase, getAllChats, storeChatMetadata } from './db.js';
|
import { _initTestDatabase, storeChatMetadata } from './db.js';
|
||||||
import { getAvailableGroups, _setRegisteredGroups } from './index.js';
|
import { getAvailableGroups, _setRegisteredGroups } from './index.js';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isSenderAllowed,
|
isSenderAllowed,
|
||||||
|
|||||||
Reference in New Issue
Block a user