diff --git a/packages/agents/.config/eslint/deferred-lint-rules.ts b/packages/agents/.config/eslint/deferred-lint-rules.ts index 30dd0bb1..fd8da843 100644 --- a/packages/agents/.config/eslint/deferred-lint-rules.ts +++ b/packages/agents/.config/eslint/deferred-lint-rules.ts @@ -1,18 +1,13 @@ export const deferredLintRules = { - 'unicorn/no-computed-property-existence-check': 'warn', 'unicorn/no-declarations-before-early-exit': 'warn', 'unicorn/no-duplicate-loops': 'warn', 'unicorn/no-incorrect-template-string-interpolation': 'warn', - 'unicorn/no-return-array-push': 'warn', 'unicorn/no-top-level-assignment-in-function': 'warn', 'unicorn/no-unreadable-for-of-expression': 'warn', 'unicorn/no-unsafe-string-replacement': 'warn', 'unicorn/prefer-await': 'warn', - 'unicorn/prefer-includes-over-repeated-comparisons': 'warn', 'unicorn/prefer-iterator-to-array': 'warn', 'unicorn/prefer-minimal-ternary': 'warn', 'unicorn/prefer-object-iterable-methods': 'warn', - 'unicorn/prefer-set-methods': 'warn', - 'unicorn/prefer-simple-condition-first': 'warn', 'preserve-caught-error': 'warn', } as const; diff --git a/packages/foreman/.config/eslint/deferred-lint-rules.ts b/packages/foreman/.config/eslint/deferred-lint-rules.ts deleted file mode 100644 index b3940e88..00000000 --- a/packages/foreman/.config/eslint/deferred-lint-rules.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const deferredLintRules = { - 'unicorn/no-global-object-property-assignment': 'warn', - 'unicorn/no-unreadable-for-of-expression': 'warn', - 'unicorn/prefer-promise-with-resolvers': 'warn', -} as const; diff --git a/packages/foreman/.config/strict-lint.config.ts b/packages/foreman/.config/strict-lint.config.ts deleted file mode 100644 index 5f432e5f..00000000 --- a/packages/foreman/.config/strict-lint.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { StrictLintConfig } from '@williamthorsen/strict-lint'; - -import { deferredLintRules } from './eslint/deferred-lint-rules.ts'; - -const config: StrictLintConfig = { - // Keep the deferred rules as warnings; strict-lint otherwise promotes every warning to an error. - maxSeverity: deferredLintRules, -}; - -export default config; diff --git a/packages/foreman/eslint.config.ts b/packages/foreman/eslint.config.ts index 7da11d55..d492e18e 100644 --- a/packages/foreman/eslint.config.ts +++ b/packages/foreman/eslint.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from 'eslint/config'; import reactPlugin from 'eslint-plugin-react'; import baseConfig from '../../eslint.config.ts'; -import { deferredLintRules } from './.config/eslint/deferred-lint-rules.ts'; const config = defineConfig([ ...baseConfig, @@ -46,10 +45,6 @@ const config = defineConfig([ }, }, }, - { - files: ['**/*.ts', '**/*.tsx', '**/*.md/*.ts'], - rules: deferredLintRules, - }, ]); export default config; diff --git a/packages/foreman/src/hooks/__tests__/useFleetSnapshot.unit.test.ts b/packages/foreman/src/hooks/__tests__/useFleetSnapshot.unit.test.ts index 6ea4b6c0..e0daf8be 100644 --- a/packages/foreman/src/hooks/__tests__/useFleetSnapshot.unit.test.ts +++ b/packages/foreman/src/hooks/__tests__/useFleetSnapshot.unit.test.ts @@ -36,7 +36,8 @@ class FakeEventSource { } private dispatch(type: string, event: MessageEvent): void { - for (const listener of this.listeners.get(type) ?? []) { + const listeners = this.listeners.get(type) ?? []; + for (const listener of listeners) { listener(event); } } @@ -105,15 +106,8 @@ describe('useFleetSnapshot', () => { }); it('discards a fetch result that resolves after a stream frame', async () => { - let resolveFetch: (response: Response) => void = () => {}; - vi.stubGlobal( - 'fetch', - vi.fn().mockReturnValue( - new Promise((resolve) => { - resolveFetch = resolve; - }), - ), - ); + const { promise: fetchResponse, resolve: resolveFetch } = Promise.withResolvers(); + vi.stubGlobal('fetch', vi.fn().mockReturnValue(fetchResponse)); const { result } = renderHook(() => useFleetSnapshot()); act(() => { diff --git a/packages/foreman/vitest.setup.ts b/packages/foreman/vitest.setup.ts index 0338a3c1..1f10774f 100644 --- a/packages/foreman/vitest.setup.ts +++ b/packages/foreman/vitest.setup.ts @@ -11,24 +11,28 @@ afterEach(() => { cleanup(); }); -// jsdom provides neither matchMedia nor ResizeObserver; Mantine components consult both when rendering. -globalThis.matchMedia = function matchMedia(query: string): MediaQueryList { - return { - matches: false, - media: query, - onchange: null, - addEventListener(): void {}, - removeEventListener(): void {}, - addListener(): void {}, - removeListener(): void {}, - dispatchEvent(): boolean { - return false; - }, - }; -}; +const matchMediaStub: typeof globalThis.matchMedia = (query) => ({ + matches: false, + media: query, + onchange: null, + addEventListener(): void {}, + removeEventListener(): void {}, + addListener(): void {}, + removeListener(): void {}, + dispatchEvent(): boolean { + return false; + }, +}); -globalThis.ResizeObserver = class ResizeObserver { +const resizeObserverStub: typeof globalThis.ResizeObserver = class { observe(): void {} unobserve(): void {} disconnect(): void {} }; + +// jsdom provides neither matchMedia nor ResizeObserver; Mantine components consult both when rendering. +// `defineProperties` survives the `vi.unstubAllGlobals()` that two suites run in `afterEach`; a `vi.stubGlobal` stub would not. +Object.defineProperties(globalThis, { + matchMedia: { configurable: true, value: matchMediaStub, writable: true }, + ResizeObserver: { configurable: true, value: resizeObserverStub, writable: true }, +}); diff --git a/packages/kb/.config/eslint/deferred-lint-rules.ts b/packages/kb/.config/eslint/deferred-lint-rules.ts index 26936b7f..e9e84237 100644 --- a/packages/kb/.config/eslint/deferred-lint-rules.ts +++ b/packages/kb/.config/eslint/deferred-lint-rules.ts @@ -7,6 +7,5 @@ export const deferredLintRules = { 'unicorn/prefer-includes-over-repeated-comparisons': 'warn', 'unicorn/prefer-iterator-to-array': 'warn', 'unicorn/prefer-number-is-safe-integer': 'warn', - 'unicorn/prefer-simple-condition-first': 'warn', 'preserve-caught-error': 'warn', } as const; diff --git a/packages/lifecycle/.config/eslint/deferred-lint-rules.ts b/packages/lifecycle/.config/eslint/deferred-lint-rules.ts deleted file mode 100644 index 26936b7f..00000000 --- a/packages/lifecycle/.config/eslint/deferred-lint-rules.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const deferredLintRules = { - 'unicorn/no-computed-property-existence-check': 'warn', - 'unicorn/no-declarations-before-early-exit': 'warn', - 'unicorn/no-top-level-assignment-in-function': 'warn', - 'unicorn/no-unreadable-for-of-expression': 'warn', - 'unicorn/prefer-await': 'warn', - 'unicorn/prefer-includes-over-repeated-comparisons': 'warn', - 'unicorn/prefer-iterator-to-array': 'warn', - 'unicorn/prefer-number-is-safe-integer': 'warn', - 'unicorn/prefer-simple-condition-first': 'warn', - 'preserve-caught-error': 'warn', -} as const; diff --git a/packages/lifecycle/.config/strict-lint.config.ts b/packages/lifecycle/.config/strict-lint.config.ts deleted file mode 100644 index 5f432e5f..00000000 --- a/packages/lifecycle/.config/strict-lint.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { StrictLintConfig } from '@williamthorsen/strict-lint'; - -import { deferredLintRules } from './eslint/deferred-lint-rules.ts'; - -const config: StrictLintConfig = { - // Keep the deferred rules as warnings; strict-lint otherwise promotes every warning to an error. - maxSeverity: deferredLintRules, -}; - -export default config; diff --git a/packages/lifecycle/eslint.config.ts b/packages/lifecycle/eslint.config.ts index 94fc2ee7..4e634003 100644 --- a/packages/lifecycle/eslint.config.ts +++ b/packages/lifecycle/eslint.config.ts @@ -1,22 +1,6 @@ import baseConfig from '@williamthorsen/eslint-config-typescript'; import { defineConfig, globalIgnores } from 'eslint/config'; -import { deferredLintRules } from './.config/eslint/deferred-lint-rules.ts'; - -const config = defineConfig([ - ...baseConfig, - globalIgnores([ - '**/coverage/**', - '**/dist/**', - '**/local/**', - // Ignore test fixtures that are intentionally syntactically broken. - // '**/__tests__/**/fixtures/**/*malformed*/**', - // '**/__tests__/**/fixtures/**/*malformed*', - ]), - { - files: ['**/*.ts', '**/*.mts', '**/*.tsx', '**/*.md/*.ts', '**/*.js'], - rules: deferredLintRules, - }, -]); +const config = defineConfig([...baseConfig, globalIgnores(['**/coverage/**', '**/dist/**', '**/local/**'])]); export default config; diff --git a/packages/run-core/.config/eslint/deferred-lint-rules.ts b/packages/run-core/.config/eslint/deferred-lint-rules.ts deleted file mode 100644 index 9e577847..00000000 --- a/packages/run-core/.config/eslint/deferred-lint-rules.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const deferredLintRules = { - 'unicorn/prefer-await': 'warn', - 'unicorn/prefer-hoisting-branch-code': 'warn', - 'unicorn/prefer-simple-condition-first': 'warn', -} as const; diff --git a/packages/run-core/.config/strict-lint.config.ts b/packages/run-core/.config/strict-lint.config.ts deleted file mode 100644 index 5f432e5f..00000000 --- a/packages/run-core/.config/strict-lint.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { StrictLintConfig } from '@williamthorsen/strict-lint'; - -import { deferredLintRules } from './eslint/deferred-lint-rules.ts'; - -const config: StrictLintConfig = { - // Keep the deferred rules as warnings; strict-lint otherwise promotes every warning to an error. - maxSeverity: deferredLintRules, -}; - -export default config; diff --git a/packages/run-core/eslint.config.ts b/packages/run-core/eslint.config.ts index 94fc2ee7..4e634003 100644 --- a/packages/run-core/eslint.config.ts +++ b/packages/run-core/eslint.config.ts @@ -1,22 +1,6 @@ import baseConfig from '@williamthorsen/eslint-config-typescript'; import { defineConfig, globalIgnores } from 'eslint/config'; -import { deferredLintRules } from './.config/eslint/deferred-lint-rules.ts'; - -const config = defineConfig([ - ...baseConfig, - globalIgnores([ - '**/coverage/**', - '**/dist/**', - '**/local/**', - // Ignore test fixtures that are intentionally syntactically broken. - // '**/__tests__/**/fixtures/**/*malformed*/**', - // '**/__tests__/**/fixtures/**/*malformed*', - ]), - { - files: ['**/*.ts', '**/*.mts', '**/*.tsx', '**/*.md/*.ts', '**/*.js'], - rules: deferredLintRules, - }, -]); +const config = defineConfig([...baseConfig, globalIgnores(['**/coverage/**', '**/dist/**', '**/local/**'])]); export default config; diff --git a/packages/run-core/src/parsers/__tests__/run-data-parser.unit.test.ts b/packages/run-core/src/parsers/__tests__/run-data-parser.unit.test.ts index 509eb43e..29be1745 100644 --- a/packages/run-core/src/parsers/__tests__/run-data-parser.unit.test.ts +++ b/packages/run-core/src/parsers/__tests__/run-data-parser.unit.test.ts @@ -128,6 +128,16 @@ const v2Fixture = { ], }; +/** Resolves with the value the operation rejects with. */ +async function captureRejection(operation: () => Promise): Promise { + try { + await operation(); + } catch (error) { + return error; + } + throw new Error('Expected the operation to reject, but it resolved.'); +} + function mockJson(data: Record): void { mockedReadFile.mockResolvedValue(JSON.stringify(data)); } @@ -593,7 +603,7 @@ describe('parseStatusFile', () => { it('throws RunDataParseError with corrupt_json category for invalid JSON', async () => { mockedReadFile.mockResolvedValue('not json'); - const error = await parseStatusFile('/path/to/status.json').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseStatusFile('/path/to/status.json')); expect(error).toBeInstanceOf(RunDataParseError); expect(error).toBeInstanceOf(Error); @@ -606,7 +616,7 @@ describe('parseStatusFile', () => { it('throws RunDataParseError with invalid_schema category and zodIssues for schema failures', async () => { mockedReadFile.mockResolvedValue('{}'); - const error = await parseStatusFile('/path/to/status.json').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseStatusFile('/path/to/status.json')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1142,7 +1152,7 @@ describe('parseRunData', () => { '/runs/test-run/run-index.json': '{ not valid json !!!', }); - const error = await parseRunData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1156,7 +1166,7 @@ describe('parseRunData', () => { '/runs/test-run/run-index.json': JSON.stringify(invalid), }); - const error = await parseRunData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1186,7 +1196,7 @@ describe('parseRunData', () => { '/runs/test-run/run-index.json': JSON.stringify(v3Header), }); - const error = await parseRunData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1199,7 +1209,7 @@ describe('parseRunData', () => { '/runs/test-run/status.json': 'not json at all', }); - const error = await parseRunData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1212,7 +1222,7 @@ describe('parseRunData', () => { '/runs/test-run/status.json': JSON.stringify({ runId: 'only-one-field' }), }); - const error = await parseRunData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1272,7 +1282,7 @@ describe(parseRunRawData, () => { '/runs/test-run/status.json': JSON.stringify(currentFormatFixture), }); - const error = await parseRunRawData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunRawData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1284,7 +1294,7 @@ describe(parseRunRawData, () => { '/runs/test-run/run-index.json': JSON.stringify(v2Fixture), }); - const error = await parseRunRawData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunRawData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1296,7 +1306,7 @@ describe(parseRunRawData, () => { '/runs/test-run/run-index.json': '{ not valid json !!!', }); - const error = await parseRunRawData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunRawData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; @@ -1308,7 +1318,7 @@ describe(parseRunRawData, () => { '/runs/test-run/run-index.json': JSON.stringify(minimalV3Header()), }); - const error = await parseRunRawData('/runs/test-run').catch((error_: unknown) => error_); + const error = await captureRejection(() => parseRunRawData('/runs/test-run')); expect(error).toBeInstanceOf(RunDataParseError); if (!(error instanceof RunDataParseError)) return; diff --git a/packages/run-core/src/scanners/run-directory-scanner.ts b/packages/run-core/src/scanners/run-directory-scanner.ts index 150a83e3..3f49b1cc 100644 --- a/packages/run-core/src/scanners/run-directory-scanner.ts +++ b/packages/run-core/src/scanners/run-directory-scanner.ts @@ -31,15 +31,12 @@ export async function discoverRunDirectories(basePath: string): Promise