chore: Update to next 16, react 19, add react compiler (#1434)

fixes: HDX-2956

Co-authored-by: Brandon Pereira <7552738+brandon-pereira@users.noreply.github.com>
This commit is contained in:
Tom Alexander 2025-12-04 18:40:59 -05:00 committed by GitHub
parent 4b1557d957
commit 52d2798582
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 3710 additions and 5959 deletions

View file

@ -0,0 +1,7 @@
---
"@hyperdx/common-utils": minor
"@hyperdx/api": minor
"@hyperdx/app": minor
---
chore: Upgrade nextjs, react, and eslint + add react compiler

1
.gitignore vendored
View file

@ -34,6 +34,7 @@ packages/app/.pnp.js
packages/app/.vercel
packages/app/coverage
packages/app/out
packages/app/next-env.d.ts
# optional npm cache directory
**/.npm

2
.nvmrc
View file

@ -1 +1 @@
v22.16.0
22.21.1

View file

@ -35,6 +35,13 @@
"**/yarn.lock": true,
"**/yarn-*.cjs": true
},
"cSpell.words": ["micropip", "opamp", "pyimport", "pyodide"],
"cSpell.words": [
"clickhouse",
"hyperdx",
"micropip",
"opamp",
"pyimport",
"pyodide"
],
"typescript.tsdk": "node_modules/typescript/lib"
}

View file

@ -101,7 +101,7 @@ yarn run lint
## File Locations Quick Reference
- **Config**: `packages/api/src/config.ts`, `packages/app/next.config.js`, `docker-compose.dev.yml`
- **Config**: `packages/api/src/config.ts`, `packages/app/next.config.mjs`, `docker-compose.dev.yml`
- **Models**: `packages/api/src/models/`
- **API Routes**: `packages/api/src/routers/`
- **Controllers**: `packages/api/src/controllers/`

View file

@ -25,7 +25,7 @@ COPY .yarn ./.yarn
COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./tsconfig.base.json ./
COPY ./packages/common-utils ./packages/common-utils
COPY ./packages/api/jest.config.js ./packages/api/tsconfig.json ./packages/api/tsconfig.build.json ./packages/api/package.json ./packages/api/
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.js ./packages/app/mdx.d.ts ./packages/app/.eslintrc.js ./packages/app/
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat

19
nx.json
View file

@ -8,35 +8,26 @@
},
"targetDefaults": {
"build": {
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
}
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test"
]
"cacheableOperations": ["build", "lint", "test"]
}
}
},
"namedInputs": {
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.eslintrc.json"
"!{projectRoot}/eslint.config.mjs"
],
"sharedGlobals": []
}
}
}

View file

@ -9,17 +9,19 @@
"devDependencies": {
"@changesets/cli": "^2.26.2",
"@nx/workspace": "21.3.11",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"babel-plugin-react-compiler": "^1.0.0",
"concurrently": "^9.1.2",
"dotenv": "^16.4.7",
"dotenv-cli": "^8.0.0",
"dotenv-expand": "^12.0.1",
"eslint": "^8.57.0",
"eslint-config-next": "13",
"eslint": "^9.39.1",
"eslint-config-next": "16.0.7",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-n": "^16.4.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-security": "^2.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"husky": "^8.0.3",
@ -43,12 +45,12 @@
"lint-staged": {
"packages/api/src/routers/external-api/**/*.ts": [
"prettier --write --ignore-unknown",
"eslint --fix --quiet",
"eslint --flag v10_config_lookup_from_file --fix --quiet",
"sh -c 'cd packages/api && yarn run docgen && git add openapi.json'"
],
"**/*.{ts,tsx}": [
"prettier --write --ignore-unknown",
"eslint --fix --quiet"
"eslint --flag v10_config_lookup_from_file --fix --quiet"
],
"**/*.{mdx,json,yml}": [
"prettier --write --ignore-unknown"
@ -56,8 +58,8 @@
},
"packageManager": "yarn@4.5.1",
"resolutions": {
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"@types/react": "19.0.7",
"@types/react-dom": "19.0.3",
"@types/express": "4.17.21",
"@types/express-serve-static-core": "4.17.43"
}

View file

@ -1,5 +0,0 @@
keys
node_modules
archive
migrate-mongo-config.ts
jest.setup.ts

View file

@ -1,44 +0,0 @@
module.exports = {
root: true,
ignorePatterns: ['migrate-mongo-config.ts'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:n/recommended',
'plugin:prettier/recommended',
'plugin:security/recommended-legacy',
],
rules: {
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'n/no-process-exit': 'warn',
'n/no-missing-import': 'off',
'n/no-unpublished-import': [
'error',
{
allowModules: ['mongodb', 'supertest'],
},
],
'n/no-unsupported-features/es-syntax': [
'error',
{
ignores: ['modules'],
},
],
'prettier/prettier': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
};

View file

@ -0,0 +1,99 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import nodePlugin from 'eslint-plugin-n';
import securityPlugin from 'eslint-plugin-security';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
{
ignores: [
'node_modules/**',
'build/**',
'dist/**',
'coverage/**',
'src/coverage/**',
'migrations/**',
'migrate-mongo-config.ts',
'**/*.config.js',
'**/*.config.mjs',
'jest.config.js',
'jest.setup.ts',
],
},
{
files: ['src/**/*.ts', '!migrations/**'],
plugins: {
'@typescript-eslint': tseslint.plugin,
'simple-import-sort': simpleImportSort,
'prettier': prettierPlugin,
'n': nodePlugin,
'security': securityPlugin,
},
rules: {
...nodePlugin.configs.recommended.rules,
...securityPlugin.configs['recommended-legacy'].rules,
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'n/no-process-exit': 'warn',
'n/no-missing-import': 'off',
'n/no-unpublished-import': [
'error',
{
allowModules: ['mongodb', 'supertest'],
},
],
'n/no-unsupported-features/es-syntax': [
'error',
{
ignores: ['modules'],
},
],
'prettier/prettier': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
globals: {
console: 'readonly',
process: 'readonly',
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
exports: 'readonly',
Buffer: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
setImmediate: 'readonly',
clearImmediate: 'readonly',
global: 'readonly',
},
},
},
];

View file

@ -2,12 +2,12 @@ import { Db, MongoClient } from 'mongodb';
import { v4 as uuidv4 } from 'uuid';
module.exports = {
async up(db: Db, client: MongoClient) {
async up(db: Db, _client: MongoClient) {
await db
.collection('users')
.updateMany({}, { $set: { accessKey: uuidv4() } });
},
async down(db: Db, client: MongoClient) {
async down(db: Db, _client: MongoClient) {
await db.collection('users').updateMany({}, { $unset: { accessKey: '' } });
},
};

View file

@ -1,64 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'simple-import-sort',
'@typescript-eslint',
'prettier',
'react-hooks',
],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
extends: [
'next',
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
rules: {
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-function-type': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'react/display-name': 'off',
'simple-import-sort/exports': 'error',
'simple-import-sort/imports': 'error',
'react-hooks/exhaustive-deps': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
},
overrides: [
{
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
rules: {
'simple-import-sort/imports': [
'error',
{
groups: [
['^react$', '^next', '^[a-z]', '^@'],
['^@/'],
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
['^.+\\.s?css$'],
['^\\u0000'],
],
},
],
},
},
{
// Disable strict rules for E2E test files
files: ['tests/e2e/**/*.ts', 'tests/e2e/**/*.js'],
rules: {
'no-console': 'off',
'no-empty': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@next/next/no-html-link-for-pages': 'off',
},
},
],
};

View file

@ -1,6 +1,10 @@
import { join, dirname } from 'path';
// This file has been automatically migrated to valid ESM format by Storybook.
import { createRequire } from 'node:module';
import { dirname, join } from 'path';
import type { StorybookConfig } from '@storybook/nextjs';
const require = createRequire(import.meta.url);
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
@ -9,9 +13,8 @@ const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('@storybook/addon-styling-webpack'),
getAbsolutePath('@storybook/addon-docs'),
],
framework: {
name: getAbsolutePath('@storybook/nextjs'),

View file

@ -1,21 +1,20 @@
import React from 'react';
import type { Preview } from '@storybook/react';
import { NextAdapter } from 'next-query-params';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { QueryClient, QueryClientProvider } from 'react-query';
import { QueryParamProvider } from 'use-query-params';
import { NextAdapter } from 'next-query-params';
import type { Preview } from '@storybook/nextjs';
import { meHandler } from '../src/mocks/handlers';
import { ThemeWrapper } from '../src/ThemeWrapper';
import '@mantine/core/styles.css';
import '@mantine/notifications/styles.css';
import '@mantine/dates/styles.css';
import '@mantine/dropzone/styles.css';
import '../styles/globals.css';
import '../styles/app.scss';
import { meHandler } from '../src/mocks/handlers';
import { ThemeWrapper } from '../src/ThemeWrapper';
export const parameters = {
layout: 'fullscreen',
options: {
@ -61,7 +60,7 @@ const preview: Preview = {
msw: {
handlers: [meHandler],
},
backgrounds: { disable: true },
backgrounds: { disabled: true },
},
};

View file

@ -7,7 +7,7 @@ WORKDIR /app
COPY .yarn ./.yarn
COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./tsconfig.base.json ./
COPY ./packages/common-utils ./packages/common-utils
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.js ./packages/app/mdx.d.ts ./packages/app/.eslintrc.js ./packages/app/
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/
RUN yarn install --mode=skip-build && yarn cache clean
@ -51,8 +51,8 @@ ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/packages/app/next.config.js ./
# You only need to copy next.config.mjs if you are NOT using the default configuration
COPY --from=builder /app/packages/app/next.config.mjs ./
COPY --from=builder --chown=nextjs:nodejs /app/packages/app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/packages/app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules

View file

@ -0,0 +1,114 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import storybook from 'eslint-plugin-storybook';
import nextPlugin from '@next/eslint-plugin-next';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import prettierConfig from 'eslint-config-prettier';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
{
ignores: [
'playwright-report/**',
'.next/**',
'node_modules/**',
'out/**',
'build/**',
'coverage/**',
'dist/**',
'**/*.config.js',
'**/*.config.ts',
'**/*.config.cjs',
'**/*.config.mjs',
'eslint.config.mjs',
'public/__ENV.js',
],
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
'@next/next': nextPlugin,
react: reactPlugin,
'react-hooks': reactHooksPlugin,
'simple-import-sort': simpleImportSort,
},
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-function-type': 'warn',
'@typescript-eslint/no-unused-expressions': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'react/display-name': 'off',
'simple-import-sort/exports': 'error',
'simple-import-sort/imports': [
'error',
{
groups: [
['^react$', '^next', '^[a-z]', '^@'],
['^@/'],
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
['^.+\\.s?css$'],
['^\\u0000'],
],
},
],
'react-hooks/exhaustive-deps': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
globals: {
React: 'readonly',
JSX: 'readonly',
NodeJS: 'readonly',
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
console: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
fetch: 'readonly',
process: 'readonly',
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
exports: 'readonly',
Buffer: 'readonly',
},
},
},
{
files: ['tests/e2e/**/*.{ts,js}'],
rules: {
'no-console': 'off',
'no-empty': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@next/next/no-html-link-for-pages': 'off',
},
},
...storybook.configs['flat/recommended'],
];

View file

@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View file

@ -1,67 +0,0 @@
const { configureRuntimeEnv } = require('next-runtime-env/build/configure');
const { version } = require('./package.json');
configureRuntimeEnv();
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './src/nextra.config.tsx',
});
const basePath = process.env.NEXT_PUBLIC_HYPERDX_BASE_PATH;
module.exports = {
basePath: basePath,
experimental: {
instrumentationHook: true,
// External packages to prevent bundling issues with Next.js 14
// https://github.com/open-telemetry/opentelemetry-js/issues/4297#issuecomment-2285070503
serverComponentsExternalPackages: [
'@opentelemetry/instrumentation',
'@opentelemetry/sdk-node',
'@opentelemetry/auto-instrumentations-node',
'@hyperdx/node-opentelemetry',
'@hyperdx/instrumentation-sentry-node',
],
},
typescript: {
tsconfigPath: 'tsconfig.build.json',
},
// Ignore otel pkgs warnings
// https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936
webpack: (
config,
{ buildId, dev, isServer, defaultLoaders, nextRuntime, webpack },
) => {
if (isServer) {
config.ignoreWarnings = [{ module: /opentelemetry/ }];
}
return config;
},
...withNextra({
async headers() {
return [
{
source: '/(.*)?', // Matches all pages
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
],
},
];
},
// This slows down builds by 2x for some reason...
swcMinify: false,
publicRuntimeConfig: {
version,
},
productionBrowserSourceMaps: false,
...(process.env.NEXT_OUTPUT_STANDALONE === 'true'
? {
output: 'standalone',
}
: {}),
}),
};

View file

@ -0,0 +1,84 @@
import { configureRuntimeEnv } from 'next-runtime-env/build/configure.js';
import nextra from 'nextra';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Read version from package.json
const packageJson = JSON.parse(
readFileSync(join(__dirname, 'package.json'), 'utf-8')
);
const { version } = packageJson;
// Support legacy consumers of next-runtime-env that expect this value under window.__ENV
process.env.NEXT_PUBLIC_APP_VERSION = version;
configureRuntimeEnv();
const withNextra = nextra({
theme: 'nextra-theme-docs',
themeConfig: './src/nextra.config.tsx',
});
const basePath = process.env.NEXT_PUBLIC_HYPERDX_BASE_PATH;
const nextConfig = {
reactCompiler: true,
basePath: basePath,
env: {
// Ensures bundler-time replacements for client/server code that references this env var
NEXT_PUBLIC_APP_VERSION: version,
},
// External packages to prevent bundling issues (moved from experimental in Next.js 15+)
// https://github.com/open-telemetry/opentelemetry-js/issues/4297#issuecomment-2285070503
serverExternalPackages: [
'@opentelemetry/instrumentation',
'@opentelemetry/sdk-node',
'@opentelemetry/auto-instrumentations-node',
'@hyperdx/node-opentelemetry',
'@hyperdx/instrumentation-sentry-node',
],
typescript: {
tsconfigPath: 'tsconfig.build.json',
},
// NOTE: Using Webpack instead of Turbopack (Next.js 16 default)
// Reason: Turbopack has CSS module parsing issues with nested :global syntax
// used in styles/SearchPage.module.scss and other SCSS files.
// The --webpack flag is added to dev and build scripts in package.json.
// TODO: Re-evaluate when Turbopack CSS module support improves
// Ignore otel pkgs warnings
// https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
if (isServer) {
config.ignoreWarnings = [{ module: /opentelemetry/ }];
}
return config;
},
...withNextra({
async headers() {
return [
{
source: '/(.*)?', // Matches all pages
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
],
},
];
},
productionBrowserSourceMaps: false,
...(process.env.NEXT_OUTPUT_STANDALONE === 'true'
? {
output: 'standalone',
}
: {}),
}),
};
export default nextConfig;

View file

@ -7,9 +7,9 @@
"node": ">=22.16.0"
},
"scripts": {
"dev": "npx dotenv -e .env.development -- next dev",
"dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev",
"build": "next build",
"dev": "npx dotenv -e .env.development -- next dev --webpack",
"dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev --webpack",
"build": "next build --webpack",
"start": "next start",
"lint": "npx eslint --quiet . --ext .ts,.tsx",
"lint:fix": "npx eslint . --ext .ts,.tsx --fix",
@ -33,13 +33,13 @@
"@hyperdx/common-utils": "^0.8.0",
"@hyperdx/node-opentelemetry": "^0.9.0",
"@lezer/highlight": "^1.2.0",
"@mantine/core": "7.9.2",
"@mantine/dates": "^7.11.2",
"@mantine/dropzone": "^8.3.1",
"@mantine/form": "^7.11.2",
"@mantine/hooks": "7.9.2",
"@mantine/notifications": "^7.9.2",
"@mantine/spotlight": "7.9.2",
"@mantine/core": "^7.17.8",
"@mantine/dates": "^7.17.8",
"@mantine/dropzone": "^7.17.8",
"@mantine/form": "^7.17.8",
"@mantine/hooks": "^7.17.8",
"@mantine/notifications": "^7.17.8",
"@mantine/spotlight": "^7.17.8",
"@microsoft/fetch-event-source": "^2.0.1",
"@tabler/icons-react": "^3.5.0",
"@tanstack/react-query": "^5.56.2",
@ -56,6 +56,7 @@
"crypto-randomuuid": "^1.0.0",
"date-fns": "^2.28.0",
"date-fns-tz": "^2.0.0",
"dayjs": "^1.11.19",
"flat": "^5.0.2",
"fuse.js": "^6.6.2",
"http-proxy-middleware": "^3.0.5",
@ -65,7 +66,7 @@
"ky-universal": "^0.10.1",
"lodash": "^4.17.21",
"ms": "^2.1.3",
"next": "^14.2.32",
"next": "^16.0.7",
"next-query-params": "^4.3.1",
"next-runtime-env": "1",
"next-seo": "^4.28.1",
@ -74,16 +75,16 @@
"numbro": "^2.4.0",
"nuqs": "1.17.0",
"object-hash": "^3.0.0",
"react": "18.3.1",
"react": "^19.2.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "18.3.1",
"react-dom": "^19.2.1",
"react-error-boundary": "^3.1.4",
"react-grid-layout": "^1.3.4",
"react-hook-form": "^7.43.8",
"react-hook-form-mantine": "^3.1.3",
"react-hotkeys-hook": "^4.3.7",
"react-json-tree": "^0.17.0",
"react-markdown": "^8.0.4",
"react-json-tree": "^0.20.0",
"react-markdown": "^10.1.0",
"react-papaparse": "^4.4.0",
"react-query": "^3.39.3",
"react-sortable-hoc": "^2.0.0",
@ -103,19 +104,16 @@
"zod": "3.25"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.5.0",
"@chromatic-com/storybook": "^4.1.3",
"@hookform/devtools": "^4.3.1",
"@jedmao/location": "^3.0.0",
"@playwright/test": "^1.47.0",
"@storybook/addon-essentials": "^8.1.5",
"@storybook/addon-interactions": "^8.1.5",
"@storybook/addon-links": "^8.1.5",
"@storybook/addon-styling-webpack": "^1.0.0",
"@storybook/addon-themes": "^10.0.8",
"@storybook/blocks": "^8.1.5",
"@storybook/nextjs": "^8.1.5",
"@storybook/react": "^8.1.5",
"@storybook/test": "^8.1.5",
"@storybook/addon-docs": "^10.1.4",
"@storybook/addon-links": "^10.1.4",
"@storybook/addon-styling-webpack": "^3.0.0",
"@storybook/addon-themes": "^10.1.4",
"@storybook/nextjs": "^10.1.4",
"@storybook/react": "^10.1.4",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^16.3.0",
@ -129,14 +127,15 @@
"@types/ms": "^0.7.31",
"@types/object-hash": "^2.2.1",
"@types/pluralize": "^0.0.29",
"@types/react": "18.3.1",
"@types/react": "^19.2.7",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "18.3.1",
"@types/react-dom": "^19.2.3",
"@types/react-grid-layout": "^1.3.2",
"@types/react-syntax-highlighter": "^13.5.2",
"@types/react-table": "^7.7.14",
"@types/sqlstring": "^2.3.2",
"eslint-config-next": "^14.2.29",
"eslint-config-next": "^16.0.7",
"eslint-plugin-storybook": "10.1.4",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
@ -146,7 +145,7 @@
"postcss": "^8.4.38",
"postcss-preset-mantine": "^1.15.0",
"prettier": "^3.3.2",
"storybook": "^8.1.5",
"storybook": "^10.1.4",
"stylelint": "^16.6.1",
"stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^5.0.0",

View file

@ -4,7 +4,9 @@ export default function Document() {
return (
<Html lang="en">
<Head>
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script src="/__ENV.js" />
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.2/full/pyodide.js"></script>
<link
rel="stylesheet"

View file

@ -45,6 +45,10 @@ import { useUserPreferences } from '@/useUserPreferences';
import packageJson from '../package.json';
// Expose the same value Next injected at build time; fall back to package.json for dev tooling
const APP_VERSION =
process.env.NEXT_PUBLIC_APP_VERSION ?? packageJson.version ?? 'dev';
import api from './api';
import {
AppNavCloudBanner,
@ -154,7 +158,9 @@ function SearchInput({
data-testid="nav-search-input"
placeholder={placeholder}
value={value}
onChange={e => onChange(e.currentTarget.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChange(e.currentTarget.value)
}
leftSection={<i className="bi bi-search fs-8 ps-1 " />}
onKeyDown={handleKeyDown}
rightSection={
@ -880,7 +886,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
}}
>
<AppNavHelpMenu
version={packageJson.version}
version={APP_VERSION}
onAddDataClick={openInstallInstructions}
/>
<AppNavUserMenu

View file

@ -26,7 +26,7 @@ export default function AutocompleteInput({
queryHistoryType,
'data-testid': dataTestId,
}: {
inputRef: React.RefObject<HTMLTextAreaElement>;
inputRef: React.RefObject<HTMLTextAreaElement | null>;
value?: string;
onChange: (value: string) => void;
onSubmit?: () => void;

View file

@ -210,7 +210,7 @@ function DBChartExplorerPage() {
// showRelativeInterval: isLive,
});
const submitRef = useRef<() => void>();
const submitRef = useRef<(() => void) | undefined>(undefined);
const { data: sources } = useSources();
const { data: me } = api.useMe();

View file

@ -469,7 +469,9 @@ function DashboardName({
<Input
type="text"
value={editedName}
onChange={e => setEditedName(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEditedName(e.target.value)
}
placeholder="Dashboard Name"
/>
<Button ms="sm" variant="outline" type="submit" color="green">
@ -697,9 +699,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
dateRange={searchedTimeRange}
onEditClick={() => setEditedTile(chart)}
granularity={
isRefreshEnabled
? granularityOverride
: (granularity ?? undefined)
isRefreshEnabled ? granularityOverride : granularity ?? undefined
}
filters={[
{

View file

@ -375,7 +375,7 @@ function SaveSearchModal({
<ActionIcon
variant="transparent"
color="gray"
onClick={e => {
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
setTags(tags.filter(t => t !== tag));
}}

View file

@ -1,5 +1,5 @@
import React from 'react';
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
import { TooltipItem } from './HDXMultiSeriesTimeChart';

View file

@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
import { NetworkBody } from './LogSidePanelElements';

View file

@ -1310,9 +1310,14 @@ function ServicesDashboardPage() {
// Auto submit when service or source changes
useEffect(() => {
const normalizedService = service ?? '';
const appliedService = appliedConfig.service ?? '';
const normalizedSource = sourceId ?? '';
const appliedSource = appliedConfig.source ?? '';
if (
service !== appliedConfig.service ||
sourceId !== appliedConfig.source
normalizedService !== appliedService ||
(normalizedSource && normalizedSource !== appliedSource)
) {
onSubmit();
}

View file

@ -280,7 +280,7 @@ function TimelineMouseCursor({
xPerc,
setXPerc,
}: {
containerRef: RefObject<HTMLDivElement>;
containerRef: RefObject<HTMLDivElement | null>;
maxVal: number;
labelWidth: number;
height: number;

View file

@ -222,7 +222,7 @@ export const UserPreferencesModal = ({
placeholder="https:// or data:"
value={userPreferences.backgroundUrl}
leftSection={<i className="bi bi-globe" />}
onChange={e =>
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setUserPreference({
backgroundUrl: e.currentTarget.value,
})

View file

@ -0,0 +1,4 @@
// Mock for react-json-tree ESM module. We could improve this by mocking the actual module if needed.
const JSONTree = <div data-testid="json-tree">JSONTree</div>;
module.exports = JSONTree;

View file

@ -34,14 +34,18 @@ export const AlertPreviewChart = ({
thresholdType,
select,
}: AlertPreviewChartProps) => {
const resolvedSelect =
(select && select.trim().length > 0
? select
: source.defaultTableSelectExpression) ?? '';
const { data: aliasMap } = useAliasMapFromChartConfig({
select: select || '',
select: resolvedSelect,
where: where || '',
connection: source.connection,
from: source.from,
whereLanguage: whereLanguage || undefined,
});
const aliasWith = Object.entries(aliasMap ?? {}).map(([key, value]) => ({
name: key,
sql: {

View file

@ -1,5 +1,5 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/nextjs';
import { ColorSwatchInput } from './ColorSwatchInput';

View file

@ -6,7 +6,7 @@ import {
useFieldArray,
useForm,
UseFormSetValue,
UseFormWatch,
useWatch,
} from 'react-hook-form';
import { NativeSelect, NumberInput } from 'react-hook-form-mantine';
import z from 'zod';
@ -18,9 +18,11 @@ import {
DateRange,
DisplayType,
Filter,
MetricsDataType,
SavedChartConfig,
SelectList,
SourceKind,
TSource,
} from '@hyperdx/common-utils/dist/types';
import {
Accordion,
@ -68,6 +70,7 @@ import {
} from '@/utils/alerts';
import HDXMarkdownChart from '../HDXMarkdownChart';
import type { NumberFormat } from '../types';
import { AggFnSelectControlled } from './AggFnSelect';
import DBNumberChart from './DBNumberChart';
@ -106,7 +109,7 @@ const NumberFormatInputControlled = ({
name="numberFormat"
render={({ field: { onChange, value } }) => (
<NumberFormatInput
onChange={newValue => {
onChange={(newValue?: NumberFormat) => {
onChange(newValue);
onSubmit();
}}
@ -130,9 +133,9 @@ function ChartSeriesEditorComponent({
setValue,
showGroupBy,
tableName: _tableName,
watch,
parentRef,
length,
tableSource,
}: {
control: Control<any>;
databaseName: string;
@ -147,25 +150,31 @@ function ChartSeriesEditorComponent({
setValue: UseFormSetValue<any>;
showGroupBy: boolean;
tableName: string;
watch: UseFormWatch<any>;
length: number;
tableSource?: TSource;
}) {
const aggFn = watch(`${namePrefix}aggFn`);
const aggConditionLanguage = watch(
`${namePrefix}aggConditionLanguage`,
'lucene',
);
const aggFn = useWatch({ control, name: `${namePrefix}aggFn` });
const aggConditionLanguage = useWatch({
control,
name: `${namePrefix}aggConditionLanguage`,
defaultValue: 'lucene',
});
const metricType = watch(`${namePrefix}metricType`);
const selectedSourceId = watch('source');
const { data: tableSource } = useSource({ id: selectedSourceId });
const metricType = useWatch({ control, name: `${namePrefix}metricType` });
// Initialize metricType to 'gauge' when switching to a metric source
useEffect(() => {
if (tableSource?.kind === SourceKind.Metric && !metricType) {
setValue(`${namePrefix}metricType`, MetricsDataType.Gauge);
}
}, [tableSource?.kind, metricType, namePrefix, setValue]);
const tableName =
tableSource?.kind === SourceKind.Metric
? getMetricTableName(tableSource, metricType)
: _tableName;
const metricName = watch(`${namePrefix}metricName`);
const metricName = useWatch({ control, name: `${namePrefix}metricName` });
const { data: attributeKeys } = useFetchMetricResourceAttrs({
databaseName,
tableName: tableName || '',
@ -243,7 +252,7 @@ function ChartSeriesEditorComponent({
control={control}
/>
</div>
{tableSource?.kind === SourceKind.Metric && (
{tableSource?.kind === SourceKind.Metric && metricType && (
<div style={{ minWidth: 220 }}>
<MetricNameSelect
metricName={metricName}
@ -257,6 +266,7 @@ function ChartSeriesEditorComponent({
setValue(`${namePrefix}metricType`, value)
}
metricSource={tableSource}
data-testid="metric-name-selector"
/>
{metricType === 'gauge' && (
<Flex justify="end">
@ -762,7 +772,7 @@ export default function EditTimeChartForm({
fields.length === 1 && displayType !== DisplayType.Number
}
tableName={tableName ?? ''}
watch={watch}
tableSource={tableSource}
/>
))}
{fields.length > 1 && displayType !== DisplayType.Number && (
@ -1123,10 +1133,12 @@ export default function EditTimeChartForm({
connection: tableSource.connection,
from: tableSource.from,
limit: { limit: 200 },
// Search mode requires a string select, not an array of aggregations
select:
queriedConfig.select ||
tableSource?.defaultTableSelectExpression ||
'',
typeof queriedConfig.select === 'string' &&
queriedConfig.select
? queriedConfig.select
: tableSource?.defaultTableSelectExpression || '',
groupBy: undefined,
granularity: undefined,
}}

View file

@ -39,8 +39,8 @@ function HistogramChart({
);
}, [graphResults]);
const barChartRef = useRef<any>();
const activeBar = useRef<CategoricalChartState>();
const barChartRef = useRef<any>(null);
const activeBar = useRef<CategoricalChartState | undefined>(undefined);
useHotkeys(['esc'], () => {
activeBar.current = undefined;

View file

@ -2,8 +2,8 @@ import { useMemo } from 'react';
import Link from 'next/link';
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
import type { FloatingPosition } from '@mantine/core';
import { Box, Code, Flex, HoverCard, Text } from '@mantine/core';
import { FloatingPosition } from '@mantine/core/lib/components/Floating';
import { useQueriedChartConfig } from '@/hooks/useChartConfig';
import type { NumberFormat } from '@/types';
@ -122,7 +122,7 @@ function ListBar({
</Text>
<Text size="xs" span>
{column.numberFormat != null
? (formatNumber(value, column.numberFormat) ?? 'N/A')
? formatNumber(value, column.numberFormat) ?? 'N/A'
: value}
</Text>
</Box>

View file

@ -412,7 +412,9 @@ export function DBRowJsonViewer({
maw="400px"
placeholder="Search properties by key or value"
value={filter}
onChange={e => setFilter(e.currentTarget.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setFilter(e.currentTarget.value)
}
leftSection={<i className="bi bi-search" />}
/>
{filter && (

View file

@ -15,7 +15,7 @@ export interface DBRowTableFieldWithPopoverProps {
cellValue: unknown;
wrapLinesEnabled: boolean;
columnName?: string;
tableContainerRef?: React.RefObject<HTMLDivElement>;
tableContainerRef?: React.RefObject<HTMLDivElement | null>;
isChart?: boolean;
}
@ -30,8 +30,8 @@ export const DBRowTableFieldWithPopover = ({
const [opened, { close, open }] = useDisclosure(false);
const [isCopied, setIsCopied] = useState(false);
const [hoverDisabled, setHoverDisabled] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout>();
const hoverDisableTimeoutRef = useRef<NodeJS.Timeout>();
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const hoverDisableTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
// Cleanup timeouts on unmount to prevent memory leaks
useEffect(() => {

View file

@ -20,6 +20,7 @@ export default function TableHeader({
header: Header<any, any>;
lastItemButtons?: React.ReactNode;
}) {
'use no memo'; // todo: table headers arent being resized properly with the react compiler
return (
<th
className="overflow-hidden"

View file

@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
import { ErrorBoundary } from './ErrorBoundary';

View file

@ -76,7 +76,7 @@
top: 0;
height: calc(100% + 1px);
max-height: 24px;
border-bottom: 1px solid var(color-border);
border-bottom: 1px solid var(--color-border);
}
.lineMenuBtn {

View file

@ -1,4 +1,4 @@
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
import HyperJson from './HyperJson';

View file

@ -113,6 +113,7 @@ export function MetricNameSelect({
isLoading,
isError,
metricSource,
'data-testid': dataTestId,
}: {
dateRange?: DateRange['dateRange'];
metricType: MetricsDataType;
@ -122,6 +123,7 @@ export function MetricNameSelect({
isLoading?: boolean;
isError?: boolean;
metricSource: TSource;
'data-testid'?: string;
}) {
const SEPARATOR = ':::::::';
@ -159,6 +161,9 @@ export function MetricNameSelect({
return metricsFromQuery;
}, [gaugeMetrics, histogramMetrics, sumMetrics, metricName, metricType]);
const currentValue =
metricName && metricType ? `${metricName}${SEPARATOR}${metricType}` : null;
return (
<Select
disabled={isLoading || isError}
@ -177,7 +182,7 @@ export function MetricNameSelect({
width: 'auto',
zIndex: 1111,
}}
value={`${metricName}${SEPARATOR}${metricType}`}
value={currentValue}
searchable
clearable
onChange={value => {
@ -187,6 +192,7 @@ export function MetricNameSelect({
setMetricType(_metricType.toLowerCase() as MetricsDataType);
}
}}
data-testid={dataTestId}
/>
);
}

View file

@ -31,7 +31,7 @@ const FORMAT_ICONS: Record<string, string> = {
time: 'clock',
};
export const NumberFormatForm: React.VFC<{
export const NumberFormatForm: React.FC<{
value?: NumberFormat;
onApply: (value: NumberFormat) => void;
onClose: () => void;
@ -198,7 +198,7 @@ export const NumberFormatForm: React.VFC<{
const TEST_NUMBER = 1234;
export const NumberFormatInput: React.VFC<{
export const NumberFormatInput: React.FC<{
value?: NumberFormat;
onChange: (value?: NumberFormat) => void;
}> = ({ value, onChange }) => {

View file

@ -27,7 +27,7 @@ export const SourceSelectRightSection = ({
rightSection: (
<>
<UnstyledButton
onClick={e => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
}}

View file

@ -105,7 +105,7 @@ export const Table = <T extends Record<string, unknown> | string[]>({
);
};
export const TableCellButton: React.VFC<{
export const TableCellButton: React.FC<{
title?: string;
label: React.ReactNode;
biIcon?: string;

View file

@ -117,7 +117,9 @@ export const Tags = React.memo(
m={8}
mb={0}
value={q}
onChange={event => setQ(event.currentTarget.value)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setQ(event.currentTarget.value)
}
onKeyDown={handleSearchKeyDown}
rightSection={
q && (

View file

@ -1,5 +1,5 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/nextjs';
import { TimePicker } from '@/components/TimePicker';

View file

@ -5,7 +5,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { InputControlled, PasswordInputControlled } from '../InputControlled';
// Test wrapper component that provides form context
function TestForm({ children }: { children: React.ReactNode }) {
function TestForm({ children }: { children: React.ReactElement }) {
const { control } = useForm({
defaultValues: {
testInput: '',
@ -13,11 +13,7 @@ function TestForm({ children }: { children: React.ReactNode }) {
},
});
return (
<form>
{React.cloneElement(children as React.ReactElement, { control })}
</form>
);
return <form>{React.cloneElement(children, { control } as any)}</form>;
}
describe('InputControlled', () => {

View file

@ -301,7 +301,7 @@ export function useRRWebEventStream(
const lastAbortController = useRef<AbortController | null>(null);
const [fetchStatus, setFetchStatus] = useState<'fetching' | 'idle'>('idle');
const lastFetchStatusRef = useRef<'fetching' | 'idle' | undefined>();
const lastFetchStatusRef = useRef<'fetching' | 'idle' | undefined>(undefined);
const { data: source } = useSource({ id: sourceId });

View file

@ -1,5 +1,5 @@
import type { Meta } from '@storybook/react';
import type { StoryObj } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
import type { StoryObj } from '@storybook/nextjs';
import AppNav from '../AppNav';
import { AppNavUserMenu } from '../AppNav.components';

View file

@ -1,5 +1,5 @@
import { Button } from '@mantine/core';
import type { Meta } from '@storybook/react';
import type { Meta } from '@storybook/nextjs';
// Just a test story, can be deleted

View file

@ -174,7 +174,9 @@ export function useTimeQuery({
)
: undefined;
const inputTimeQueryDerivedTimeQueryRef = useRef<[Date, Date] | undefined>();
const inputTimeQueryDerivedTimeQueryRef = useRef<[Date, Date] | undefined>(
undefined,
);
// When the inputTimeQuery changes, we should calculate the time range
// and set the timeRangeQuery if there is no existing time range query

View file

@ -509,7 +509,7 @@ export const useIsTerms = () => {
};
export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});

View file

@ -64,14 +64,17 @@ test.describe('Dashboard', { tag: ['@dashboard'] }, () => {
const demoMetricsOption = page.locator('text=Demo Metrics');
await expect(demoMetricsOption).toBeVisible();
await demoMetricsOption.click();
await page.waitForTimeout(1000);
// Wait for the metric selector to appear
const metricSelector = page.locator(
'input[placeholder*="metric"], input[placeholder*="Select a metric"]',
'[data-testid="metric-name-selector"]',
);
await expect(metricSelector).toBeVisible();
await expect(metricSelector).toBeVisible({ timeout: 5000 });
// Click to open the dropdown first
await metricSelector.click();
await page.waitForTimeout(500);
// Type the metric name to filter options
await metricSelector.fill('k8s.container.cpu_limit');

View file

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"sourceMap": true,
"skipLibCheck": true,
@ -15,31 +11,18 @@
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"useUnknownInCatchVariables": false,
"jsx": "preserve",
"types": [
"@types/intercom-web",
"jest"
],
"types": ["@types/intercom-web", "jest"],
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
],
"@styles/*": [
"styles/*"
]
"@/*": ["src/*"],
"@styles/*": ["styles/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View file

@ -1,7 +0,0 @@
keys
node_modules
archive
dist
migrations
migrate-mongo-config.ts
tsup.config.ts

View file

@ -1,43 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:n/recommended',
'plugin:prettier/recommended',
'plugin:security/recommended-legacy',
],
rules: {
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
'n/no-process-exit': 'warn',
'n/no-missing-import': 'off',
'n/no-unpublished-import': [
'error',
{
allowModules: ['supertest'],
},
],
'n/no-unsupported-features/es-syntax': [
'error',
{
ignores: ['modules'],
},
],
'prettier/prettier': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
};

View file

@ -0,0 +1,94 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettierConfig from 'eslint-config-prettier';
import prettierPlugin from 'eslint-plugin-prettier';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import nodePlugin from 'eslint-plugin-n';
import securityPlugin from 'eslint-plugin-security';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
{
ignores: [
'node_modules/**',
'dist/**',
'coverage/**',
'**/*.config.js',
'**/*.config.mjs',
'jest.config.js',
],
},
{
files: ['src/**/*.ts'],
plugins: {
'@typescript-eslint': tseslint.plugin,
'simple-import-sort': simpleImportSort,
'prettier': prettierPlugin,
'n': nodePlugin,
'security': securityPlugin,
},
rules: {
...nodePlugin.configs.recommended.rules,
...securityPlugin.configs['recommended-legacy'].rules,
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'n/no-process-exit': 'warn',
'n/no-missing-import': 'off',
'n/no-unpublished-import': [
'error',
{
allowModules: ['supertest'],
},
],
'n/no-unsupported-features/es-syntax': [
'error',
{
ignores: ['modules'],
},
],
'prettier/prettier': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
globals: {
console: 'readonly',
process: 'readonly',
module: 'readonly',
require: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
exports: 'readonly',
Buffer: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
setImmediate: 'readonly',
clearImmediate: 'readonly',
global: 'readonly',
},
},
},
];

View file

@ -703,6 +703,7 @@ type AllKeys<T> = T extends any ? keyof T : never;
// 3. [keyof T]
// Indexes into the mapped type to get the union of all non-never values
type NonOptionalKeysPresentInEveryUnionBranch<T> = {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

8679
yarn.lock

File diff suppressed because it is too large Load diff