| 1 | import * as os from "node:os" |
| 2 | import * as path from "node:path" |
| 3 | import { defineConfig, mergeConfig, type ViteUserConfig } from "vitest/config" |
| 4 | |
| 5 | const isDeno = process.versions.deno !== undefined |
| 6 | const isBun = process.versions.bun !== undefined |
| 7 | const isNode = typeof process !== "undefined" && |
| 8 | process.release.name === "node" && |
| 9 | !isDeno && |
| 10 | !isBun |
| 11 | const integrationTestsEnabled = process.env.EFFECT_INTEGRATION_TESTS === "1" |
| 12 | const clusterTestsEnabled = process.env.EFFECT_CLUSTER_TESTS === "1" |
| 13 | |
| 14 | const project = ( |
| 15 | name: string, |
| 16 | directory: string, |
| 17 | include: boolean = true, |
| 18 | config: ViteUserConfig = {}, |
| 19 | projectExclude?: ReadonlyArray<string>, |
| 20 | projectInclude?: ReadonlyArray<string> |
| 21 | ) => { |
| 22 | if (!include) { |
| 23 | return [] |
| 24 | } |
| 25 | |
| 26 | const cfg = mergeConfig({ |
| 27 | root: directory, |
| 28 | test: { name } |
| 29 | }, config) |
| 30 | |
| 31 | const merged = mergeConfig(shared, cfg) |
| 32 | if (projectExclude !== undefined) { |
| 33 | merged.test!.exclude = [...projectExclude] |
| 34 | } |
| 35 | if (projectInclude !== undefined) { |
| 36 | merged.test!.include = [...projectInclude] |
| 37 | } |
| 38 | return [merged] |
| 39 | } |
| 40 | |
| 41 | export const exclude = [ |
| 42 | "**/.*/**", |
| 43 | "**/node_modules/**", |
| 44 | "**/dist/**", |
| 45 | "**/benchmark/**", |
| 46 | "**/bundle/**", |
| 47 | "**/typetest/**", |
| 48 | "**/coverage/**", |
| 49 | "**/test/utils/**", |
| 50 | "**/test/cluster-integration/**", |
| 51 | ...(!integrationTestsEnabled ? ["**/*.integration.test.{ts,tsx}"] : []), |
| 52 | "**/*.d.ts", |
| 53 | "**/*.config.*", |
| 54 | "**/vitest.*" |
| 55 | ] |
| 56 | |
| 57 | const shared: ViteUserConfig = { |
| 58 | optimizeDeps: { |
| 59 | exclude: ["bun:sqlite"] |
| 60 | }, |
| 61 | server: { |
| 62 | watch: { |
| 63 | ignored: exclude |
| 64 | } |
| 65 | }, |
| 66 | resolve: { |
| 67 | tsconfigPaths: true |
| 68 | }, |
| 69 | test: { |
| 70 | exclude, |
| 71 | passWithNoTests: true, |
| 72 | setupFiles: [path.join(import.meta.dirname, "vitest.setup.ts")], |
| 73 | sequence: { |
| 74 | concurrent: true |
| 75 | }, |
| 76 | include: ["test/**/*.test.{ts,tsx}"], |
| 77 | coverage: { |
| 78 | provider: "v8", |
| 79 | reporter: ["html"], |
| 80 | reportsDirectory: "coverage", |
| 81 | exclude |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | export default defineConfig({ |
| 87 | test: { |
| 88 | passWithNoTests: true, |
| 89 | projects: [ |
| 90 | ...project( |
| 91 | "effect", |
| 92 | "packages/effect", |
| 93 | true, |
| 94 | {}, |
| 95 | isBun |
| 96 | ? [ |
| 97 | ...exclude, |
| 98 | // These tests assert Node-specific Web API or stack trace behavior. |
| 99 | "test/reactivity/Atom.test.ts", |
| 100 | "test/schema/Schema.test.ts", |
| 101 | "test/schema/SchemaGetter.test.ts", |
| 102 | "test/schema/toCodec.test.ts", |
| 103 | "test/schema/toDifferJsonPatch.test.ts", |
| 104 | "test/http/HttpEffect.test.ts", |
| 105 | "test/http/HttpServerRequest.test.ts" |
| 106 | ] |
| 107 | : undefined |
| 108 | ), |
| 109 | ...project("@effect/ai-anthropic", "packages/ai/anthropic"), |
| 110 | ...project("@effect/ai-cloudflare", "packages/ai/cloudflare"), |
| 111 | ...project("@effect/ai-openai", "packages/ai/openai"), |
| 112 | ...project("@effect/ai-typesafe", "packages/ai/typesafe"), |
| 113 | ...project("@effect/ai-openai-compat", "packages/ai/openai-compat"), |
| 114 | ...project("@effect/ai-openrouter", "packages/ai/openrouter"), |
| 115 | ...project("@effect/atom-react", "packages/atom/react", true, { |
| 116 | test: { |
| 117 | environment: "jsdom", |
| 118 | setupFiles: [path.join(import.meta.dirname, "packages/atom/react/vitest.setup.ts")] |
| 119 | } |
| 120 | }), |
| 121 | ...project("@effect/atom-solid", "packages/atom/solid", true, { |
| 122 | resolve: { |
| 123 | conditions: ["browser"] |
| 124 | }, |
| 125 | test: { |
| 126 | environment: "jsdom", |
| 127 | setupFiles: [path.join(import.meta.dirname, "packages/atom/solid/vitest.setup.ts")] |
| 128 | } |
| 129 | }), |
| 130 | ...project("@effect/atom-vue", "packages/atom/vue", true, { |
| 131 | test: { |
| 132 | environment: "happy-dom" |
| 133 | } |
| 134 | }), |
| 135 | ...project("@effect/opentelemetry", "packages/opentelemetry"), |
| 136 | ...project("@effect/platform-browser", "packages/platform/browser", true, { |
| 137 | test: { |
| 138 | environment: "happy-dom", |
| 139 | // Bun interprets Node's --localstorage-file value as a module path. |
| 140 | execArgv: isBun |
| 141 | ? [] |
| 142 | : [ |
| 143 | "--localstorage-file", |
| 144 | path.resolve(os.tmpdir(), `vitest-${process.pid}.localstorage`) |
| 145 | ], |
| 146 | setupFiles: [path.join(import.meta.dirname, "packages/platform/browser/vitest.setup.ts")] |
| 147 | } |
| 148 | }), |
| 149 | ...project("@effect/platform-bun", "packages/platform/bun", isBun), |
| 150 | ...project("@effect/platform-deno", "packages/platform/deno", isDeno), |
| 151 | ...project("@effect/platform-node", "packages/platform/node", isNode), |
| 152 | ...project( |
| 153 | "cluster-integration", |
| 154 | "packages/platform/node", |
| 155 | isNode && clusterTestsEnabled, |
| 156 | { |
| 157 | test: { |
| 158 | globalSetup: [ |
| 159 | path.join(import.meta.dirname, "packages/platform/node/test/cluster-integration/globalSetup.ts") |
| 160 | ], |
| 161 | include: ["test/cluster-integration/**/*.test.ts"], |
| 162 | retry: 0, |
| 163 | sequence: { |
| 164 | concurrent: false |
| 165 | }, |
| 166 | testTimeout: 60_000 |
| 167 | } |
| 168 | }, |
| 169 | exclude.filter((path) => path !== "**/test/cluster-integration/**"), |
| 170 | [ |
| 171 | "test/cluster-integration/**/*.test.ts" |
| 172 | ] |
| 173 | ), |
| 174 | ...project("@effect/platform-node-shared", "packages/platform/node-shared", isNode), |
| 175 | ...project("@effect/vitest", "packages/vitest"), |
| 176 | ...project("@effect/sql-clickhouse", "packages/sql/clickhouse"), |
| 177 | ...project("@effect/sql-d1", "packages/sql/d1", !isDeno), |
| 178 | ...project("@effect/sql-libsql", "packages/sql/libsql"), |
| 179 | ...project("@effect/sql-mssql", "packages/sql/mssql"), |
| 180 | // MySQL starts a fresh container per integration test suite, so avoid competing |
| 181 | // with the other container-backed projects on integration-test runners. |
| 182 | ...project( |
| 183 | "@effect/sql-mysql2", |
| 184 | "packages/sql/mysql2", |
| 185 | true, |
| 186 | integrationTestsEnabled |
| 187 | ? { |
| 188 | test: { |
| 189 | fileParallelism: false, |
| 190 | sequence: { |
| 191 | groupOrder: 1 |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | : {} |
| 196 | ), |
| 197 | ...project("@effect/sql-pg", "packages/sql/pg"), |
| 198 | ...project("@effect/sql-pglite", "packages/sql/pglite"), |
| 199 | ...project("@effect/sql-sqlite-bun", "packages/sql/sqlite-bun"), |
| 200 | ...project( |
| 201 | "@effect/sql-sqlite-do", |
| 202 | "packages/sql/sqlite-do", |
| 203 | true, |
| 204 | {}, |
| 205 | // Miniflare needs Node/Bun; keep the in-process client tests on Deno. |
| 206 | isDeno ? [...exclude, "test/Miniflare.test.ts"] : undefined |
| 207 | ), |
| 208 | ...project("@effect/sql-sqlite-node", "packages/sql/sqlite-node", isNode), |
| 209 | ...project("@effect/sql-sqlite-react-native", "packages/sql/sqlite-react-native"), |
| 210 | ...project("@effect/sql-sqlite-wasm", "packages/sql/sqlite-wasm"), |
| 211 | ...project("@effect/ai-codegen", "packages/tools/ai-codegen", isNode), |
| 212 | ...project("@effect/api-diff", "packages/tools/api-diff"), |
| 213 | ...project("@effect/bundle", "packages/tools/bundle"), |
| 214 | ...project("@effect/doctest", "packages/tools/doctest"), |
| 215 | ...project("@effect/docgen", "packages/tools/docgen"), |
| 216 | ...project("@effect/jsdocs", "packages/tools/jsdocs"), |
| 217 | ...project("@effect/openapi-generator", "packages/tools/openapi-generator"), |
| 218 | ...project("@effect/utils", "packages/tools/utils", isNode), |
| 219 | ...project("@effect/oxc", "packages/tools/oxc") |
| 220 | ] |
| 221 | } |
| 222 | }) |