@aamini/config 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/msw.d.mts +3 -0
  2. package/dist/msw.mjs +3 -0
  3. package/dist/playwright.shared.config.d.mts +8 -0
  4. package/dist/playwright.shared.config.mjs +47 -0
  5. package/dist/playwright.shared.config.mjs.map +1 -0
  6. package/dist/setup/styles.d.mts +1 -0
  7. package/dist/setup/styles.mjs +3 -0
  8. package/dist/test/browser.d.mts +15 -0
  9. package/dist/test/browser.mjs +26 -0
  10. package/dist/test/browser.mjs.map +1 -0
  11. package/dist/test/database.d.mts +22 -0
  12. package/dist/test/database.mjs +18 -0
  13. package/dist/test/database.mjs.map +1 -0
  14. package/dist/test/db.d.mts +21 -0
  15. package/dist/test/db.mjs +53 -0
  16. package/dist/test/db.mjs.map +1 -0
  17. package/dist/test/playwright.d.mts +7 -0
  18. package/dist/test/playwright.mjs +15 -0
  19. package/dist/test/playwright.mjs.map +1 -0
  20. package/dist/test/postgres.d.mts +14 -0
  21. package/dist/test/postgres.mjs +25 -0
  22. package/dist/test/postgres.mjs.map +1 -0
  23. package/dist/test/server.d.mts +15 -0
  24. package/dist/test/server.mjs +23 -0
  25. package/dist/test/server.mjs.map +1 -0
  26. package/dist/test/visual-page.d.mts +36 -0
  27. package/dist/test/visual-page.mjs +30 -0
  28. package/dist/test/visual-page.mjs.map +1 -0
  29. package/dist/vite.shared.d.mts +14 -0
  30. package/dist/vite.shared.mjs +232 -0
  31. package/dist/vite.shared.mjs.map +1 -0
  32. package/dist/vitest.shared.d.mts +12 -0
  33. package/dist/vitest.shared.mjs +122 -0
  34. package/dist/vitest.shared.mjs.map +1 -0
  35. package/msw.ts +5 -0
  36. package/package.json +99 -0
  37. package/playwright.shared.config.ts +71 -0
  38. package/setup/styles.ts +4 -0
  39. package/test/browser.ts +31 -0
  40. package/test/database.ts +38 -0
  41. package/test/db.ts +80 -0
  42. package/test/playwright.ts +17 -0
  43. package/test/postgres.ts +43 -0
  44. package/test/server.ts +31 -0
  45. package/test/visual-page.ts +57 -0
  46. package/tsconfig/tsconfig.astro.json +34 -0
  47. package/tsconfig/tsconfig.lib.json +19 -0
  48. package/tsconfig/tsconfig.react.json +16 -0
  49. package/tsconfig/tsconfig.strict.json +16 -0
  50. package/tsconfig/tsconfig.tanstack.json +17 -0
  51. package/virtual-modules.d.ts +6 -0
  52. package/vite.config.ts +53 -0
  53. package/vite.shared.ts +298 -0
  54. package/vitest.shared.ts +183 -0
package/test/db.ts ADDED
@@ -0,0 +1,80 @@
1
+ // oxlint-disable typescript-eslint/triple-slash-reference
2
+ /// <reference path="../virtual-modules.d.ts" />
3
+ // oxlint-disable no-empty-pattern
4
+ import handlers from '@test/handlers'
5
+ import { setupServer, SetupServer } from 'msw/node'
6
+ import { resolve } from 'node:path'
7
+ import { test as baseTest } from 'vitest'
8
+ import type { Database } from './database'
9
+ import { seedDatabase } from './database'
10
+
11
+ const DEFAULT_SCHEMA_PATH = 'src/db/tables.ts'
12
+ const DEFAULT_MIGRATIONS_PATH = 'src/db/migrations'
13
+ const DEFAULT_IMAGE = 'postgres:17'
14
+
15
+ export interface DbFixture {
16
+ server: SetupServer
17
+ _cleanup: void
18
+ db: Database
19
+ seedFunction: (db: Database) => Promise<void>
20
+ }
21
+
22
+ const mswServer = setupServer(...handlers)
23
+
24
+ export const test = baseTest.extend<DbFixture>({
25
+ // MSW Server fixture
26
+ server: [
27
+ async ({}, use) => {
28
+ mswServer.listen({ onUnhandledRequest: 'bypass' })
29
+ await use(mswServer)
30
+ mswServer.close()
31
+ },
32
+ { auto: true, scope: 'worker' },
33
+ ],
34
+ _cleanup: [
35
+ async ({ server }, use) => {
36
+ await use()
37
+ server.resetHandlers()
38
+ },
39
+ { auto: true },
40
+ ],
41
+
42
+ // Database fixtures
43
+ seedFunction: [async ({}, use) => use(async () => {}), { scope: 'file' }],
44
+ db: [
45
+ async ({ seedFunction }: Pick<DbFixture, 'seedFunction'>, use) => {
46
+ const { PostgreSqlContainer } = await import('@testcontainers/postgresql')
47
+ const { drizzle } = await import('drizzle-orm/node-postgres')
48
+ const { Wait } = await import('testcontainers')
49
+ const { Pool } = await import('pg')
50
+
51
+ // Auto-discover schema
52
+ const schemaPath = resolve(process.cwd(), DEFAULT_SCHEMA_PATH)
53
+
54
+ // Resolve migrations folder relative to cwd (app root)
55
+ const migrationsFolder = resolve(process.cwd(), DEFAULT_MIGRATIONS_PATH)
56
+
57
+ const container = await new PostgreSqlContainer(DEFAULT_IMAGE)
58
+ .withWaitStrategy(Wait.forHealthCheck())
59
+ .start()
60
+ const db = drizzle({
61
+ client: new Pool({ connectionString: container.getConnectionUri() }),
62
+ }) as Database
63
+
64
+ try {
65
+ await seedDatabase(db, { schemaPath, migrationsFolder, seedFunction })
66
+ await use(db)
67
+ } finally {
68
+ await db.$client.end()
69
+ await container.stop()
70
+ }
71
+ },
72
+ { scope: 'file' },
73
+ ],
74
+ })
75
+
76
+ export function initDb(seedFunction: (db: Database) => Promise<void>) {
77
+ test.override({
78
+ seedFunction: [async ({}, use) => use(seedFunction), { scope: 'file' }],
79
+ })
80
+ }
@@ -0,0 +1,17 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import type { Page } from '@playwright/test'
3
+
4
+ export async function mockJsonRoute(
5
+ page: Page,
6
+ url: string | RegExp,
7
+ fixtureUrl: URL,
8
+ ) {
9
+ await page.route(url, async (route) => {
10
+ const body = await readFile(fixtureUrl, 'utf8')
11
+
12
+ await route.fulfill({
13
+ contentType: 'application/json',
14
+ body,
15
+ })
16
+ })
17
+ }
@@ -0,0 +1,43 @@
1
+ import {
2
+ PostgreSqlContainer,
3
+ type StartedPostgreSqlContainer,
4
+ } from '@testcontainers/postgresql'
5
+ import { Wait } from 'testcontainers'
6
+
7
+ type State = {
8
+ container: StartedPostgreSqlContainer
9
+ databaseUrl: string
10
+ }
11
+
12
+ let state: State | undefined
13
+
14
+ export async function ensurePostgresContainer() {
15
+ const databaseUrl = process.env.DATABASE_URL
16
+ if (databaseUrl) {
17
+ return {
18
+ databaseUrl,
19
+ }
20
+ }
21
+
22
+ if (!state) {
23
+ const container = await new PostgreSqlContainer('postgres:17')
24
+ .withDatabase('imdbgraph')
25
+ .withUsername('imdbgraph')
26
+ .withPassword('imdbgraph')
27
+ .withWaitStrategy(Wait.forHealthCheck())
28
+ .start()
29
+
30
+ state = {
31
+ container,
32
+ databaseUrl: container.getConnectionUri(),
33
+ }
34
+ }
35
+
36
+ return state
37
+ }
38
+
39
+ export async function stopPostgresContainer() {
40
+ if (!state) return
41
+ await state.container.stop()
42
+ state = undefined
43
+ }
package/test/server.ts ADDED
@@ -0,0 +1,31 @@
1
+ // oxlint-disable typescript-eslint/triple-slash-reference
2
+ /// <reference path="../virtual-modules.d.ts" />
3
+ // oxlint-disable no-empty-pattern
4
+ import handlers from '@test/handlers'
5
+ import { SetupServer, setupServer } from 'msw/node'
6
+ import { test as baseTest } from 'vitest'
7
+
8
+ export interface MswServerFixture {
9
+ server: SetupServer
10
+ _cleanup: void
11
+ }
12
+
13
+ const mswServer = setupServer(...handlers)
14
+
15
+ export const test = baseTest.extend<MswServerFixture>({
16
+ server: [
17
+ async ({}, use) => {
18
+ mswServer.listen({ onUnhandledRequest: 'bypass' })
19
+ await use(mswServer)
20
+ mswServer.close()
21
+ },
22
+ { auto: true, scope: 'worker' },
23
+ ],
24
+ _cleanup: [
25
+ async ({ server }, use) => {
26
+ await use()
27
+ server.resetHandlers()
28
+ },
29
+ { auto: true },
30
+ ],
31
+ })
@@ -0,0 +1,57 @@
1
+ import { createElement, type ComponentType, type ReactNode } from 'react'
2
+ import { expect } from 'vitest'
3
+ import { page, type Locator } from 'vitest/browser'
4
+ import { render, type RenderResult } from 'vitest-browser-react'
5
+
6
+ interface Viewport {
7
+ width: number
8
+ height: number
9
+ }
10
+
11
+ interface ExpectComponentScreenshotOptions {
12
+ component: ComponentType
13
+ name: string
14
+ waitFor: (screen: RenderResult) => Locator
15
+ prepare?: ((screen: RenderResult) => void | Promise<void>) | undefined
16
+ target?: ((screen: RenderResult) => Locator) | undefined
17
+ setup?: (() => void | Promise<void>) | undefined
18
+ wrapper?: ComponentType<{ children: ReactNode }> | undefined
19
+ viewport?: Viewport | undefined
20
+ fullPage?: boolean | undefined
21
+ }
22
+
23
+ export async function expectComponentScreenshot({
24
+ component,
25
+ name,
26
+ waitFor,
27
+ prepare,
28
+ target = (screen) => screen.locator,
29
+ setup,
30
+ wrapper,
31
+ viewport = { width: 1280, height: 720 },
32
+ fullPage = true,
33
+ }: ExpectComponentScreenshotOptions) {
34
+ localStorage.clear()
35
+ document.body.replaceChildren()
36
+ await setup?.()
37
+ await page.viewport(viewport.width, viewport.height)
38
+
39
+ const container = document.body.appendChild(document.createElement('div'))
40
+ container.style.width = '100vw'
41
+ container.style.minHeight = '100vh'
42
+
43
+ const screen = await render(
44
+ createElement(component),
45
+ wrapper ? { container, wrapper } : { container },
46
+ )
47
+
48
+ await prepare?.(screen)
49
+ await expect.element(waitFor(screen)).toBeVisible()
50
+ await document.fonts.ready
51
+ if (fullPage) {
52
+ await page.viewport(viewport.width, Math.ceil(container.scrollHeight))
53
+ }
54
+ await expect.element(target(screen)).toMatchScreenshot(`${name}.png`, {
55
+ screenshotOptions: { scale: 'css' },
56
+ })
57
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ // TODO: remove
3
+ "$schema": "https://json.schemastore.org/tsconfig",
4
+ "extends": "./tsconfig.strict.json",
5
+ "compilerOptions": {
6
+ // Enable top-level await, and other modern ESM features.
7
+ "target": "ESNext",
8
+ "module": "ESNext",
9
+ // Enable module resolution without file extensions on relative paths, for things like npm package imports.
10
+ "moduleResolution": "Bundler",
11
+ // Allow importing TypeScript files using their native extension (.ts(x)).
12
+ "allowImportingTsExtensions": true,
13
+ // Enable JSON imports.
14
+ "resolveJsonModule": true,
15
+ // Enforce the usage of type-only imports when needed, which helps avoiding bundling issues.
16
+ "verbatimModuleSyntax": true,
17
+ // Ensure that each file can be transpiled without relying on other imports.
18
+ // This is redundant with the previous option, however it ensures that it's on even if someone disable `verbatimModuleSyntax`
19
+ "isolatedModules": true,
20
+ // Astro directly run TypeScript code, no transpilation needed.
21
+ "noEmit": true,
22
+ // Report an error when importing a file using a casing different from another import of the same file.
23
+ "forceConsistentCasingInFileNames": true,
24
+ // Properly support importing CJS modules in ESM
25
+ "esModuleInterop": true,
26
+ // Skip typechecking libraries and .d.ts files
27
+ "skipLibCheck": true,
28
+ // Allow JavaScript files to be imported
29
+ "allowJs": true,
30
+ // Allow JSX files (or files that are internally considered JSX, like Astro files) to be imported inside `.js` and `.ts` files.
31
+ "jsx": "react-jsx",
32
+ "jsxImportSource": "react"
33
+ }
34
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./tsconfig.strict.json",
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "esModuleInterop": true,
9
+ "incremental": false,
10
+ "isolatedModules": true,
11
+ "lib": ["es2022", "DOM", "DOM.Iterable"],
12
+ "module": "NodeNext",
13
+ "moduleDetection": "force",
14
+ "moduleResolution": "NodeNext",
15
+ "resolveJsonModule": true,
16
+ "skipLibCheck": true,
17
+ "target": "ES2022"
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./tsconfig.strict.json",
4
+ "compilerOptions": {
5
+ "target": "ES2022",
6
+ "jsx": "react-jsx",
7
+ "module": "ESNext",
8
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": false,
14
+ "noEmit": true
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "skipLibCheck": true,
4
+ "strict": true,
5
+ "noUnusedLocals": true,
6
+ "noUnusedParameters": true,
7
+ "noFallthroughCasesInSwitch": true,
8
+ "noUncheckedSideEffectImports": true,
9
+ "noImplicitOverride": true,
10
+ "noImplicitReturns": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "exactOptionalPropertyTypes": true,
13
+ "allowUnreachableCode": false,
14
+ "allowUnusedLabels": false
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "./tsconfig.strict.json",
4
+ "compilerOptions": {
5
+ "target": "ES2022",
6
+ "jsx": "react-jsx",
7
+ "module": "ESNext",
8
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
9
+ "types": ["vite/client"],
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": false,
15
+ "noEmit": true
16
+ }
17
+ }
@@ -0,0 +1,6 @@
1
+ declare module '@test/handlers' {
2
+ import type { RequestHandler } from 'msw'
3
+ const handlers: RequestHandler[]
4
+ export default handlers
5
+ }
6
+ declare module '@test/styles' {}
package/vite.config.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { defineConfig } from 'vite-plus'
2
+
3
+ export default defineConfig({
4
+ pack: {
5
+ entry: [
6
+ 'vite.shared.ts',
7
+ 'vitest.shared.ts',
8
+ 'playwright.shared.config.ts',
9
+ 'msw.ts',
10
+ 'test/browser.ts',
11
+ 'test/database.ts',
12
+ 'test/db.ts',
13
+ 'test/playwright.ts',
14
+ 'test/postgres.ts',
15
+ 'test/server.ts',
16
+ 'test/visual-page.ts',
17
+ 'setup/styles.ts',
18
+ ],
19
+ dts: true,
20
+ format: ['esm'],
21
+ sourcemap: true,
22
+ deps: {
23
+ skipNodeModulesBundle: true,
24
+ neverBundle: [
25
+ '@playwright/test',
26
+ '@rolldown/plugin-babel',
27
+ '@tailwindcss/vite',
28
+ '@tanstack/devtools-vite',
29
+ '@tanstack/react-start',
30
+ '@test/handlers',
31
+ '@test/styles',
32
+ '@testing-library/jest-dom',
33
+ '@testcontainers/postgresql',
34
+ '@vitejs/plugin-react',
35
+ '@vitest/browser-playwright',
36
+ '@voidzero-dev/vite-plus-core',
37
+ '@voidzero-dev/vite-plus-test',
38
+ 'drizzle-orm',
39
+ 'drizzle-seed',
40
+ 'msw',
41
+ 'nitro',
42
+ 'pg',
43
+ 'react',
44
+ 'testcontainers',
45
+ 'vite',
46
+ 'vite-plus',
47
+ 'vite-plugin-svgr',
48
+ 'vitest',
49
+ 'vitest-browser-react',
50
+ ],
51
+ },
52
+ },
53
+ })
package/vite.shared.ts ADDED
@@ -0,0 +1,298 @@
1
+ import { nitro } from 'nitro/vite'
2
+ import { devtools } from '@tanstack/devtools-vite'
3
+ import { tanstackStart } from '@tanstack/react-start/plugin/vite'
4
+ import viteReact, { reactCompilerPreset } from '@vitejs/plugin-react'
5
+ import tailwindcss from '@tailwindcss/vite'
6
+ import babel from '@rolldown/plugin-babel'
7
+ import svgr from 'vite-plugin-svgr'
8
+ import { existsSync } from 'node:fs'
9
+ import { dirname, resolve } from 'node:path'
10
+ import { fileURLToPath } from 'node:url'
11
+ import type { Plugin } from 'vite'
12
+ import {
13
+ defineConfig as defineVitestConfig,
14
+ mergeConfig,
15
+ type TestProjectConfiguration,
16
+ type TestProjectInlineConfiguration,
17
+ } from 'vitest/config'
18
+ import { defineConfig } from 'vite'
19
+ import { playwright } from 'vite-plus/test/browser-playwright'
20
+
21
+ const HANDLER_LOCATIONS = [
22
+ '__mocks__/handlers.ts',
23
+ '__mocks__/handlers.js',
24
+ 'src/mocks/handlers.ts',
25
+ 'src/mocks/handlers.js',
26
+ ]
27
+
28
+ const STYLES_LOCATIONS = ['src/styles.css', 'src/index.css', 'src/app.css']
29
+
30
+ function mswHandlersPlugin(root: string): Plugin {
31
+ const virtualModuleId = '@test/handlers'
32
+ const resolvedVirtualModuleId = '\0' + virtualModuleId
33
+
34
+ return {
35
+ name: 'msw-handlers-resolver',
36
+ resolveId(id) {
37
+ if (id === virtualModuleId) {
38
+ return resolvedVirtualModuleId
39
+ }
40
+ return undefined
41
+ },
42
+ load(id) {
43
+ if (id === resolvedVirtualModuleId) {
44
+ for (const location of HANDLER_LOCATIONS) {
45
+ const fullPath = resolve(root, location)
46
+ if (existsSync(fullPath)) {
47
+ return `export { default } from '${fullPath}'`
48
+ }
49
+ }
50
+ throw new Error(
51
+ `No MSW handlers found. Expected one of:\n${HANDLER_LOCATIONS.map((l) => ` - ${resolve(root, l)}`).join('\n')}`,
52
+ )
53
+ }
54
+ return undefined
55
+ },
56
+ }
57
+ }
58
+
59
+ function stylesPlugin(root: string): Plugin {
60
+ const virtualModuleId = '@test/styles'
61
+ const resolvedVirtualModuleId = '\0' + virtualModuleId
62
+
63
+ return {
64
+ name: 'styles-resolver',
65
+ resolveId(id) {
66
+ if (id === virtualModuleId) {
67
+ return resolvedVirtualModuleId
68
+ }
69
+ return undefined
70
+ },
71
+ load(id) {
72
+ if (id === resolvedVirtualModuleId) {
73
+ for (const location of STYLES_LOCATIONS) {
74
+ const fullPath = resolve(root, location)
75
+ if (existsSync(fullPath)) {
76
+ return `import '${fullPath}'`
77
+ }
78
+ }
79
+ throw new Error(
80
+ `No styles file found. Expected one of:\n${STYLES_LOCATIONS.map((l) => ` - ${resolve(root, l)}`).join('\n')}`,
81
+ )
82
+ }
83
+ return undefined
84
+ },
85
+ }
86
+ }
87
+
88
+ const stylesSetup = new URL('setup/styles.ts', import.meta.url).pathname
89
+
90
+ function mergeProjectConfig(
91
+ base: TestProjectConfiguration,
92
+ overrides: TestProjectConfiguration = {},
93
+ ): TestProjectConfiguration {
94
+ return mergeConfig(
95
+ base as Record<string, any>,
96
+ overrides as Record<string, any>,
97
+ ) as TestProjectConfiguration
98
+ }
99
+
100
+ interface ProjectOverrides {
101
+ server?: TestProjectConfiguration
102
+ browser?: TestProjectConfiguration
103
+ }
104
+
105
+ function createBaseConfig(
106
+ root: string,
107
+ overrides: TestProjectInlineConfiguration = {},
108
+ projectOverrides: ProjectOverrides = {},
109
+ ) {
110
+ const localSetup = resolve(root, 'vitest.setup.ts')
111
+ const hasLocalSetup = existsSync(localSetup)
112
+
113
+ return mergeConfig(
114
+ defineVitestConfig({
115
+ root,
116
+ resolve: {
117
+ tsconfigPaths: true,
118
+ dedupe: ['react', 'react-dom'],
119
+ alias: [
120
+ { find: '@/mocks', replacement: resolve(root, '__mocks__') },
121
+ { find: '@', replacement: resolve(root, 'src') },
122
+ ],
123
+ },
124
+ plugins: [
125
+ tailwindcss(),
126
+ viteReact(),
127
+ svgr({
128
+ include: '**/*.svg',
129
+ svgrOptions: { exportType: 'default' },
130
+ }),
131
+ ],
132
+ test: {
133
+ passWithNoTests: true,
134
+ exclude: [
135
+ '**/node_modules/**',
136
+ '**/dist/**',
137
+ '**/.output/**',
138
+ '**/.vercel/**',
139
+ '**/cypress/**',
140
+ '**/.{idea,git,cache,output,temp}/**',
141
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
142
+ '**/e2e/**',
143
+ ],
144
+ projects: [
145
+ {
146
+ extends: true,
147
+ test: {
148
+ name: 'unit',
149
+ include: ['src/**/*.test.unit.ts'],
150
+ },
151
+ },
152
+ mergeProjectConfig(
153
+ {
154
+ extends: true,
155
+ plugins: [mswHandlersPlugin(root)],
156
+ test: {
157
+ name: 'server',
158
+ include: ['src/**/*.test.ts'],
159
+ exclude: ['src/**/*.test.unit.ts', 'src/**/*.test.tsx'],
160
+ testTimeout: 30_000,
161
+ fileParallelism: false,
162
+ setupFiles: hasLocalSetup ? [localSetup] : [],
163
+ },
164
+ } satisfies TestProjectConfiguration,
165
+ projectOverrides.server ?? {},
166
+ ),
167
+ mergeProjectConfig(
168
+ {
169
+ extends: true,
170
+ plugins: [mswHandlersPlugin(root), stylesPlugin(root)],
171
+ test: {
172
+ name: 'browser',
173
+ include: ['src/**/*.test.tsx', 'tests/**/*.test.tsx'],
174
+ setupFiles: [stylesSetup],
175
+ browser: {
176
+ instances: [
177
+ {
178
+ browser: 'chromium',
179
+ },
180
+ ],
181
+ provider: playwright(),
182
+ enabled: true,
183
+ headless: true,
184
+ },
185
+ },
186
+ } satisfies TestProjectConfiguration,
187
+ projectOverrides.browser ?? {},
188
+ ),
189
+ ],
190
+ },
191
+ }),
192
+ overrides,
193
+ )
194
+ }
195
+
196
+ const isVitest = process.env.VITEST === 'true'
197
+
198
+ function createFrameworkConfig(root: string) {
199
+ return defineConfig({
200
+ root,
201
+ resolve: {
202
+ tsconfigPaths: true,
203
+ dedupe: ['react', 'react-dom'],
204
+ alias: [
205
+ { find: '@/mocks', replacement: resolve(root, '__mocks__') },
206
+ { find: '@', replacement: resolve(root, 'src') },
207
+ ],
208
+ },
209
+ ssr: {
210
+ noExternal: ['recharts'],
211
+ },
212
+ plugins: [
213
+ ...(isVitest ? [] : [devtools(), nitro(), tanstackStart()]),
214
+ tailwindcss(),
215
+ viteReact(),
216
+ babel({ presets: [reactCompilerPreset()] }),
217
+ svgr({
218
+ include: '**/*.svg',
219
+ svgrOptions: { exportType: 'default' },
220
+ }),
221
+ ],
222
+ })
223
+ }
224
+
225
+ // Creates a full app config with customized test project overrides
226
+ export function createAppConfig(
227
+ root: string,
228
+ projectOverrides: ProjectOverrides = {},
229
+ overrides: TestProjectInlineConfiguration = {},
230
+ ) {
231
+ return mergeConfig(
232
+ createFrameworkConfig(root),
233
+ createBaseConfig(root, overrides, projectOverrides),
234
+ )
235
+ }
236
+
237
+ // Helper to create config from import.meta.url
238
+ export function baseConfigFor(configUrl: string | URL) {
239
+ const root = dirname(fileURLToPath(configUrl))
240
+ return createAppConfig(root)
241
+ }
242
+
243
+ // Workspace-level shared config (lint, fmt, staged)
244
+ // Cast to any because Vite+ extensions (fmt, lint, staged) aren't in standard Vite types
245
+ export const workspaceConfig = defineConfig({
246
+ fmt: {
247
+ singleQuote: true,
248
+ semi: false,
249
+ useTabs: true,
250
+ printWidth: 80,
251
+ proseWrap: 'always',
252
+ ignorePatterns: [
253
+ '**/.vercel',
254
+ '**/.output',
255
+ '**/env.d.ts',
256
+ 'pnpm-lock.yaml',
257
+ 'routeTree.gen.ts',
258
+ '**/charts/**',
259
+ '**/*.yaml',
260
+ '**/*.yml',
261
+ ],
262
+ },
263
+ lint: {
264
+ plugins: ['typescript', 'react', 'unicorn'],
265
+ options: {
266
+ typeAware: true,
267
+ typeCheck: true,
268
+ },
269
+ rules: {
270
+ 'no-console': ['error', { allow: ['warn', 'error'] }],
271
+ },
272
+ overrides: [
273
+ {
274
+ files: ['apps/**'],
275
+ plugins: ['typescript', 'react'],
276
+ rules: {
277
+ 'react/self-closing-comp': 'error',
278
+ },
279
+ },
280
+ {
281
+ files: ['**/*.test.ts', '**/*.test.tsx'],
282
+ plugins: ['typescript', 'vitest'],
283
+ rules: {
284
+ '@typescript-eslint/no-explicit-any': 'off',
285
+ },
286
+ },
287
+ {
288
+ files: ['**/scripts/**', '**/src/lib/imdb/**'],
289
+ rules: {
290
+ 'no-console': 'off',
291
+ },
292
+ },
293
+ ],
294
+ },
295
+ staged: {
296
+ '*.{js,ts,tsx}': 'vp check --fix',
297
+ },
298
+ } as any)