Merged in chore/eslint9 (pull request #2029)

chore: Update to ESLint 9

* wip: apply codemod and upgrade swc plugin

* Update eslint to 9 in scandic-web

apply code mod to config
fix existing lint issues

* Remove uneccessary fixupConfigRules

* Update eslint to 9 in design-system

* Add lint turbo dependency

* Move redis-api to eslint and prettier instead of biome

* Simplify eslint configs

* Clean up

* Apply linting


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-06-03 14:26:44 +00:00
parent 91278feb40
commit dd4ef527df
37 changed files with 858 additions and 497 deletions

View File

@@ -1,38 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true,
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules"],
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
},
"organizeImports": {
"enabled": true,
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"performance": {
"noBarrelFile": "error",
},
"style": {
"useImportType": "error",
"useExportType": "error",
},
},
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingCommas": "all",
},
},
}

View File

@@ -0,0 +1,41 @@
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import { defineConfig } from "eslint/config";
import simpleImportSort from "eslint-plugin-simple-import-sort";
const compat = new FlatCompat({
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([
{
extends: compat.extends("plugin:@typescript-eslint/recommended"),
plugins: {
"simple-import-sort": simpleImportSort,
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
},
rules: {
"no-unused-vars": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
},
},
]);

View File

@@ -1,10 +1,11 @@
{
"name": "redis-api",
"name": "@scandic-hotels/redis-api",
"module": "index.ts",
"type": "module",
"private": true,
"scripts": {
"dev": "bun --watch src/index.ts | pino-pretty -o '{if module}[{module}] {end}{msg}' -i pid,hostname"
"dev": "bun --watch src/index.ts | pino-pretty -o '{if module}[{module}] {end}{msg}' -i pid,hostname",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && tsc"
},
"dependencies": {
"@elysiajs/server-timing": "^1.3.0",
@@ -17,9 +18,18 @@
"pino": "^9.6.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.26.0",
"@types/bun": "latest",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"eslint": "^9",
"eslint-plugin-simple-import-sort": "^10.0.0",
"pino-pretty": "^13.0.0",
"prettier": "^3.5.3",
"typescript": "^5.7.2"
},
"prettier": {
"tabWidth": 4
}
}

View File

@@ -1,17 +1,17 @@
import "@/server/sentry.server.config";
import serverTiming from "@elysiajs/server-timing";
import { swagger } from "@elysiajs/swagger";
import * as Sentry from "@sentry/bun";
import { Elysia } from "elysia";
import { swagger } from "@elysiajs/swagger";
import { apiRoutes } from "@/routes/api";
import { healthRoutes } from "@/routes/health";
import { baseLogger } from "@/utils/logger";
import { env } from "@/env";
import serverTiming from "@elysiajs/server-timing";
import { AuthenticationError } from "@/errors/AuthenticationError";
import { ModelValidationError } from "@/errors/ModelValidationError";
import { apiRoutes } from "@/routes/api";
import { healthRoutes } from "@/routes/health";
import { setupShutdown } from "@/shutdown";
import { baseLogger } from "@/utils/logger";
setupShutdown();
@@ -48,7 +48,7 @@ if (env.IS_DEV) {
version: "1.0.0",
},
},
})
}),
);
}

View File

@@ -1,6 +1,7 @@
import { AuthenticationError } from "@/errors/AuthenticationError";
import type { Context } from "elysia";
import { env } from "@/env";
import { AuthenticationError } from "@/errors/AuthenticationError";
const API_KEY_HEADER = "x-api-key";

View File

@@ -1,11 +1,11 @@
import * as Sentry from "@sentry/bun";
import { Elysia, t } from "elysia";
import { redis } from "@/services/redis";
import { ModelValidationError } from "@/errors/ModelValidationError";
import { redis } from "@/services/redis";
import { loggerModule } from "@/utils/logger";
import { truncate } from "@/utils/truncate";
import { timeout } from "@/utils/timeout";
import { truncate } from "@/utils/truncate";
const MIN_LENGTH = 1;
@@ -42,7 +42,7 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" })
{
query: QUERY_TYPE,
response: { 200: t.Object({ data: t.Any() }), 404: t.String() },
}
},
)
.put(
"/",
@@ -52,7 +52,7 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" })
if (!body.ttl || body.ttl < 0) {
cacheRouteLogger.warn(
`PUT /cache ${key} with ttl=${body.ttl}, will not cache the data`
`PUT /cache ${key} with ttl=${body.ttl}, will not cache the data`,
);
return status("Bad Request", "ttl is required");
}
@@ -66,14 +66,14 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" })
body: t.Object({ data: t.Any(), ttl: t.Number() }),
query: QUERY_TYPE,
response: { 204: t.Undefined(), 400: t.String() },
}
},
)
.delete(
"/",
async ({ query: { key, fuzzy } }) => {
key = validateKey(key);
cacheRouteLogger.debug(
`DELETE /cache ${key} ${fuzzy ? "fuzzy" : ""}`
`DELETE /cache ${key} ${fuzzy ? "fuzzy" : ""}`,
);
const deletedKeys: number = fuzzy
? await deleteWithPattern(`*${key}*`)
@@ -91,7 +91,7 @@ export const cacheRoutes = new Elysia({ prefix: "/cache" })
200: t.Object({ deletedKeys: t.Number() }),
400: t.String(),
},
}
},
);
function validateKey(key: string) {
@@ -99,7 +99,7 @@ function validateKey(key: string) {
if (parsedKey.length < MIN_LENGTH) {
throw new ModelValidationError(
"Key has to be at least 1 character long"
"Key has to be at least 1 character long",
);
}
@@ -122,7 +122,7 @@ async function deleteWithPattern(pattern: string) {
"MATCH",
pattern,
"COUNT",
SCAN_SIZE
SCAN_SIZE,
);
cursor = newCursor;

View File

@@ -1,7 +1,9 @@
import { Elysia } from "elysia";
import { cacheRoutes } from "./cache";
import { apiKeyMiddleware } from "@/middleware/apiKeyMiddleware";
import { cacheRoutes } from "./cache";
export const apiRoutes = new Elysia({ prefix: "/api" })
.guard({ beforeHandle: apiKeyMiddleware })
.use(cacheRoutes);

View File

@@ -1,8 +1,8 @@
import Elysia, { t } from "elysia";
import { redis } from "@/services/redis";
import { baseLogger, loggerModule } from "@/utils/logger";
import { env } from "@/env";
import { redis } from "@/services/redis";
import { baseLogger } from "@/utils/logger";
const healthLogger = baseLogger.child({
module: "health",
@@ -52,5 +52,5 @@ export const healthRoutes = new Elysia().get(
duration: t.String(),
}),
},
}
},
);

View File

@@ -1,7 +1,9 @@
import "@sentry/tracing";
import { env } from "@/env";
import * as Sentry from "@sentry/bun";
import { env } from "@/env";
Sentry.init({
dsn: env.SENTRY_DSN,
enabled: env.SENTRY_ENABLED,

View File

@@ -1,6 +1,7 @@
import { redisConfig, env } from "@/env";
import ioredis from "ioredis";
import { env, redisConfig } from "@/env";
const redis = new ioredis({
host: redisConfig.host,
port: redisConfig.port,

View File

@@ -1,5 +1,5 @@
import { loggerModule } from "@/utils/logger";
import { redis } from "@/services/redis";
import { loggerModule } from "@/utils/logger";
const shutdownLogger = loggerModule("shutdown");

View File

@@ -1,34 +1,36 @@
import pino from "pino";
import { mask } from "./mask";
import { env } from "@/env";
const serializers: { [key: string]: pino.SerializerFn } = {
password: (payload) => {
if (payload) {
return env.IS_DEV
? mask(payload)
: mask(payload, {
visibleStart: 0,
visibleEnd: 0,
});
}
import { mask } from "./mask";
return payload;
},
email: (payload) => {
if (payload) {
return env.IS_DEV ? payload : mask(payload);
}
return payload;
},
const serializers: { [key: string]: pino.SerializerFn } = {
password: (payload) => {
if (payload) {
return env.IS_DEV
? mask(payload)
: mask(payload, {
visibleStart: 0,
visibleEnd: 0,
});
}
return payload;
},
email: (payload) => {
if (payload) {
return env.IS_DEV ? payload : mask(payload);
}
return payload;
},
};
export const baseLogger = pino({
level: process.env.LOG_LEVEL || "info",
timestamp: pino.stdTimeFunctions.isoTime,
serializers,
level: process.env.LOG_LEVEL || "info",
timestamp: pino.stdTimeFunctions.isoTime,
serializers,
});
export const loggerModule = (loggerName: string) => {
return baseLogger.child({ module: loggerName });
return baseLogger.child({ module: loggerName });
};

View File

@@ -1,42 +1,43 @@
import { describe, it, expect } from "bun:test";
import { describe, expect, it } from "bun:test";
import { mask } from "./mask";
describe("mask", () => {
it("should return empty string for empty input", () => {
expect(mask("")).toBe("");
});
it("should return empty string for empty input", () => {
expect(mask("")).toBe("");
});
it("should mask string with default parameters", () => {
expect(mask("1234567890")).toBe("12******90");
});
it("should mask string with default parameters", () => {
expect(mask("1234567890")).toBe("12******90");
});
it("should show custom number of characters at start", () => {
expect(mask("1234567890", { visibleStart: 3 })).toBe("123*****90");
});
it("should show custom number of characters at start", () => {
expect(mask("1234567890", { visibleStart: 3 })).toBe("123*****90");
});
it("should show custom number of characters at end", () => {
expect(mask("1234567890", { visibleStart: 2, visibleEnd: 3 })).toBe(
"12*****890",
);
});
it("should show custom number of characters at end", () => {
expect(mask("1234567890", { visibleStart: 2, visibleEnd: 3 })).toBe(
"12*****890",
);
});
it("should mask entire string when visible parts exceed length", () => {
expect(mask("123", { visibleStart: 2, visibleEnd: 2 })).toBe("***");
});
it("should mask entire string when visible parts exceed length", () => {
expect(mask("123", { visibleStart: 2, visibleEnd: 2 })).toBe("***");
});
it("should handle undefined end part", () => {
expect(mask("1234567890", { visibleStart: 2, visibleEnd: 0 })).toBe(
"12********",
);
});
it("should handle undefined end part", () => {
expect(mask("1234567890", { visibleStart: 2, visibleEnd: 0 })).toBe(
"12********",
);
});
it("should handle long strings", () => {
expect(mask("12345678901234567890")).toBe("12**********90");
});
it("should handle long strings", () => {
expect(mask("12345678901234567890")).toBe("12**********90");
});
it("should handle emails", () => {
expect(mask("test.testsson@scandichotels.com")).toBe(
"te*********on@sc*********ls.com",
);
});
it("should handle emails", () => {
expect(mask("test.testsson@scandichotels.com")).toBe(
"te*********on@sc*********ls.com",
);
});
});

View File

@@ -1,89 +0,0 @@
{
"extends": ["next/core-web-vitals", "plugin:import/typescript"],
"plugins": ["simple-import-sort", "@typescript-eslint", "formatjs"],
"parser": "@typescript-eslint/parser",
"rules": {
"no-unused-vars": "off",
"react/function-component-definition": "error",
"simple-import-sort/imports": [
"error",
{
"groups": [
// Side effect imports.
["^\\u0000"],
// Node.js builtins.
["^node:"],
// NPM packages.
["^@?\\w"],
// Internal packages.
["^@scandic-hotels/(?!.*\u0000$).*$"],
// Local imports (lib, constants, etc.), excl. types.
[
"^@/constants/?(?!.*\u0000$).*$",
"^@/env/?(?!.*\u0000$).*$",
"^@/lib/?(?!.*\u0000$).*$",
"^@/server/?(?!.*\u0000$).*$",
"^@/stores/?(?!.*\u0000$).*$"
],
// Local imports (the rest), excl. types.
["^@/(?!(types|.*\u0000$)).*$"],
// Parent imports. Put `..` last.
// Other relative imports. Put same-folder imports and `.` last.
[
"^\\.\\.(?!/?$)",
"^\\.\\./?$",
"^\\./(?=.*/)(?!/?$)",
"^\\.(?!/?$)",
"^\\./?$"
],
// Style imports.
["^(?!\\u0000).+\\.s?css$"],
// Node.js builtins and NPM packages type imports.
["^node:.*\\u0000$", "^@?\\w.*\\u0000$"],
// Local type imports.
[
"^@scandichotels/.*\\u0000$",
"^@/types/.*",
"^@/.*\\u0000$",
"^[^.].*\\u0000$",
"^\\..*\\u0000$"
]
]
}
],
"simple-import-sort/exports": "error",
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": [
"error",
{
"prefer-inline": true
}
],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "all",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
// "formatjs/enforce-description": ["warn", "anything"],
"formatjs/enforce-default-message": ["error", "literal"],
"formatjs/enforce-placeholders": ["error"],
"formatjs/enforce-plural-rules": ["error"],
"formatjs/no-literal-string-in-jsx": ["error"],
"formatjs/no-multiple-whitespaces": ["error"],
"formatjs/no-multiple-plurals": ["error"],
"formatjs/no-invalid-icu": ["error"],
"formatjs/no-id": ["error"],
"formatjs/no-complex-selectors": ["error"],
"formatjs/no-useless-message": ["error"],
"formatjs/prefer-pound-in-plural": ["error"]
}
}

View File

@@ -15,7 +15,11 @@ import { getIntl } from "@/i18n"
import { getHotelSearchDetails } from "@/utils/hotelSearchDetails"
import { parseSelectHotelSearchParams } from "@/utils/url"
import type { LangParams, NextSearchParams, PageArgs } from "@/types/params"
import {
type LangParams,
type NextSearchParams,
type PageArgs,
} from "@/types/params"
export default async function AlternativeHotelsPage(
props: PageArgs<LangParams, NextSearchParams>

View File

@@ -24,7 +24,6 @@ export default async function HotelReservationPage(
}
return (
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
<div className={styles.page}>
<TrackingSDK pageData={pageTrackingData} />
</div>

View File

@@ -1,5 +1,3 @@
/* eslint-disable @next/next/no-img-element */
import { env } from "@/env/server"
import HotelMarkerByType from "@/components/Maps/Markers"

View File

@@ -6,11 +6,10 @@ import type {
NucleoIconProps,
} from "@scandic-hotels/design-system/Icons"
import type { MaterialIconSetIconProps } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import type { JSX } from "react"
import { FacilityEnum } from "@/types/enums/facilities"
import type { JSX } from "react";
const facilityToIconMap: Record<FacilityEnum, IconName> = {
[FacilityEnum.AccessibleBathingControls]: IconName.StarFilled,
[FacilityEnum.AccessibleBathtubs]: IconName.StarFilled,

View File

@@ -5,6 +5,8 @@ import {
import { ChildBedTypeEnum } from "@/constants/booking"
import type { JSX } from "react"
import { ChildBedMapEnum } from "@/types/components/bookingWidget/enums"
import {
RoomPackageCodeEnum,
@@ -13,8 +15,6 @@ import {
import type { Child } from "@/types/components/hotelReservation/selectRate/selectRate"
import type { Packages } from "@/types/requests/packages"
import type { JSX } from "react";
interface IconForFeatureCodeProps {
featureCode: RoomPackageCodes
}

View File

@@ -30,8 +30,7 @@ import type {
IconProps,
NucleoIconProps,
} from "@scandic-hotels/design-system/Icons"
import type { JSX } from "react";
import type { JSX } from "react"
interface IconByIconNameProps {
iconName: IconName | null

View File

@@ -1,4 +1,4 @@
import type { Dispatch, SetStateAction, JSX } from "react";
import type { Dispatch, JSX, SetStateAction } from "react"
export enum AnimationStateEnum {
unmounted = "unmounted",

View File

@@ -36,7 +36,6 @@ import type {
NucleoIconProps,
} from "@scandic-hotels/design-system/Icons"
import type { MaterialSymbolProps } from "@scandic-hotels/design-system/Icons/MaterialIcon/MaterialSymbol"
import type { JSX } from "react"
interface FacilityIconProps {

View File

@@ -3,11 +3,11 @@ import {
type MaterialIconSetIconProps,
} from "@scandic-hotels/design-system/Icons/MaterialIcon"
import type { JSX } from "react"
import { AlertTypeEnum } from "@/types/enums/alert"
import type { AlertProps } from "./alert"
import type { JSX } from "react";
interface IconByAlertProps {
alertType: AlertTypeEnum
variant?: AlertProps["variant"]

View File

@@ -1,6 +1,5 @@
import type { IconProps } from "@scandic-hotels/design-system/Icons"
import type { JSX } from "react";
import type { JSX } from "react"
export interface RadioCardProps
extends Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "title"> {

View File

@@ -0,0 +1,106 @@
import { FlatCompat } from "@eslint/eslintrc"
import js from "@eslint/js"
import typescriptEslint from "@typescript-eslint/eslint-plugin"
import tsParser from "@typescript-eslint/parser"
import { defineConfig } from "eslint/config"
import formatjs from "eslint-plugin-formatjs"
import simpleImportSort from "eslint-plugin-simple-import-sort"
const compat = new FlatCompat({
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
})
export default defineConfig([
{
extends: compat.extends("next/core-web-vitals", "plugin:import/typescript"),
plugins: {
"simple-import-sort": simpleImportSort,
"@typescript-eslint": typescriptEslint,
formatjs,
},
languageOptions: {
parser: tsParser,
},
rules: {
"no-unused-vars": "off",
"react/function-component-definition": "error",
"simple-import-sort/imports": [
"error",
{
groups: [
["^\\u0000"],
["^node:"],
["^@?\\w"],
["^@scandic-hotels/(?!.*\u0000$).*$"],
[
"^@/constants/?(?!.*\u0000$).*$",
"^@/env/?(?!.*\u0000$).*$",
"^@/lib/?(?!.*\u0000$).*$",
"^@/server/?(?!.*\u0000$).*$",
"^@/stores/?(?!.*\u0000$).*$",
],
["^@/(?!(types|.*\u0000$)).*$"],
[
"^\\.\\.(?!/?$)",
"^\\.\\./?$",
"^\\./(?=.*/)(?!/?$)",
"^\\.(?!/?$)",
"^\\./?$",
],
["^(?!\\u0000).+\\.s?css$"],
["^node:.*\\u0000$", "^@?\\w.*\\u0000$"],
[
"^@scandichotels/.*\\u0000$",
"^@/types/.*",
"^@/.*\\u0000$",
"^[^.].*\\u0000$",
"^\\..*\\u0000$",
],
],
},
],
"simple-import-sort/exports": "error",
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": [
"error",
{
"prefer-inline": true,
},
],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"formatjs/enforce-default-message": ["error", "literal"],
"formatjs/enforce-placeholders": ["error"],
"formatjs/enforce-plural-rules": ["error"],
"formatjs/no-literal-string-in-jsx": ["error"],
"formatjs/no-multiple-whitespaces": ["error"],
"formatjs/no-multiple-plurals": ["error"],
"formatjs/no-invalid-icu": ["error"],
"formatjs/no-id": ["error"],
"formatjs/no-complex-selectors": ["error"],
"formatjs/no-useless-message": ["error"],
"formatjs/prefer-pound-in-plural": ["error"],
},
},
])

View File

@@ -114,6 +114,9 @@
"zustand": "^4.5.2"
},
"devDependencies": {
"@eslint/compat": "^1.2.9",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.26.0",
"@formatjs/cli": "^6.7.1",
"@lokalise/node-api": "^14.0.0",
"@scandic-hotels/typescript-config": "workspace:*",
@@ -134,7 +137,7 @@
"adm-zip": "^0.5.16",
"cypress": "^14.3.3",
"dotenv": "^16.5.0",
"eslint": "^8",
"eslint": "^9",
"eslint-config-next": "15.3.2",
"eslint-plugin-formatjs": "^5.3.1",
"eslint-plugin-import": "^2.31.0",

View File

@@ -9,6 +9,9 @@
},
"test": {
"dependsOn": ["@scandic-hotels/design-system#build"]
},
"lint": {
"dependsOn": ["@scandic-hotels/design-system#build"]
}
}
}

View File

@@ -1,9 +1,9 @@
import type { JSX } from "react"
import type { MembershipLevel } from "@/constants/membershipLevels"
import type { LoyaltyLevel } from "@/server/routers/contentstack/loyaltyLevel/output"
import type { CMSReward } from "../trpc/routers/contentstack/reward"
import type { JSX } from "react";
export type OverviewTableClientProps = {
activeMembership: MembershipLevel | null
levels: ComparisonLevel[]

View File

@@ -1,4 +1,4 @@
import { RTETypeEnum } from "./enums"
import type { JSX } from "react"
import type { EmbedByUid } from "../components/deprecatedjsontohtml"
import type {
@@ -8,10 +8,9 @@ import type {
RTEImageVaultAttrs,
RTELinkAttrs,
} from "./attrs"
import type { RTETypeEnum } from "./enums"
import type { RenderOptions } from "./option"
import type { JSX } from "react";
export interface RTEDefaultNode {
attrs: Attributes
children: RTENode[]

View File

@@ -1,3 +1,5 @@
import type { JSX } from "react"
import type { EmbedByUid } from "../jsontohtml"
import type {
Attributes,
@@ -9,8 +11,6 @@ import type {
import type { RTETypeEnum } from "./enums"
import type { RenderOptions } from "./option"
import type { JSX } from "react";
export interface RTEDefaultNode {
attrs: Attributes
children: RTENode[]

View File

@@ -1,16 +0,0 @@
{
"linter": {
"enabled": false,
"rules": {
"recommended": true,
},
},
"overrides": [
{
"include": ["apps/redis-api/**"],
"linter": {
"enabled": true,
},
},
],
}

View File

@@ -16,6 +16,7 @@
"packages/*"
],
"devDependencies": {
"@eslint/compat": "^1.2.9",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.0",
"husky": "^9.1.7",

View File

@@ -1,19 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

View File

@@ -16,6 +16,6 @@ const config: StorybookConfig = {
}
export default config
function getAbsolutePath(value: string): any {
function getAbsolutePath(value: string) {
return dirname(require.resolve(join(value, 'package.json')))
}

View File

@@ -0,0 +1,40 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import tsParser from '@typescript-eslint/parser'
import reactRefresh from 'eslint-plugin-react-refresh'
import { FlatCompat } from '@eslint/eslintrc'
import js from '@eslint/js'
const compat = new FlatCompat({
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
})
export default defineConfig([
{
languageOptions: {
globals: {
...globals.browser,
},
parser: tsParser,
},
extends: compat.extends(
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended'
),
plugins: {
'react-refresh': reactRefresh,
},
rules: {
'react-refresh/only-export-components': [
'warn',
{
allowConstantExport: true,
},
],
},
},
globalIgnores(['**/dist', '**/.eslintrc.cjs']),
])

View File

@@ -134,6 +134,8 @@
"react-dom": "^19.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.26.0",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-links": "^8.6.12",
@@ -153,11 +155,12 @@
"colord": "^2.9.3",
"copy-to-clipboard": "^3.3.3",
"deepmerge-ts": "^7.1.5",
"eslint": "^8",
"eslint": "^9",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-storybook": "^0.12.0",
"glob": "^11.0.2",
"globals": "^16.1.0",
"husky": "^9.1.7",
"jiti": "^1.21.0",
"lint-staged": "^15.5.2",

765
yarn.lock

File diff suppressed because it is too large Load Diff