diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..234c453f2 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,73 @@ +import { includeIgnoreFile } from '@eslint/compat'; +import js from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import prettier from 'eslint-plugin-prettier'; +import pluginReact from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import turbo from 'eslint-plugin-turbo'; +import { defineConfig } from 'eslint/config'; +import globals from 'globals'; +import { fileURLToPath } from 'node:url'; +import tsEslint from 'typescript-eslint'; + +const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url)); + +export default defineConfig([ + includeIgnoreFile(gitignorePath), + { + ignores: ['public/**', 'postcss.config.*'], + }, + { + files: ['**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + plugins: { js }, + extends: ['js/recommended'], + languageOptions: { globals: { ...globals.browser } }, + }, + ...tsEslint.configs.recommended, + pluginReact.configs.flat.recommended, + eslintConfigPrettier, + { + plugins: { + 'react-hooks': reactHooks, + turbo, + prettier, + }, + settings: { react: { version: 'detect' } }, + rules: { + // React + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'react/display-name': 'warn', + + // React Hooks + ...reactHooks.configs.recommended.rules, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + + // TypeScript + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + + // Prettier + 'prettier/prettier': 'error', + + // Turbo + 'turbo/no-undeclared-env-vars': 'error', + }, + }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: './', + }, + }, + }, +]); diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index b1925810b..000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,130 +0,0 @@ -import { fixupPluginRules } from '@eslint/compat'; -import { FlatCompat } from '@eslint/eslintrc'; -import js from '@eslint/js'; -import typescriptEslint from '@typescript-eslint/eslint-plugin'; -import tsParser from '@typescript-eslint/parser'; -import prettier from 'eslint-plugin-prettier'; -import react from 'eslint-plugin-react'; -import reactHooks from 'eslint-plugin-react-hooks'; -import globals from 'globals'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); - -export default [ - { - ignores: [ - '**/public', - '**/node_modules', - 'resources/views', - '**/babel.config.js', - '**/tailwind.config.js', - '**/webpack.config.js', - '**/tsconfig.json', - '**/eslint.config.mjs', - ], - }, - ...compat.extends( - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:@typescript-eslint/recommended', - ), - { - plugins: { - react, - 'react-hooks': fixupPluginRules(reactHooks), - prettier, - '@typescript-eslint': typescriptEslint, - }, - - languageOptions: { - globals: { - ...globals.browser, - ...globals.node, - }, - - parser: tsParser, - }, - - settings: { - react: { - pragma: 'React', - version: 'detect', - }, - - linkComponents: [ - { - name: 'Link', - linkAttribute: 'to', - }, - { - name: 'NavLink', - linkAttribute: 'to', - }, - ], - }, - - rules: { - '@typescript-eslint/no-var-requires': 0, - '@typescript-eslint/ban-ts-comment': 0, - - 'prettier/prettier': [ - 'warn', - { - endOfLine: 'auto', - }, - { - usePrettierrc: true, - }, - ], - - 'react/prop-types': 0, - 'react/display-name': 0, - - 'react/no-unknown-property': [ - 'error', - { - ignore: ['css'], - }, - ], - - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/no-non-null-assertion': 0, - 'no-use-before-define': 0, - '@typescript-eslint/no-use-before-define': 'warn', - - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], - }, - }, - { - files: ['**/*.ts', '**/*.tsx'], - - languageOptions: { - ecmaVersion: 6, - sourceType: 'script', - - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - - project: './tsconfig.json', - tsconfigRootDir: './', - }, - }, - }, -]; diff --git a/package.json b/package.json index 10908058f..e0d7505b6 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "debounce": "^2.2.0", "deepmerge-ts": "^7.1.5", "easy-peasy": "^6.1.0", + "eslint-plugin-turbo": "^2.5.4", "events": "^3.3.0", "formik": "^2.4.6", "globals": "^16.3.0", @@ -80,6 +81,7 @@ "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11", "turbo": "^2.5.4", + "typescript-eslint": "^8.36.0", "uuid": "^11.1.0", "vite": "^7.0.2", "yup": "^1.6.1" @@ -119,7 +121,7 @@ "vite-plugin-manifest-sri": "^0.2.0" }, "scripts": { - "lint": "eslint ./resources/scripts --ext .ts,.tsx --fix", + "lint": "eslint . --fix", "lint:turbo": "turbo lint", "dev": "vite", "dev:docker": "cross-env APP_URL=\"http://localhost:3000\" vite", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19f40a263..76493fff1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,6 +152,9 @@ importers: easy-peasy: specifier: ^6.1.0 version: 6.1.0(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + eslint-plugin-turbo: + specifier: ^2.5.4 + version: 2.5.4(eslint@9.30.1(jiti@2.4.2))(turbo@2.5.4) events: specifier: ^3.3.0 version: 3.3.0 @@ -227,6 +230,9 @@ importers: turbo: specifier: ^2.5.4 version: 2.5.4 + typescript-eslint: + specifier: ^8.36.0 + version: 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) uuid: specifier: ^11.1.0 version: 11.1.0 @@ -2426,6 +2432,10 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dotenv@16.5.0: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} @@ -2548,6 +2558,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-turbo@2.5.4: + resolution: {integrity: sha512-IZsW61DFj5mLMMaCJxhh1VE4HvNhfdnHnAaXajgne+LUzdyHk2NvYT0ECSa/1SssArcqgTvV74MrLL68hWLLFw==} + peerDependencies: + eslint: '>6.6.0' + turbo: '>2.0.0' + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3814,6 +3830,13 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.36.0: + resolution: {integrity: sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -6277,6 +6300,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.0.3: {} + dotenv@16.5.0: {} dunder-proto@1.0.1: @@ -6496,6 +6521,12 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-turbo@2.5.4(eslint@9.30.1(jiti@2.4.2))(turbo@2.5.4): + dependencies: + dotenv: 16.0.3 + eslint: 9.30.1(jiti@2.4.2) + turbo: 2.5.4 + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -7783,6 +7814,16 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + typescript@5.8.3: {} unbox-primitive@1.1.0: diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index ceb377f27..dddb559cf 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -58,7 +58,7 @@ function LoginContainer() { const resetCaptcha = () => { setToken(''); if (isTurnstileEnabled && turnstileRef.current) { - // @ts-ignore - The type doesn't expose the reset method directly + // @ts-expect-error - The type doesn't expose the reset method directly turnstileRef.current.reset(); } if (isFriendlyEnabled && friendlyCaptchaRef.current) { diff --git a/resources/scripts/components/elements/CopyOnClick.tsx b/resources/scripts/components/elements/CopyOnClick.tsx index 42545f61c..dc697eb9e 100644 --- a/resources/scripts/components/elements/CopyOnClick.tsx +++ b/resources/scripts/components/elements/CopyOnClick.tsx @@ -41,7 +41,7 @@ const CopyOnClick = ({ text, children, showInNotification }: CopyOnClickProps) = const child = !text ? React.Children.only(children) : React.cloneElement(React.Children.only(children), { - // @ts-ignore + // @ts-expect-error - Props type inference issue with React.cloneElement className: clsx(children.props.className || '', 'cursor-pointer'), onClick: (e: React.MouseEvent) => { copy(String(text)); diff --git a/resources/scripts/components/elements/Pagination.tsx b/resources/scripts/components/elements/Pagination.tsx index 118a4d9ca..3d50ea01a 100644 --- a/resources/scripts/components/elements/Pagination.tsx +++ b/resources/scripts/components/elements/Pagination.tsx @@ -35,7 +35,7 @@ function Pagination({ data: { items, pagination }, onPageSelect, children }: const end = Math.min(pagination.totalPages, pagination.currentPage + 5); for (let i = start; i <= end; i++) { - // @ts-ignore + // @ts-expect-error - Type issue with array push pages.push(i); } diff --git a/resources/scripts/components/elements/PermissionRoute.tsx b/resources/scripts/components/elements/PermissionRoute.tsx index e203e38ab..749b6658a 100644 --- a/resources/scripts/components/elements/PermissionRoute.tsx +++ b/resources/scripts/components/elements/PermissionRoute.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +import type { JSX, ReactNode } from 'react'; import { ServerError } from '@/components/elements/ScreenBlock'; @@ -6,17 +6,16 @@ import { usePermissions } from '@/plugins/usePermissions'; interface Props { children?: ReactNode; - permission?: string | string[]; } function PermissionRoute({ children, permission }: Props): JSX.Element { + const can = usePermissions(permission || []); + if (permission === undefined || permission === null) { return <>{children}; } - const can = usePermissions(permission); - if (can.filter((p) => p).length > 0) { return <>{children}; } diff --git a/resources/scripts/components/server/console/ServerDetailsBlock.tsx b/resources/scripts/components/server/console/ServerDetailsBlock.tsx index 547b4431e..531f7c802 100644 --- a/resources/scripts/components/server/console/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/console/ServerDetailsBlock.tsx @@ -25,7 +25,7 @@ type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>; // return undefined; // }; -// @ts-ignore +// @ts-expect-error - Unused parameter in component definition // eslint-disable-next-line @typescript-eslint/no-unused-vars const Limit = ({ limit, children }: { limit: string | null; children: React.ReactNode }) => <>{children}; diff --git a/resources/scripts/components/server/console/chart.ts b/resources/scripts/components/server/console/chart.ts index 4c70987ef..4216fbb47 100644 --- a/resources/scripts/components/server/console/chart.ts +++ b/resources/scripts/components/server/console/chart.ts @@ -66,7 +66,7 @@ const options: ChartOptions<'line'> = { }; function getOptions(opts?: DeepPartial> | undefined): ChartOptions<'line'> { - // @ts-ignore I'm not even going to try to tell you what this error means + // @ts-expect-error - deepmerge type compatibility issue with ChartOptions return deepmerge(options, opts || {}); } diff --git a/resources/scripts/components/server/files/FileManagerContainer.tsx b/resources/scripts/components/server/files/FileManagerContainer.tsx index 5f1bc70f0..57f3682b5 100644 --- a/resources/scripts/components/server/files/FileManagerContainer.tsx +++ b/resources/scripts/components/server/files/FileManagerContainer.tsx @@ -192,7 +192,7 @@ export default () => { }} > diff --git a/resources/scripts/routers/DashboardRouter.tsx b/resources/scripts/routers/DashboardRouter.tsx index afcfc01ed..b1241b463 100644 --- a/resources/scripts/routers/DashboardRouter.tsx +++ b/resources/scripts/routers/DashboardRouter.tsx @@ -41,13 +41,13 @@ export default () => { const showSideBar = (shown: boolean) => { setSidebarVisible(shown); - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (!shown) setSidebarPosition(-500); else setSidebarPosition(0); }; const checkIfMinimal = () => { - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (!(window.getComputedStyle(sidebarRef.current, null).display === 'block')) { showSideBar(true); return true; @@ -98,31 +98,30 @@ export default () => { // Handle touch events for swipe to close const handleTouchStart = (e: React.TouchEvent) => { if (checkIfMinimal()) return; - // @ts-ignore it is not "possibly undefined." Pretty much guarunteed to work. + // @ts-expect-error - Legacy type suppression it is not "possibly undefined." Pretty much guarunteed to work. if (isSidebarVisible) setTouchStartX(e.touches[0].clientX - sidebarRef.current?.clientWidth); - // @ts-ignore + // @ts-expect-error - Legacy type suppression else setTouchStartX(e.touches[0].clientX); }; const handleTouchMove = (e: React.TouchEvent) => { if (checkIfMinimal()) return; - // @ts-ignore go to sleep TSC + // @ts-expect-error - Legacy type suppression go to sleep TSC const sidebarWidth = sidebarRef.current.clientWidth; - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (e.touches[0].clientX - touchStartX < 30) { setSidebarPosition(-sidebarWidth); return; } - // @ts-ignore + // @ts-expect-error - Legacy type suppression const clampedValue = Math.max(Math.min(e.touches[0].clientX - touchStartX, sidebarWidth), 0) - sidebarWidth; setSidebarBetween(false); console.group('updateDragLocation'); - //@ts-ignore Ok, TSC please go back to bed. console.info(`start ${clampedValue}`); console.groupEnd(); @@ -135,7 +134,7 @@ export default () => { setTouchStartX(null); setSidebarBetween(true); - // @ts-ignore + // @ts-expect-error - Legacy type suppression if ((sidebarPosition - sidebarRef.current?.clientWidth) / sidebarRef.current?.clientWidth > -1.35) { showSideBar(true); } else { diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 8780728c6..7c10b6159 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -108,13 +108,13 @@ export default () => { const showSideBar = (shown: boolean) => { setSidebarVisible(shown); - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (!shown) setSidebarPosition(-500); else setSidebarPosition(0); }; const checkIfMinimal = () => { - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (!(window.getComputedStyle(sidebarRef.current, null).display === 'block')) { showSideBar(true); return true; @@ -167,31 +167,30 @@ export default () => { // Handle touch events for swipe to close const handleTouchStart = (e: React.TouchEvent) => { if (checkIfMinimal()) return; - // @ts-ignore it is not "possibly undefined." Pretty much guarunteed to work. + // @ts-expect-error - Legacy type suppression it is not "possibly undefined." Pretty much guarunteed to work. if (isSidebarVisible) setTouchStartX(e.touches[0].clientX - sidebarRef.current?.clientWidth); - // @ts-ignore + // @ts-expect-error - Legacy type suppression else setTouchStartX(e.touches[0].clientX); }; const handleTouchMove = (e: React.TouchEvent) => { if (checkIfMinimal()) return; - // @ts-ignore go to sleep TSC + // @ts-expect-error - Legacy type suppression go to sleep TSC const sidebarWidth = sidebarRef.current.clientWidth; - // @ts-ignore + // @ts-expect-error - Legacy type suppression if (e.touches[0].clientX - touchStartX < 30) { setSidebarPosition(-sidebarWidth); return; } - // @ts-ignore + // @ts-expect-error - Legacy type suppression const clampedValue = Math.max(Math.min(e.touches[0].clientX - touchStartX, sidebarWidth), 0) - sidebarWidth; setSidebarBetween(false); console.group('updateDragLocation'); - //@ts-ignore Ok, TSC please go back to bed. console.info(`start ${clampedValue}`); console.groupEnd(); @@ -204,8 +203,8 @@ export default () => { setTouchStartX(null); setSidebarBetween(true); - // @ts-ignore - // @ts-ignore + // @ts-expect-error - Legacy type suppression + // @ts-expect-error - Legacy type suppression if ((sidebarPosition - sidebarRef.current?.clientWidth) / sidebarRef.current?.clientWidth > -1.35) { showSideBar(true); } else { @@ -411,7 +410,6 @@ export default () => { viewBox='0 0 16 15' className='flex shrink-0 h-full w-full' > - {/* @ts-ignore */} diff --git a/turbo.json b/turbo.json index 65cfba6fd..04e9e36d8 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,6 @@ { "$schema": "https://turbo.build/schema.json", + "globalEnv": ["NODE_ENV"], "tasks": { "build": { "outputs": ["public/build/**"] diff --git a/vite.config.ts b/vite.config.ts index 00423dcb5..bbb99aaa4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -58,11 +58,10 @@ export default defineConfig({ rollupOptions: { input: path.resolve('resources/scripts/index.tsx'), output: { - // @ts-ignore + // @ts-expect-error It won't fail lol manualChunks(id) { if (id.includes('node_modules')) { - // @ts-expect-error - // It won't fail lol + // @ts-expect-error It won't fail lol return id.toString().split('node_modules/')[1].split('/')[0].toString(); } },