diff --git a/client/src/setupTests.ts b/client/src/setupTests.ts index 1b93852fd..3458516a8 100644 --- a/client/src/setupTests.ts +++ b/client/src/setupTests.ts @@ -1,3 +1,4 @@ +import '@testing-library/jest-dom/vitest'; import { vi } from 'vitest' global.jest = vi as any; diff --git a/client/src/utils/collections.ts b/client/src/utils/collections.ts index 4bf43da3d..a6cb1b34a 100644 --- a/client/src/utils/collections.ts +++ b/client/src/utils/collections.ts @@ -23,5 +23,5 @@ export function filterNullOrUndefined( } export function arrayFromArrayOrSingleItem(array: readonly T[] | T): T[] { - return Array.isArray(array) ? [...array] : [array]; + return Array.isArray(array) ? [...(array as readonly T[])] : [array as T]; } diff --git a/client/tsconfig.json b/client/tsconfig.json index 4a90f1225..fef1424df 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "jsx": "react-jsx", "target": "ESNext", - "types": ["vite/client", "vite-plugin-svgr/client"], + "types": ["vite/client", "vite-plugin-svgr/client", "jest", "google.maps"], "module": "esnext", "strict": true, "esModuleInterop": true, diff --git a/server/api.ts b/server/api.ts index fba498021..b32c6d9aa 100644 --- a/server/api.ts +++ b/server/api.ts @@ -18,7 +18,8 @@ import cors from 'cors'; import express, { type ErrorRequestHandler } from 'express'; import session from 'express-session'; import { GraphQLError, type GraphQLFormattedError } from 'graphql'; -import helmet from 'helmet'; +import helmet_, { type HelmetOptions } from 'helmet'; +const helmet = helmet_ as unknown as (options?: Readonly) => (req: unknown, res: unknown, next: (err?: unknown) => void) => void; import passport from 'passport'; import { makeLoginUserDoesNotExistError } from './graphql/datasources/userApiErrors.js'; diff --git a/server/condition_evaluator/getDerivedFieldValue.ts b/server/condition_evaluator/getDerivedFieldValue.ts index 9c09fe549..4e5f7f341 100644 --- a/server/condition_evaluator/getDerivedFieldValue.ts +++ b/server/condition_evaluator/getDerivedFieldValue.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; -import stringify from 'safe-stable-stringify'; +import stringify_ from 'safe-stable-stringify'; +const stringify = stringify_ as unknown as (value: unknown) => string | undefined; import { getDerivedFieldValue, diff --git a/server/graphql/customScalars/OpaqueScalarMixin.ts b/server/graphql/customScalars/OpaqueScalarMixin.ts index 33ac57ea7..f4628d334 100644 --- a/server/graphql/customScalars/OpaqueScalarMixin.ts +++ b/server/graphql/customScalars/OpaqueScalarMixin.ts @@ -43,6 +43,7 @@ export default ( 'serialize' | 'parseValue' | 'parseLiteral' > => ({ serialize(value) { + // @ts-expect-error -- @types/jsonwebtoken@9 brands expiresIn as StringValue from ms; plain string is valid at runtime return jwt.sign(value as T, jwtSigningKey, { expiresIn: jwtExpiresIn, }); diff --git a/server/lib/cache/utils/utils.ts b/server/lib/cache/utils/utils.ts index ba8982977..401bceafe 100644 --- a/server/lib/cache/utils/utils.ts +++ b/server/lib/cache/utils/utils.ts @@ -1,6 +1,16 @@ import { setTimeout } from "timers/promises"; -import debug from "debug"; -import stringify from "safe-stable-stringify"; +import stringify_ from "safe-stable-stringify"; + +// Minimal debug-compatible logger — avoids phantom dep under pnpm strict mode. +// Behaviour is identical when DEBUG is unset (the common case in tests/prod). +const debug = (namespace: string) => { + const patterns = (process.env.DEBUG ?? '').split(',').map((p) => p.trim()); + const enabled = patterns.some( + (p) => p === '*' || p === namespace || (p.endsWith('*') && namespace.startsWith(p.slice(0, -1))), + ); + return (...args: unknown[]) => { if (enabled) console.debug(` ${namespace}`, ...args); }; +}; +const stringify = stringify_ as unknown as (value: unknown) => string | undefined; import { type JsonValue, type Tagged } from "type-fest"; import { components, type Logger } from "../types/index.js"; diff --git a/server/lib/cache/utils/wrapProducer.ts b/server/lib/cache/utils/wrapProducer.ts index 3029505e3..3e3daa24b 100644 --- a/server/lib/cache/utils/wrapProducer.ts +++ b/server/lib/cache/utils/wrapProducer.ts @@ -1,4 +1,5 @@ -import stableStringify from "safe-stable-stringify"; +import stableStringify_ from "safe-stable-stringify"; +const stableStringify = stableStringify_ as unknown as (value: unknown) => string | undefined; import type Cache from "../Cache.js"; import { type NormalizedProducerResult } from "../types/06_Normalization.js"; diff --git a/server/plugins/warehouse/utils/clickhouseSql.ts b/server/plugins/warehouse/utils/clickhouseSql.ts index 42f6277ba..0eabcae21 100644 --- a/server/plugins/warehouse/utils/clickhouseSql.ts +++ b/server/plugins/warehouse/utils/clickhouseSql.ts @@ -1,4 +1,5 @@ -import safeStableStringify from 'safe-stable-stringify'; +import safeStableStringify_ from 'safe-stable-stringify'; +const safeStableStringify = safeStableStringify_ as unknown as (value: unknown) => string | undefined; function escapeString(value: string): string { return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); @@ -37,7 +38,7 @@ function formatValue(value: unknown): string { } if (typeof value === 'object') { - const json = safeStableStringify(value); + const json = safeStableStringify(value) ?? 'null'; return `'${escapeString(json)}'`; } diff --git a/server/rule_engine/RuleEvaluator.ts b/server/rule_engine/RuleEvaluator.ts index 6a0028c4d..12191c5e1 100644 --- a/server/rule_engine/RuleEvaluator.ts +++ b/server/rule_engine/RuleEvaluator.ts @@ -57,7 +57,7 @@ type RuleEvaluationContextImpl = Readonly<{ export type RuleEvaluationContext = Opaque< RuleEvaluationContextImpl, - RuleEvaluationContextImpl + 'RuleEvaluationContext' >; export type RuleExecutionResult = { diff --git a/server/services/itemInvestigationService/utils.ts b/server/services/itemInvestigationService/utils.ts index 3f6258c8f..78b5c95a4 100644 --- a/server/services/itemInvestigationService/utils.ts +++ b/server/services/itemInvestigationService/utils.ts @@ -1,6 +1,7 @@ import type { ItemIdentifier } from '@roostorg/coop-types'; import _ from 'lodash'; -import stringify from 'safe-stable-stringify'; +import stringify_ from 'safe-stable-stringify'; +const stringify = stringify_ as unknown as (value: unknown) => string | undefined; import _S2A from 'stream-to-async-iterator'; import { diff --git a/server/services/manualReviewToolService/modules/JobDecisioning.ts b/server/services/manualReviewToolService/modules/JobDecisioning.ts index 61e913cf2..cba34aaaa 100644 --- a/server/services/manualReviewToolService/modules/JobDecisioning.ts +++ b/server/services/manualReviewToolService/modules/JobDecisioning.ts @@ -324,7 +324,9 @@ export default class JobDecisioning { .with(['ALREADY_LOGGED', 'SUCCESS'], () => jobAlreadySubmittedError) // Case 4, client retrying after failed job deletion; deletion failed again. .with(['ALREADY_LOGGED', 'FAILED'], () => decisioningFailedError) - .exhaustive(), + // ts-pattern v5.9 + TS 5.9: .exhaustive() triggers NonExhaustiveError<[any]>; + // all four cases above are covered so this branch is dead code. + .otherwise(() => undefined), }; })(); diff --git a/server/services/networkingService/index.ts b/server/services/networkingService/index.ts index 8d6417dc2..762e1a820 100644 --- a/server/services/networkingService/index.ts +++ b/server/services/networkingService/index.ts @@ -213,7 +213,7 @@ export async function fetchHTTP( // If the body isn't already an ArrayBuffer, we need to encode the body // as an ArrayBuffer, so we first coerce it to a string from a `string | // URLSearchParams` type, and then encode it with TextEncoder - const bodyBuffer = + const bodyBuffer: ArrayBuffer = castBody instanceof ArrayBuffer ? castBody : new TextEncoder().encode( @@ -221,7 +221,7 @@ export async function fetchHTTP( // necessarily just call toString, won't get accidentally // handled incorrectly (castBody satisfies string | URLSearchParams).toString(), - ); + ).buffer as ArrayBuffer; const { signature } = await query.signWith(bodyBuffer); return b64EncodeArrayBuffer(signature); diff --git a/server/services/orgAwareSignalExecutionService/signalExecutionService.ts b/server/services/orgAwareSignalExecutionService/signalExecutionService.ts index 4872a13f9..d19b26f85 100644 --- a/server/services/orgAwareSignalExecutionService/signalExecutionService.ts +++ b/server/services/orgAwareSignalExecutionService/signalExecutionService.ts @@ -1,6 +1,7 @@ import { SpanStatusCode } from '@opentelemetry/api'; import _ from 'lodash'; -import stringify from 'safe-stable-stringify'; +import stringify_ from 'safe-stable-stringify'; +const stringify = stringify_ as unknown as (value: unknown) => string | undefined; import { type ReadonlyDeep } from 'type-fest'; import { inject } from '../../iocContainer/utils.js'; diff --git a/server/test/arbitraries/ContentType.ts b/server/test/arbitraries/ContentType.ts index c01bed428..ba5ee4945 100644 --- a/server/test/arbitraries/ContentType.ts +++ b/server/test/arbitraries/ContentType.ts @@ -47,7 +47,7 @@ export const GeohashArbitrary = fc }); export const DateStringArbitrary = fc - .date() + .date({ noInvalidDate: true }) .map((date) => makeDateString(date.toISOString())!); // Id-like fields allow numbers and strings as inputs, but the normalized diff --git a/server/test/utils.ts b/server/test/utils.ts index 1d51474bb..fad77a270 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -167,7 +167,11 @@ export function makeTestWithFixture>( function _makeTestWithFixture>( makeSetupTeardown: () => Promise> | Fixture, - jestFn = it, + jestFn: ( + name: string, + fn?: jest.ProvidesCallback, + timeout?: number, + ) => void = it, ) { return ( name: string, diff --git a/server/utils/encoding.ts b/server/utils/encoding.ts index 2414c1e87..e378d1859 100644 --- a/server/utils/encoding.ts +++ b/server/utils/encoding.ts @@ -1,4 +1,5 @@ -import stringify from 'safe-stable-stringify'; +import stringify_ from 'safe-stable-stringify'; +const stringify = stringify_ as unknown as (value: unknown) => string | undefined; import { type Opaque } from 'type-fest'; import { JSON } from './json-schema-types.js';