build: migrate from CRA to Vite with TypeScript config

This commit is contained in:
Arthur Bassi 2026-06-12 11:41:03 -03:00
parent bb11aab86c
commit 0451788764
10 changed files with 7455 additions and 23342 deletions

28
index.html Normal file
View file

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Seahaven - Officetrax" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<!-- Font Awesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<title>Seahaven - Officetrax</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

30182
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,55 +2,102 @@
"name": "seahaven-new-app",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "node --experimental-strip-types scripts/fix-router-paths.ts && tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
},
"lint-staged": {
"src/{api,domain,app,lib,providers,infra,config,hooks,components}/**/*.{ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{ts,tsx}": [
"prettier --write"
],
"src/test/**/*.{test,integration.test}.{ts,tsx}": [
"prettier --write"
],
"*.{json,css,md,mdc}": [
"prettier --write"
],
"eslint.config.js": [
"prettier --write"
],
"commitlint.config.js": [
"prettier --write"
]
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/dm-sans": "^5.2.8",
"@fontsource/jetbrains-mono": "^5.2.8",
"@fontsource/montserrat": "^5.2.8",
"@fullcalendar/daygrid": "^6.1.20",
"@fullcalendar/interaction": "^6.1.20",
"@fullcalendar/react": "^6.1.20",
"@fullcalendar/timegrid": "^6.1.20",
"@generouted/react-router": "^1.20.2",
"@hookform/resolvers": "^5.4.0",
"@mui/icons-material": "^7.3.1",
"@mui/material": "^7.3.1",
"@reduxjs/toolkit": "^2.11.2",
"@tanstack/react-query": "^5.100.10",
"@tanstack/react-query-devtools": "^5.100.10",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.16.0",
"http-proxy-middleware": "^3.0.5",
"clsx": "^2.1.1",
"ky": "^1.7.5",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.78.0",
"react-icons": "^5.5.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.4",
"react-scripts": "5.0.1",
"react-toastify": "^11.0.5",
"web-vitals": "^2.1.4"
"tailwind-merge": "^3.6.0",
"web-vitals": "^2.1.4",
"zod": "^4.4.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"devDependencies": {
"@commitlint/cli": "^21.0.2",
"@commitlint/config-conventional": "^21.0.2",
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.60.0",
"@tailwindcss/vite": "^4.1.11",
"@types/node": "^22.15.30",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.2",
"@vitest/coverage-v8": "^3.2.4",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
"husky": "^9.1.7",
"jsdom": "^26.1.0",
"lint-staged": "^16.4.0",
"prettier": "^3.8.4",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
"typescript-eslint": "^8.61.0",
"vite": "^6.3.5",
"vitest": "^3.2.4"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"engines": {
"node": ">=20"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Seahaven - Officetrax</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -0,0 +1,23 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
export const defaultRouterFile = path.resolve(scriptDir, "../src/router.ts");
/** Strip `/src/app` prefix from generouted Path types — views live under src/app/, not src/pages/. */
export function fixGeneroutedRouterPaths(routerFile = defaultRouterFile) {
const content = fs.readFileSync(routerFile, "utf8");
const fixed = content.replace(/`\/+src\/app/g, "`").replace(/"\/+src\/app/g, '"/');
if (fixed !== content) {
fs.writeFileSync(routerFile, fixed);
}
}
const isMain =
process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
if (isMain) {
fixGeneroutedRouterPaths();
}

269
scripts/migrate-routes.mjs Normal file
View file

@ -0,0 +1,269 @@
import fs from "node:fs";
import path from "node:path";
const root = path.resolve(import.meta.dirname, "..");
const src = path.join(root, "src");
const app = path.join(src, "app");
const routesSrc = path.join(app, "routes");
function read(file) {
return fs.readFileSync(file, "utf8");
}
function write(file, content) {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, content, "utf8");
}
function rmrf(dir) {
if (!fs.existsSync(dir)) return;
fs.rmSync(dir, { recursive: true, force: true });
}
function toDefaultExport(content, exportName) {
let result = content;
if (exportName) {
result = result.replace(
new RegExp(`export function ${exportName}\\b`),
`export default function ${exportName}`,
);
result = result.replace(
new RegExp(`export const ${exportName}\\b`),
`export default function ${exportName}`,
);
}
return result
.replace(/^export \{ ([A-Za-z]+) as default \} from .+;\s*$/m, "")
.trimEnd() + "\n";
}
function copyRoutesTree() {
const entries = ["(auth)", "(protected)", "v"];
for (const entry of entries) {
const from = path.join(routesSrc, entry);
const to = path.join(app, entry);
if (!fs.existsSync(from)) continue;
rmrf(to);
fs.cpSync(from, to, { recursive: true });
}
}
function updateProtectedLayout() {
const layoutPath = path.join(app, "(protected)", "_layout.tsx");
let content = read(layoutPath);
content = content.replace(
"export default function ProtectedLayout()",
"export default function AdminShellLayout()",
);
write(layoutPath, content);
}
function mergePage(sourceRel, destRel, exportName, importReplacements = [], { namedOnly = false } = {}) {
const source = path.join(app, sourceRel);
if (!fs.existsSync(source)) {
console.warn("Missing source:", sourceRel);
return;
}
let content = read(source);
for (const [from, to] of importReplacements) {
content = content.split(from).join(to);
}
if (!namedOnly) {
content = toDefaultExport(content, exportName);
}
write(path.join(app, destRel), content.endsWith("\n") ? content : content + "\n");
}
function copyFile(sourceRel, destRel, importReplacements = []) {
mergePage(sourceRel, destRel, null, importReplacements, { namedOnly: true });
}
function writeReExport(destRel, componentPath, { nestedEdit = false } = {}) {
const importPath = nestedEdit ? componentPath.replace(/^\.\//, "../") : componentPath;
write(path.join(app, destRel), `export { default } from "${importPath}";\n`);
}
// --- infra ---
write(path.join(src, "app-root.tsx"), read(path.join(app, "app-root.tsx")));
let main = read(path.join(src, "main.tsx"));
main = main.replace('@/app/app-root', '@/app-root');
write(path.join(src, "main.tsx"), main);
let vite = read(path.join(root, "vite.config.ts"));
vite = vite.replace(
'routes: "./src/app/routes/**/[\\w[-]*.{jsx,tsx}"',
'routes: "./src/app/**/[\\w[-]*.{jsx,tsx}"',
);
vite = vite.replace(
'modals: "./src/app/routes/**/[+]*.{jsx,tsx}"',
'modals: "./src/app/**/[+]*.{jsx,tsx}"',
);
write(path.join(root, "vite.config.ts"), vite);
copyRoutesTree();
updateProtectedLayout();
// --- accounts pilot ---
mergePage("accounts/accounts-list-page.tsx", "(protected)/accounts/index.tsx", "AccountsListPage");
mergePage("accounts/account-form-page.tsx", "(protected)/accounts/_components/account-form.tsx", "AccountFormPage");
writeReExport("(protected)/accounts/new.tsx", "./_components/account-form");
writeReExport("(protected)/accounts/edit/[id].tsx", "./_components/account-form");
// --- contacts, employees, locations ---
mergePage("contacts/contacts-list-page.tsx", "(protected)/contacts/index.tsx", "ContactsListPage");
mergePage("contacts/contact-form-page.tsx", "(protected)/contacts/_components/contact-form.tsx", "ContactFormPage");
writeReExport("(protected)/contacts/new.tsx", "./_components/contact-form");
writeReExport("(protected)/contacts/edit/[id].tsx", "./_components/contact-form");
mergePage("employees/employees-list-page.tsx", "(protected)/employees/index.tsx", "EmployeesListPage");
mergePage("employees/employee-form-page.tsx", "(protected)/employees/_components/employee-form.tsx", "EmployeeFormPage");
writeReExport("(protected)/employees/new.tsx", "./_components/employee-form");
writeReExport("(protected)/employees/edit/[id].tsx", "./_components/employee-form");
mergePage("locations/locations-list-page.tsx", "(protected)/locations/index.tsx", "LocationsListPage");
mergePage("locations/location-form-page.tsx", "(protected)/locations/_components/location-form.tsx", "LocationFormPage");
writeReExport("(protected)/locations/new.tsx", "./_components/location-form");
writeReExport("(protected)/locations/edit/[id].tsx", "./_components/location-form");
// --- assets, vendors, followups, pmschedules ---
mergePage("assets/assets-list-page.tsx", "(protected)/assets/index.tsx", "AssetsListPage");
mergePage("assets/asset-form-page.tsx", "(protected)/assets/_components/asset-form.tsx", "AssetFormPage");
writeReExport("(protected)/assets/new.tsx", "./_components/asset-form");
writeReExport("(protected)/assets/edit/[id].tsx", "./_components/asset-form");
mergePage("vendors/vendors-list-page.tsx", "(protected)/vendors/index.tsx", "VendorsListPage");
mergePage(
"vendors/vendor-form-page.tsx",
"(protected)/vendors/_components/vendor-form.tsx",
"VendorFormPage",
[["@/app/vendors/components/vendor-portal-token-panel", "./vendor-portal-token-panel"]],
);
copyFile(
"vendors/components/vendor-portal-token-panel.tsx",
"(protected)/vendors/_components/vendor-portal-token-panel.tsx",
);
writeReExport("(protected)/vendors/new.tsx", "./_components/vendor-form");
writeReExport("(protected)/vendors/edit/[id].tsx", "./_components/vendor-form");
mergePage("follow-ups/follow-ups-list-page.tsx", "(protected)/followups/index.tsx", "FollowUpsListPage");
mergePage("follow-ups/follow-up-form-page.tsx", "(protected)/followups/_components/follow-up-form.tsx", "FollowUpFormPage");
writeReExport("(protected)/followups/new.tsx", "./_components/follow-up-form");
writeReExport("(protected)/followups/edit/[id].tsx", "./_components/follow-up-form");
mergePage("pm-schedules/pm-schedules-list-page.tsx", "(protected)/pmschedules/index.tsx", "PmSchedulesListPage");
mergePage("pm-schedules/pm-schedule-form-page.tsx", "(protected)/pmschedules/_components/pm-schedule-form.tsx", "PmScheduleFormPage");
writeReExport("(protected)/pmschedules/new.tsx", "./_components/pm-schedule-form");
writeReExport("(protected)/pmschedules/edit/[id].tsx", "./_components/pm-schedule-form");
// --- calendar, uplifts, vendor-pos, settings, dashboard, login ---
mergePage("calendar/calendar-page.tsx", "(protected)/calendar/index.tsx", "CalendarPage");
mergePage("calendar/event-form-page.tsx", "(protected)/calendar/_components/event-form.tsx", "EventFormPage");
writeReExport("(protected)/calendar/new.tsx", "./_components/event-form");
writeReExport("(protected)/calendar/edit/[id].tsx", "./_components/event-form");
mergePage("uplifts/uplift-queue-page.tsx", "(protected)/uplifts/index.tsx", "UpliftQueuePage");
mergePage("vendor-pos/vendor-pos-list-page.tsx", "(protected)/vendor-pos/index.tsx", "VendorPosListPage");
mergePage("vendor-pos/vendor-po-view-page.tsx", "(protected)/vendor-pos/[id].tsx", "VendorPoViewPage");
mergePage("settings/dropdown-options-page.tsx", "(protected)/settings/dropdowns.tsx", "DropdownOptionsPage");
mergePage("settings/task-templates-page.tsx", "(protected)/settings/task-templates.tsx", "TaskTemplatesPage");
mergePage("dashboard/dashboard-page.tsx", "(protected)/index.tsx", "DashboardPage");
mergePage("dashboard/dashboard-page.tsx", "(protected)/dashboard.tsx", "DashboardPage");
mergePage("login/login-page.tsx", "(auth)/login.tsx", "LoginPage");
// --- workorders ---
mergePage("work-orders/work-orders-list-page.tsx", "(protected)/workorders/index.tsx", "WorkOrdersListPage");
mergePage("work-orders/work-order-form-page.tsx", "(protected)/workorders/_components/work-order-form.tsx", "WorkOrderFormPage");
writeReExport("(protected)/workorders/new.tsx", "./_components/work-order-form");
writeReExport("(protected)/workorders/edit/[id].tsx", "./_components/work-order-form");
mergePage(
"work-orders/work-order-view-page.tsx",
"(protected)/workorders/[id].tsx",
"WorkOrderViewPage",
[
["@/app/work-orders/components/dispatch-create-modal", "./_components/dispatch-create-modal"],
["@/app/work-orders/components/dispatch-detail-modal", "./_components/dispatch-detail-modal"],
],
);
copyFile(
"work-orders/components/dispatch-create-modal.tsx",
"(protected)/workorders/_components/dispatch-create-modal.tsx",
);
copyFile(
"work-orders/components/dispatch-detail-modal.tsx",
"(protected)/workorders/_components/dispatch-detail-modal.tsx",
);
// --- vendor portal ---
const vpComponents = path.join(app, "v", "_components");
fs.mkdirSync(vpComponents, { recursive: true });
copyFile("vendor-portal/vendor-portal-provider.tsx", "v/_components/vendor-portal-provider.tsx");
copyFile("vendor-portal/components/signature-pad.tsx", "v/_components/signature-pad.tsx");
fs.copyFileSync(
path.join(app, "vendor-portal/vendor-portal.css"),
path.join(vpComponents, "vendor-portal.css"),
);
let vpLayout = read(path.join(app, "vendor-portal/vendor-portal-layout.tsx"));
vpLayout = vpLayout
.replace("@/app/vendor-portal/vendor-portal-provider", "@/app/v/_components/vendor-portal-provider")
.replace("@/app/vendor-portal/vendor-portal.css", "@/app/v/_components/vendor-portal.css")
.replace("export function VendorPortalLayout", "export default function VendorPortalLayout");
write(path.join(app, "v", "[token]", "_layout.tsx"), vpLayout);
mergePage(
"vendor-portal/vendor-portal-dashboard-page.tsx",
"v/[token]/dashboard.tsx",
"VendorPortalDashboardPage",
[["@/app/vendor-portal/vendor-portal-provider", "@/app/v/_components/vendor-portal-provider"]],
);
mergePage(
"vendor-portal/vendor-portal-pos-page.tsx",
"v/[token]/pos.tsx",
"VendorPortalPosPage",
[["@/app/vendor-portal/vendor-portal-provider", "@/app/v/_components/vendor-portal-provider"]],
);
mergePage(
"vendor-portal/vendor-portal-dispatch-page.tsx",
"v/[token]/dispatch/[id].tsx",
"VendorPortalDispatchPage",
[
["@/app/vendor-portal/components/signature-pad", "@/app/v/_components/signature-pad"],
["@/app/vendor-portal/vendor-portal-provider", "@/app/v/_components/vendor-portal-provider"],
],
);
// --- cleanup old dirs ---
const toDelete = [
path.join(app, "routes"),
path.join(app, "app-root.tsx"),
path.join(app, "store.js"),
path.join(app, "slices"),
path.join(app, "accounts"),
path.join(app, "contacts"),
path.join(app, "employees"),
path.join(app, "locations"),
path.join(app, "assets"),
path.join(app, "vendors"),
path.join(app, "follow-ups"),
path.join(app, "pm-schedules"),
path.join(app, "calendar"),
path.join(app, "uplifts"),
path.join(app, "vendor-pos"),
path.join(app, "settings"),
path.join(app, "dashboard"),
path.join(app, "login"),
path.join(app, "work-orders"),
path.join(app, "vendor-portal"),
];
for (const dir of toDelete) {
rmrf(dir);
}
console.log("Migration complete.");

29
tsconfig.json Normal file
View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"allowJs": true,
"checkJs": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

20
tsconfig.node.json Normal file
View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"strict": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "./node_modules/.tmp/tsconfig.node",
"types": ["node"]
},
"include": ["vite.config.ts", "vitest.config.ts", "scripts/fix-router-paths.ts"]
}

77
vite.config.ts Normal file
View file

@ -0,0 +1,77 @@
import generouted from "@generouted/react-router/plugin";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "node:path";
import { defineConfig, loadEnv, transformWithEsbuild } from "vite";
import { defaultRouterFile, fixGeneroutedRouterPaths } from "./scripts/fix-router-paths.ts";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
return {
plugins: [
{
name: "load-js-files-as-jsx",
async transform(code, id) {
if (!/\/src\/.*\.js$/.test(id)) {
return null;
}
return transformWithEsbuild(code, id, {
loader: "jsx",
jsx: "automatic",
});
},
},
react(),
tailwindcss(),
generouted({
source: {
routes: "./src/app/**/[\\w[-]*.{jsx,tsx}",
modals: "./src/app/**/[+]*.{jsx,tsx}",
},
output: "./src/router.ts",
}),
{
name: "generouted-app-paths",
enforce: "post",
buildEnd() {
fixGeneroutedRouterPaths(defaultRouterFile);
},
configureServer(server) {
const scheduleFix = () => {
setTimeout(() => fixGeneroutedRouterPaths(defaultRouterFile), 50);
};
server.watcher.on("add", scheduleFix);
server.watcher.on("change", scheduleFix);
server.watcher.on("unlink", scheduleFix);
},
},
],
optimizeDeps: {
esbuildOptions: {
loader: {
".js": "jsx",
},
},
},
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
server: {
port: 3000,
proxy: {
"/api": {
target: env.VITE_API_TARGET ?? "http://localhost:5141",
changeOrigin: true,
secure: false,
},
},
},
build: {
outDir: "dist",
},
};
});

19
vitest.config.ts Normal file
View file

@ -0,0 +1,19 @@
import react from "@vitejs/plugin-react";
import path from "node:path";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
test: {
environment: "jsdom",
setupFiles: ["./src/test/setup.ts"],
globals: false,
include: ["src/test/**/*.test.{ts,tsx}", "src/test/**/*.integration.test.{ts,tsx}"],
exclude: ["e2e/**", "node_modules/**"],
},
});