Bump Typescript Version (app) (#1401)

As part of implementing a dependency upgrade (nuqs) we noticed that it requires Typescript 5, but we are on 4.

We should update this dependency so we don't get too outdated, by bumping this we can unblock other upgrades.

The biggest painpoint in this upgrade was the way that Typescript handles ESM in v4->v5 broke ts-jest ESM handling logic. I have mitigated this problem but using a lower version of `flat` which supports CJS, and mocking `ky` package since it's not actually needed for tests.

Fixes HDX-2900
This commit is contained in:
Brandon Pereira 2025-11-24 14:19:42 -07:00 committed by GitHub
parent 4503d39494
commit 7405d18308
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1929 additions and 428 deletions

View file

@ -0,0 +1,6 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/app": patch
---
bump typescript version

View file

@ -75,7 +75,7 @@
"ts-node": "^10.8.1",
"tsc-alias": "^1.8.8",
"tsconfig-paths": "^4.2.0",
"typescript": "5.9.3"
"typescript": "^5.9.3"
},
"scripts": {
"start": "node ./build/index.js",

View file

@ -1,28 +1,25 @@
module.exports = {
preset: 'ts-jest/presets/js-with-ts',
testEnvironment: 'jsdom',
globalSetup: '<rootDir>/global-setup.js',
roots: ['<rootDir>/src'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
const { createJsWithTsPreset } = require('ts-jest');
const tsJestTransformCfg = createJsWithTsPreset({
tsconfig: {
jsx: 'react-jsx',
},
});
/** @type {import("jest").Config} **/
module.exports = {
...tsJestTransformCfg,
testEnvironment: 'jsdom',
roots: ['<rootDir>/src'],
globalSetup: '<rootDir>/global-setup.js',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
transformIgnorePatterns: ['/node_modules/(?!(ky|ky-universal|flat))'],
transformIgnorePatterns: ['/node_modules/'],
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1',
'^ky-universal$': '<rootDir>/src/__mocks__/ky-universal.ts',
'^ky$': '<rootDir>/src/__mocks__/ky-universal.ts',
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.tsx'],
// Prettier 3 not supported yet
// See: https://stackoverflow.com/a/76818962
prettierPath: null,
globals: {
// This is necessary because next.js forces { "jsx": "preserve" }, but ts-jest appears to require { "jsx": "react-jsx" }
'ts-jest': {
tsconfig: {
jsx: 'react-jsx',
},
},
},
};

View file

@ -56,7 +56,7 @@
"crypto-randomuuid": "^1.0.0",
"date-fns": "^2.28.0",
"date-fns-tz": "^2.0.0",
"flat": "^6.0.1",
"flat": "^5.0.2",
"fuse.js": "^6.6.2",
"http-proxy-middleware": "^3.0.5",
"immer": "^9.0.21",
@ -66,13 +66,13 @@
"lodash": "^4.17.21",
"ms": "^2.1.3",
"next": "^14.2.32",
"next-query-params": "^4.1.0",
"next-query-params": "^4.3.1",
"next-runtime-env": "1",
"next-seo": "^4.28.1",
"nextra": "2.0.1",
"nextra-theme-docs": "^2.0.2",
"numbro": "^2.4.0",
"nuqs": "^1.17.0",
"nuqs": "1.17.0",
"object-hash": "^3.0.0",
"react": "18.3.1",
"react-copy-to-clipboard": "^5.1.0",
@ -121,6 +121,7 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.5.2",
"@types/crypto-js": "^4",
"@types/flat": "^5.0.5",
"@types/identity-obj-proxy": "^3",
"@types/intercom-web": "^2.8.18",
"@types/jest": "^29.5.14",
@ -137,8 +138,8 @@
"@types/sqlstring": "^2.3.2",
"eslint-config-next": "^14.2.29",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^29.7.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"knip": "^5.33.2",
"msw": "^2.3.0",
"msw-storybook-addon": "^2.0.2",
@ -149,8 +150,8 @@
"stylelint": "^16.6.1",
"stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^5.0.0",
"ts-jest": "^29.2.6",
"typescript": "^4.9.5"
"ts-jest": "^29.4.5",
"typescript": "^5.9.3"
},
"nx": {
"targets": {

View file

@ -33,7 +33,8 @@ import '@xyflow/react/dist/style.css';
// Polyfill crypto.randomUUID for non-HTTPS environments
if (typeof crypto !== 'undefined' && !crypto.randomUUID) {
crypto.randomUUID = randomUUID;
crypto.randomUUID =
randomUUID as () => `${string}-${string}-${string}-${string}-${string}`;
}
enableMapSet();

View file

@ -0,0 +1,17 @@
// Mock for ky-universal ESM module
const ky = jest.fn(() => ({
get: jest.fn(() => Promise.resolve({})),
post: jest.fn(() => Promise.resolve({})),
put: jest.fn(() => Promise.resolve({})),
patch: jest.fn(() => Promise.resolve({})),
delete: jest.fn(() => Promise.resolve({})),
head: jest.fn(() => Promise.resolve({})),
}));
// Mock the create and extend methods to return the ky mock itself
// @ts-expect-error this exists
ky.create = jest.fn(() => ky);
// @ts-expect-error this exists
ky.extend = jest.fn(() => ky);
module.exports = ky;

View file

@ -80,7 +80,7 @@ describe('Service Dashboard', () => {
it('should throw an error if an empty list of fields is passed', () => {
expect(() => {
makeCoalescedFieldsAccessQuery([], false);
}).toThrowError(
}).toThrow(
'Empty fields array passed while trying to build a coalesced field access query',
);
});
@ -88,7 +88,7 @@ describe('Service Dashboard', () => {
it('should throw an error if more than 100 fields are passed', () => {
expect(() => {
makeCoalescedFieldsAccessQuery(Array(101).fill('field'), false);
}).toThrowError(
}).toThrow(
'Too many fields (101) passed while trying to build a coalesced field access query. Maximum allowed is 100',
);
});

View file

@ -69,6 +69,7 @@ describe.skip('useTimeQuery tests', () => {
jest.resetAllMocks();
locationMock = new LocationMock('https://www.hyperdx.io/');
testRouter = new TestRouter(locationMock);
// @ts-ignore - this is a mock
window.location = locationMock;
(useRouter as jest.Mock).mockReturnValue(testRouter);
@ -77,6 +78,7 @@ describe.skip('useTimeQuery tests', () => {
});
afterAll(() => {
// @ts-ignore - this is a mock
window.location = savedLocation;
});

View file

@ -531,6 +531,6 @@ describe('useQueryHistory', () => {
act(() => {
setQueryHistory(' '); // empty after trim
});
expect(mockSetItem).not.toBeCalled();
expect(mockSetItem).not.toHaveBeenCalled();
});
});

View file

@ -18,6 +18,11 @@ describe('RawLogTable', () => {
isLoading: false,
error: null,
} as any);
// Suppress console errors for expected errors in tests. Keeps the test output clean.
jest.spyOn(console, 'error').mockImplementation(() => {
/* noop */
});
});
it('should render no results message when no results found', async () => {

View file

@ -338,7 +338,7 @@ describe('FilterGroup', () => {
// Check that zebra is marked as excluded
const excludedCheckbox = options[1];
expect(excludedCheckbox).toHaveAttribute('data-indeterminate', 'true');
expect(labels[1]).toHaveStyle({ color: expect.stringContaining('red') });
expect(labels[1]).toHaveStyle({ color: 'var(--color-text-danger)' });
});
it('should handle more than MAX_FILTER_GROUP_ITEMS', async () => {

View file

@ -217,7 +217,7 @@ export function useLocalStorage<T>(key: string, initialValue: T) {
useEffect(() => {
const handleCustomStorageChange = (event: Event) => {
if (
event instanceof CustomEvent<CustomStorageChangeDetail> &&
event instanceof CustomEvent &&
event.detail.key === key &&
event.detail.instanceId !== instanceId
) {

View file

@ -46,7 +46,7 @@
"tsc-alias": "^1.8.8",
"tsconfig-paths": "^4.2.0",
"tsup": "^8.4.0",
"typescript": "^4.9.5"
"typescript": "^5.9.3"
},
"scripts": {
"dev": "nodemon --watch ./src --ext ts --exec \"yarn dev:build\"",

2260
yarn.lock

File diff suppressed because it is too large Load diff