@reasonabletech/config-vitest 0.1.0 → 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @reasonabletech/config-vitest
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - - Automated release.
8
+
9
+ ## [Unreleased]
10
+
11
+ Initial release preparation. See [README.md](./README.md) for usage.
12
+
13
+ ---
14
+
15
+ _Changelog entries are automatically generated from [changesets](https://github.com/changesets/changesets) on release._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reasonabletech/config-vitest",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared Vitest configuration",
5
5
  "keywords": [
6
6
  "reasonabletech",
@@ -39,8 +39,11 @@
39
39
  },
40
40
  "files": [
41
41
  "dist",
42
+ "!dist/**/*.map",
43
+ "!dist/**/*.tsbuildinfo",
42
44
  "docs/**/*",
43
- "src"
45
+ "README.md",
46
+ "CHANGELOG.md"
44
47
  ],
45
48
  "dependencies": {
46
49
  "@vitejs/plugin-react": "5.1.4",
@@ -54,8 +57,8 @@
54
57
  "tsup": "8.5.1",
55
58
  "typescript": "5.9.3",
56
59
  "vitest": "4.0.18",
57
- "@reasonabletech/config-eslint": "0.1.0",
58
- "@reasonabletech/config-typescript": "0.1.0"
60
+ "@reasonabletech/eslint-config": "0.2.1",
61
+ "@reasonabletech/config-typescript": "0.1.2"
59
62
  },
60
63
  "peerDependencies": {
61
64
  "vite": ">=7.3.1 <8",
@@ -1 +0,0 @@
1
- {"version":3,"file":"global-setup.d.ts","sourceRoot":"","sources":["../../src/global-setup.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAQxE;AAED;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,WAAW,IAAI,IAAI,CAgC1C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,qDAAqD;AACrD,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GACxD,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAC9B,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,GACzB,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAC9B,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GACjB,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAClB,CAAC,CAAC,CAAC,CAAC;CACb,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC;IACtC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC,CAAC;AAKH;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;CAwCtB,CAAC;AAuDF,gBAAgB;AAChB;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,EAC1C,YAAY,CAAC,EAAE,YAAY,GAC1B,UAAU,CAAC,OAAO,YAAY,CAAC,CAwFjC;AAED,gBAAgB;AAChB;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,EAC1C,YAAY,CAAC,EAAE,YAAY,GAC1B,UAAU,CAAC,OAAO,YAAY,CAAC,CA0BjC;AAGD,OAAO,EAAE,iBAAiB,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE7E,eAAe,kBAAkB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/workspace.ts","../../src/react.ts"],"sourcesContent":["/**\n * Base Vitest configuration for all packages\n * @module @reasonabletech/config-vitest\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { defaultClientConditions, defaultServerConditions } from \"vite\";\nimport { defineConfig } from \"vitest/config\";\nimport type { InlineConfig } from \"vitest/node\";\nimport { readPackageName } from \"./workspace.js\";\n\n/** Recursively makes all properties of T readonly */\nexport type DeepReadonly<T> = {\n readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends Array<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends object\n ? DeepReadonly<T[P]>\n : T[P];\n};\n\n/**\n * Immutable configuration object accepted by {@link createVitestConfig} and\n * related helpers.\n */\nexport type VitestConfig = DeepReadonly<{\n test?: InlineConfig;\n resolve?: {\n conditions?: string[];\n alias?: Record<string, string>;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}>;\n\n// Empty readonly config for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\n\n/**\n * Base configuration options that apply to all test environments\n */\nexport const baseConfig = {\n test: {\n testTimeout: 10000, // 10 seconds default timeout\n hookTimeout: 10000, // 10 seconds for setup/teardown hooks\n coverage: {\n provider: \"v8\" as const,\n reporter: [\"text\", \"html\", \"lcov\", \"json\"],\n reportsDirectory: \"./generated/test-coverage\",\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/tests/**\",\n \"**/*.d.ts\",\n \"**/*.config.{js,ts,mjs,mts}\",\n \"**/coverage/**\",\n \"**/examples/**\",\n \"**/src/index.ts\",\n \"**/src/*/index.ts\",\n \"**/src/types/**\",\n \"tsup.config.ts\",\n \"vitest.config.mts\",\n \"tailwind.config.mjs\",\n \"**/vitest.setup.ts\",\n ],\n thresholds:\n process.env.VITEST_COVERAGE_THRESHOLDS_DISABLED === \"true\"\n ? {\n lines: 0,\n functions: 0,\n branches: 0,\n statements: 0,\n }\n : {\n lines: 100,\n functions: 100,\n branches: 100,\n statements: 100,\n },\n },\n },\n};\n\n/**\n * Generates resolve aliases that map a package's own name back to its source\n * directory. This allows test files to `import { foo } from \"@reasonabletech/my-pkg\"`\n * and have Vite resolve it to the local `src/` tree instead of requiring a prior\n * build step.\n *\n * Placed before user-supplied aliases so consumers can override if needed.\n * @param projectDir - Absolute path to the project directory\n * @returns A record mapping the package name to its `src/` directory\n */\nfunction generateSelfPackageAliases(\n projectDir: string,\n): Record<string, string> {\n const packageName = readPackageName(projectDir);\n if (packageName === null) {\n return {};\n }\n return {\n [packageName]: `${projectDir}/src`,\n };\n}\n\n/**\n * Auto-detects setup files in a project directory\n * @param projectDir - The project directory path\n * @returns Array of detected setup file paths\n */\nfunction autoDetectSetupFiles(projectDir?: string): string[] {\n if (projectDir === undefined || projectDir === \"\") {\n return [];\n }\n\n const vitestSetupPath = join(projectDir, \"vitest.setup.ts\");\n if (existsSync(vitestSetupPath)) {\n return [\"./vitest.setup.ts\"];\n }\n\n const testsSetupPath = join(projectDir, \"tests/setup.ts\");\n if (existsSync(testsSetupPath)) {\n return [\"./tests/setup.ts\"];\n }\n\n return [];\n}\n\n/**\n * Auto-detects test include patterns based on project structure\n * @returns Array of include patterns\n */\nfunction autoDetectIncludePatterns(): string[] {\n return [\"tests/**/*.test.{ts,tsx,js,jsx}\"];\n}\n\n/** @overload */\n/**\n * Creates a merged configuration from the base and any custom options.\n * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.\n * @param customConfig - Additional configuration to merge when a project directory is provided.\n * @returns A merged Vitest configuration tailored for the caller.\n */\nexport function createVitestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Auto-detect configuration if not explicitly provided\n const autoSetupFiles =\n config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0\n ? []\n : autoDetectSetupFiles(projectDir);\n const autoIncludePatterns =\n config.test?.include !== undefined && config.test.include.length > 0\n ? []\n : autoDetectIncludePatterns();\n\n return defineConfig({\n ...baseConfig,\n ...config,\n test: {\n ...baseConfig.test,\n // Auto-detect setupFiles if not explicitly provided\n ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),\n // Auto-detect include patterns if not explicitly provided\n ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),\n ...config.test,\n coverage: {\n ...baseConfig.test.coverage,\n ...config.test?.coverage,\n },\n },\n resolve: {\n // Prefer \"source\" condition in package.json exports, allowing\n // Vitest to resolve workspace dependencies directly to TypeScript source\n // files without requiring a prior build step.\n //\n // Since Vite 6, resolve.conditions REPLACES the defaults instead of\n // extending them. We must include defaultClientConditions to preserve\n // standard conditions like \"module\", \"browser\", \"development|production\".\n conditions: [\"source\", ...defaultClientConditions],\n alias: {\n // Work around packages whose \"module\" entry is bundler-oriented but not\n // directly Node.js ESM-resolvable in Vitest (e.g. extensionless internal\n // specifiers). Prefer the Node/CJS build for tests.\n \"@opentelemetry/api\": \"@opentelemetry/api/build/src/index.js\",\n // Auto-generate self-package alias (e.g. \"@reasonabletech/utils\" → \"./src\")\n ...(projectDir !== undefined &&\n projectDir !== \"\" &&\n generateSelfPackageAliases(projectDir)),\n // Standard \"@\" → src alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" && { \"@\": `${projectDir}/src` }),\n // Consumer-provided aliases override everything above\n ...config.resolve?.alias,\n },\n ...config.resolve,\n },\n // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is\n // independent from resolve.conditions and defaults to defaultServerConditions.\n // We must explicitly include \"source\" in ssr.resolve.conditions so\n // workspace dependencies resolve to TypeScript source during test execution.\n // Additionally, ssr.resolve.externalConditions passes \"source\" to\n // Node.js when it natively resolves externalized packages.\n //\n // Note: We intentionally omit the \"module\" export condition for SSR tests.\n // Some third-party packages expose a \"module\" build that is bundler-friendly\n // but not directly Node.js ESM-resolvable (for example: extensionless internal\n // specifiers). For tests, preferring Node-compatible (\"node\"/\"default\") entry\n // points avoids runtime import failures.\n ssr: {\n resolve: {\n conditions: [\n \"source\",\n ...defaultServerConditions.filter(\n (condition) => condition !== \"module\",\n ),\n ],\n externalConditions: [\"source\"],\n },\n },\n });\n}\n\n/** @overload */\n/**\n * Creates a configuration with extended timeouts for long-running tests.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to extend.\n * @param customConfig - Additional configuration to merge when a project directory is supplied.\n * @returns A Vitest configuration suited for long-running suites.\n */\nexport function createLongRunningTestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n const longRunningConfig: VitestConfig = {\n ...config,\n test: {\n testTimeout: 30000, // 30 seconds for long-running tests\n hookTimeout: 30000, // 30 seconds for setup/teardown hooks\n ...config.test,\n },\n };\n\n if (projectDir !== undefined) {\n return createVitestConfig(projectDir, longRunningConfig);\n }\n return createVitestConfig(longRunningConfig);\n}\n\n// Re-export for convenience\nexport { createReactConfig, createReactConfigWithPlugins } from \"./react.js\";\n\nexport default createVitestConfig;\n","/**\n * Workspace utility functions for discovering monorepo structure\n *\n * These are extracted from global-setup.ts so they can be reused by\n * the Vitest config factories (e.g. auto self-package aliasing).\n * @module @reasonabletech/config-vitest/workspace\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Walks up the directory tree from `startDir` until it finds a directory\n * containing `pnpm-workspace.yaml`, which marks the monorepo root.\n *\n * Returns `startDir` unchanged if no workspace root is found (e.g. when\n * running outside the monorepo).\n * @param startDir - The directory to start searching from\n * @returns The absolute path to the monorepo root, or `startDir` if not found\n */\nexport function findRepoRoot(startDir: string): string {\n let currentDir = startDir;\n for (;;) {\n if (existsSync(path.join(currentDir, \"pnpm-workspace.yaml\"))) {\n return currentDir;\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return startDir;\n }\n currentDir = parentDir;\n }\n}\n\n/**\n * Reads the `name` field from the `package.json` in the given directory.\n *\n * Returns `null` when:\n * - No `package.json` exists at `packageDir`\n * - The file cannot be parsed as JSON\n * - The `name` field is missing, empty, or not a string\n * @param packageDir - The directory containing the package.json to read\n * @returns The package name string, or `null` if unavailable\n */\nexport function readPackageName(packageDir: string): string | null {\n const packageJsonPath = path.join(packageDir, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n name?: unknown;\n };\n return typeof parsed.name === \"string\" && parsed.name.length > 0\n ? parsed.name\n : null;\n } catch {\n return null;\n }\n}\n","/**\n * React-specific Vitest configuration\n * @module @reasonabletech/config-vitest/react\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n defaultClientConditions,\n defaultServerConditions,\n type PluginOption,\n} from \"vite\";\nimport { defineConfig } from \"vitest/config\";\nimport type { InlineConfig } from \"vitest/node\";\nimport react from \"@vitejs/plugin-react\";\nimport { baseConfig, type DeepReadonly } from \"./index.js\";\nimport { readPackageName } from \"./workspace.js\";\n\ntype VitestConfig = DeepReadonly<{\n test?: InlineConfig;\n resolve?: {\n alias?: Record<string, string>;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}>;\n\n// Empty readonly config and plugins array for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\nconst EMPTY_PLUGINS = [] as const satisfies readonly PluginOption[];\n\n/**\n * Generates resolve aliases mapping a package's own name to its source directory.\n * @param projectDir - Absolute path to the project directory\n * @returns A record mapping the package name to its `src/` directory\n * @see index.ts for the equivalent function used in the base config\n */\nfunction generateSelfPackageAliases(\n projectDir: string,\n): Record<string, string> {\n const packageName = readPackageName(projectDir);\n if (packageName === null) {\n return {};\n }\n return {\n [packageName]: `${projectDir}/src`,\n };\n}\n\n/**\n * Auto-detects setup files in a project directory\n * @param projectDir - The project directory path\n * @returns Array of detected setup file paths\n */\nfunction autoDetectSetupFiles(projectDir?: string): string[] {\n if (projectDir === undefined || projectDir === \"\") {\n return [];\n }\n\n const vitestSetupPath = join(projectDir, \"vitest.setup.ts\");\n if (existsSync(vitestSetupPath)) {\n return [\"./vitest.setup.ts\"];\n }\n\n const testsSetupPath = join(projectDir, \"tests/setup.ts\");\n if (existsSync(testsSetupPath)) {\n return [\"./tests/setup.ts\"];\n }\n\n return [];\n}\n\n/**\n * Auto-detects test include patterns based on project structure\n * @returns Array of include patterns\n */\nfunction autoDetectIncludePatterns(): string[] {\n return [\"tests/**/*.test.{ts,tsx,js,jsx}\"];\n}\n\n/**\n * React-specific configuration options\n */\nexport const reactConfig = {\n test: {\n environment: \"jsdom\",\n exclude: [\"**/node_modules/**\", \"**/dist/**\"],\n // Suppress vitest's unhandled error reporting for expected test errors\n silent: false,\n onConsoleLog: (): boolean | undefined => {\n // Allow packages to customize console filtering in their own config\n return undefined;\n },\n },\n};\n\n/** @overload */\n/**\n * Creates a Vitest configuration for React-based packages.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.\n * @param customConfig - Additional configuration options for the project-dir overload.\n * @returns A Vitest configuration optimized for React components.\n */\nexport function createReactConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? ({} as const);\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? ({} as const);\n }\n\n if (projectDir !== undefined) {\n return createReactConfigWithPlugins([react()], projectDir, config);\n }\n return createReactConfigWithPlugins([react()], config);\n}\n\n/** @overload */\n/**\n * Creates a Vitest configuration for React-based packages with plugins.\n * @param plugins - Array of Vite plugins to include.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.\n * @param customConfig - Additional configuration options for the project-dir overload.\n * @returns A Vitest configuration optimized for React components with plugins.\n */\nexport function createReactConfigWithPlugins(\n plugins: readonly PluginOption[] = EMPTY_PLUGINS,\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Auto-detect configuration if not explicitly provided\n const autoSetupFiles =\n config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0\n ? []\n : autoDetectSetupFiles(projectDir);\n const autoIncludePatterns =\n config.test?.include !== undefined && config.test.include.length > 0\n ? []\n : autoDetectIncludePatterns();\n\n return defineConfig({\n plugins: [...plugins],\n ...baseConfig,\n ...reactConfig,\n ...config,\n test: {\n ...baseConfig.test,\n ...reactConfig.test,\n // Auto-detect setupFiles if not explicitly provided\n ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),\n // Auto-detect include patterns if not explicitly provided\n ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),\n ...config.test,\n coverage: {\n ...baseConfig.test.coverage,\n ...config.test?.coverage,\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/tests/**\",\n \"**/*.d.ts\",\n \"**/*.config.{js,ts,mjs,mts}\",\n \"**/coverage/**\",\n \"**/examples/**\",\n \"**/src/index.ts\",\n \"**/src/*/index.ts\",\n \"**/src/types/**\",\n \"tsup.config.ts\",\n \"vitest.config.mts\",\n \"tailwind.config.mjs\",\n \"**/.next/**\",\n \"**/vitest.setup.ts\",\n \"**/types.ts\",\n ],\n },\n },\n resolve: {\n // Prefer \"source\" condition in package.json exports, allowing\n // Vitest to resolve workspace dependencies directly to TypeScript source\n // files without requiring a prior build step.\n //\n // Since Vite 6, resolve.conditions REPLACES the defaults instead of\n // extending them. We must include defaultClientConditions to preserve\n // standard conditions like \"module\", \"browser\", \"development|production\".\n conditions: [\"source\", ...defaultClientConditions],\n // Deduplicate React to prevent multiple-instance issues in tests.\n // This is Vite's standard API for singleton enforcement — no filesystem\n // paths needed, Vite resolves from the project root automatically.\n dedupe: [\n \"react\",\n \"react-dom\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n ],\n alias: {\n // Auto-generate self-package alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" &&\n generateSelfPackageAliases(projectDir)),\n // Standard \"@\" → src alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" && { \"@\": `${projectDir}/src` }),\n // Consumer-provided aliases override everything above\n ...config.resolve?.alias,\n },\n ...config.resolve,\n },\n // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is\n // independent from resolve.conditions and defaults to defaultServerConditions.\n // We must explicitly include \"source\" in ssr.resolve.conditions so\n // workspace dependencies resolve to TypeScript source during test execution.\n ssr: {\n resolve: {\n conditions: [\"source\", ...defaultServerConditions],\n externalConditions: [\"source\"],\n },\n },\n });\n}\n\nexport default createReactConfig;\n"],"mappings":"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","names":["existsSync","join","defaultClientConditions","defaultServerConditions","defineConfig","existsSync","join","join","existsSync","EMPTY_CONFIG","generateSelfPackageAliases","autoDetectSetupFiles","join","existsSync","autoDetectIncludePatterns","defineConfig","defaultClientConditions","defaultServerConditions"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnE,6FAA6F;AAC7F,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,YAAY,CAAC,CAAC;CAC5C,CAAC;AAKF;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;CAKU,CAAC;AAElC;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,EAC1C,YAAY,CAAC,EAAE,YAAY,GAC1B,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAmCvC;AAED,eAAe,gBAAgB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/workspace.ts","../../src/node.ts"],"sourcesContent":["/**\n * Base Vitest configuration for all packages\n * @module @reasonabletech/config-vitest\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { defaultClientConditions, defaultServerConditions } from \"vite\";\nimport { defineConfig } from \"vitest/config\";\nimport type { InlineConfig } from \"vitest/node\";\nimport { readPackageName } from \"./workspace.js\";\n\n/** Recursively makes all properties of T readonly */\nexport type DeepReadonly<T> = {\n readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends Array<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends object\n ? DeepReadonly<T[P]>\n : T[P];\n};\n\n/**\n * Immutable configuration object accepted by {@link createVitestConfig} and\n * related helpers.\n */\nexport type VitestConfig = DeepReadonly<{\n test?: InlineConfig;\n resolve?: {\n conditions?: string[];\n alias?: Record<string, string>;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}>;\n\n// Empty readonly config for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\n\n/**\n * Base configuration options that apply to all test environments\n */\nexport const baseConfig = {\n test: {\n testTimeout: 10000, // 10 seconds default timeout\n hookTimeout: 10000, // 10 seconds for setup/teardown hooks\n coverage: {\n provider: \"v8\" as const,\n reporter: [\"text\", \"html\", \"lcov\", \"json\"],\n reportsDirectory: \"./generated/test-coverage\",\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/tests/**\",\n \"**/*.d.ts\",\n \"**/*.config.{js,ts,mjs,mts}\",\n \"**/coverage/**\",\n \"**/examples/**\",\n \"**/src/index.ts\",\n \"**/src/*/index.ts\",\n \"**/src/types/**\",\n \"tsup.config.ts\",\n \"vitest.config.mts\",\n \"tailwind.config.mjs\",\n \"**/vitest.setup.ts\",\n ],\n thresholds:\n process.env.VITEST_COVERAGE_THRESHOLDS_DISABLED === \"true\"\n ? {\n lines: 0,\n functions: 0,\n branches: 0,\n statements: 0,\n }\n : {\n lines: 100,\n functions: 100,\n branches: 100,\n statements: 100,\n },\n },\n },\n};\n\n/**\n * Generates resolve aliases that map a package's own name back to its source\n * directory. This allows test files to `import { foo } from \"@reasonabletech/my-pkg\"`\n * and have Vite resolve it to the local `src/` tree instead of requiring a prior\n * build step.\n *\n * Placed before user-supplied aliases so consumers can override if needed.\n * @param projectDir - Absolute path to the project directory\n * @returns A record mapping the package name to its `src/` directory\n */\nfunction generateSelfPackageAliases(\n projectDir: string,\n): Record<string, string> {\n const packageName = readPackageName(projectDir);\n if (packageName === null) {\n return {};\n }\n return {\n [packageName]: `${projectDir}/src`,\n };\n}\n\n/**\n * Auto-detects setup files in a project directory\n * @param projectDir - The project directory path\n * @returns Array of detected setup file paths\n */\nfunction autoDetectSetupFiles(projectDir?: string): string[] {\n if (projectDir === undefined || projectDir === \"\") {\n return [];\n }\n\n const vitestSetupPath = join(projectDir, \"vitest.setup.ts\");\n if (existsSync(vitestSetupPath)) {\n return [\"./vitest.setup.ts\"];\n }\n\n const testsSetupPath = join(projectDir, \"tests/setup.ts\");\n if (existsSync(testsSetupPath)) {\n return [\"./tests/setup.ts\"];\n }\n\n return [];\n}\n\n/**\n * Auto-detects test include patterns based on project structure\n * @returns Array of include patterns\n */\nfunction autoDetectIncludePatterns(): string[] {\n return [\"tests/**/*.test.{ts,tsx,js,jsx}\"];\n}\n\n/** @overload */\n/**\n * Creates a merged configuration from the base and any custom options.\n * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.\n * @param customConfig - Additional configuration to merge when a project directory is provided.\n * @returns A merged Vitest configuration tailored for the caller.\n */\nexport function createVitestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Auto-detect configuration if not explicitly provided\n const autoSetupFiles =\n config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0\n ? []\n : autoDetectSetupFiles(projectDir);\n const autoIncludePatterns =\n config.test?.include !== undefined && config.test.include.length > 0\n ? []\n : autoDetectIncludePatterns();\n\n return defineConfig({\n ...baseConfig,\n ...config,\n test: {\n ...baseConfig.test,\n // Auto-detect setupFiles if not explicitly provided\n ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),\n // Auto-detect include patterns if not explicitly provided\n ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),\n ...config.test,\n coverage: {\n ...baseConfig.test.coverage,\n ...config.test?.coverage,\n },\n },\n resolve: {\n // Prefer \"source\" condition in package.json exports, allowing\n // Vitest to resolve workspace dependencies directly to TypeScript source\n // files without requiring a prior build step.\n //\n // Since Vite 6, resolve.conditions REPLACES the defaults instead of\n // extending them. We must include defaultClientConditions to preserve\n // standard conditions like \"module\", \"browser\", \"development|production\".\n conditions: [\"source\", ...defaultClientConditions],\n alias: {\n // Work around packages whose \"module\" entry is bundler-oriented but not\n // directly Node.js ESM-resolvable in Vitest (e.g. extensionless internal\n // specifiers). Prefer the Node/CJS build for tests.\n \"@opentelemetry/api\": \"@opentelemetry/api/build/src/index.js\",\n // Auto-generate self-package alias (e.g. \"@reasonabletech/utils\" → \"./src\")\n ...(projectDir !== undefined &&\n projectDir !== \"\" &&\n generateSelfPackageAliases(projectDir)),\n // Standard \"@\" → src alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" && { \"@\": `${projectDir}/src` }),\n // Consumer-provided aliases override everything above\n ...config.resolve?.alias,\n },\n ...config.resolve,\n },\n // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is\n // independent from resolve.conditions and defaults to defaultServerConditions.\n // We must explicitly include \"source\" in ssr.resolve.conditions so\n // workspace dependencies resolve to TypeScript source during test execution.\n // Additionally, ssr.resolve.externalConditions passes \"source\" to\n // Node.js when it natively resolves externalized packages.\n //\n // Note: We intentionally omit the \"module\" export condition for SSR tests.\n // Some third-party packages expose a \"module\" build that is bundler-friendly\n // but not directly Node.js ESM-resolvable (for example: extensionless internal\n // specifiers). For tests, preferring Node-compatible (\"node\"/\"default\") entry\n // points avoids runtime import failures.\n ssr: {\n resolve: {\n conditions: [\n \"source\",\n ...defaultServerConditions.filter(\n (condition) => condition !== \"module\",\n ),\n ],\n externalConditions: [\"source\"],\n },\n },\n });\n}\n\n/** @overload */\n/**\n * Creates a configuration with extended timeouts for long-running tests.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to extend.\n * @param customConfig - Additional configuration to merge when a project directory is supplied.\n * @returns A Vitest configuration suited for long-running suites.\n */\nexport function createLongRunningTestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n const longRunningConfig: VitestConfig = {\n ...config,\n test: {\n testTimeout: 30000, // 30 seconds for long-running tests\n hookTimeout: 30000, // 30 seconds for setup/teardown hooks\n ...config.test,\n },\n };\n\n if (projectDir !== undefined) {\n return createVitestConfig(projectDir, longRunningConfig);\n }\n return createVitestConfig(longRunningConfig);\n}\n\n// Re-export for convenience\nexport { createReactConfig, createReactConfigWithPlugins } from \"./react.js\";\n\nexport default createVitestConfig;\n","/**\n * Workspace utility functions for discovering monorepo structure\n *\n * These are extracted from global-setup.ts so they can be reused by\n * the Vitest config factories (e.g. auto self-package aliasing).\n * @module @reasonabletech/config-vitest/workspace\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Walks up the directory tree from `startDir` until it finds a directory\n * containing `pnpm-workspace.yaml`, which marks the monorepo root.\n *\n * Returns `startDir` unchanged if no workspace root is found (e.g. when\n * running outside the monorepo).\n * @param startDir - The directory to start searching from\n * @returns The absolute path to the monorepo root, or `startDir` if not found\n */\nexport function findRepoRoot(startDir: string): string {\n let currentDir = startDir;\n for (;;) {\n if (existsSync(path.join(currentDir, \"pnpm-workspace.yaml\"))) {\n return currentDir;\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return startDir;\n }\n currentDir = parentDir;\n }\n}\n\n/**\n * Reads the `name` field from the `package.json` in the given directory.\n *\n * Returns `null` when:\n * - No `package.json` exists at `packageDir`\n * - The file cannot be parsed as JSON\n * - The `name` field is missing, empty, or not a string\n * @param packageDir - The directory containing the package.json to read\n * @returns The package name string, or `null` if unavailable\n */\nexport function readPackageName(packageDir: string): string | null {\n const packageJsonPath = path.join(packageDir, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n name?: unknown;\n };\n return typeof parsed.name === \"string\" && parsed.name.length > 0\n ? parsed.name\n : null;\n } catch {\n return null;\n }\n}\n","/**\n * Node.js-specific Vitest configuration\n * @module @reasonabletech/config-vitest/node\n */\n\nimport type { InlineConfig } from \"vitest/node\";\nimport type { UserConfig } from \"vite\";\nimport { createVitestConfig, type DeepReadonly } from \"./index.js\";\n\n/** Immutable Vitest configuration type combining Vite UserConfig with Vitest InlineConfig */\nexport type VitestConfig = DeepReadonly<UserConfig> & {\n readonly test?: DeepReadonly<InlineConfig>;\n};\n\n// Empty readonly config for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\n\n/**\n * Node.js-specific configuration options\n */\nexport const nodeConfig = {\n test: {\n environment: \"node\" as const,\n include: [\"tests/**/*.test.ts\"],\n },\n} as const satisfies VitestConfig;\n\n/**\n * Creates a Vitest configuration for Node.js-based packages.\n * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.\n * @param customConfig - Additional configuration to merge when a project directory is provided.\n * @returns A Vitest configuration optimized for Node.js environments\n */\nexport function createNodeConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof createVitestConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Merge node-specific settings with consumer config\n const mergedConfig = {\n ...nodeConfig,\n ...config,\n test: {\n ...nodeConfig.test,\n ...config.test,\n },\n };\n\n // Delegate to createVitestConfig which handles aliasing and base config.\n // When projectDir is present, pass both args so createVitestConfig generates aliases.\n // When absent, pass the merged config as the sole argument.\n if (projectDir !== undefined) {\n return createVitestConfig(\n projectDir,\n mergedConfig as Parameters<typeof createVitestConfig>[1],\n );\n }\n return createVitestConfig(\n mergedConfig as Parameters<typeof createVitestConfig>[0],\n );\n}\n\nexport default createNodeConfig;\n"],"mappings":"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","names":["existsSync","join","join","existsSync","EMPTY_CONFIG"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/react.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3D,KAAK,YAAY,GAAG,YAAY,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC,CAAC;AAuDH;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;4BAMF,OAAO,GAAG,SAAS;;CAKxC,CAAC;AAEF,gBAAgB;AAChB;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,EAC1C,YAAY,CAAC,EAAE,YAAY,GAC1B,UAAU,CAAC,OAAO,YAAY,CAAC,CAiBjC;AAED,gBAAgB;AAChB;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,SAAS,YAAY,EAAkB,EAChD,kBAAkB,CAAC,EAAE,MAAM,GAAG,YAAY,EAC1C,YAAY,CAAC,EAAE,YAAY,GAC1B,UAAU,CAAC,OAAO,YAAY,CAAC,CAqGjC;AAED,eAAe,iBAAiB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/react.ts","../../src/index.ts","../../src/workspace.ts"],"sourcesContent":["/**\n * React-specific Vitest configuration\n * @module @reasonabletech/config-vitest/react\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n defaultClientConditions,\n defaultServerConditions,\n type PluginOption,\n} from \"vite\";\nimport { defineConfig } from \"vitest/config\";\nimport type { InlineConfig } from \"vitest/node\";\nimport react from \"@vitejs/plugin-react\";\nimport { baseConfig, type DeepReadonly } from \"./index.js\";\nimport { readPackageName } from \"./workspace.js\";\n\ntype VitestConfig = DeepReadonly<{\n test?: InlineConfig;\n resolve?: {\n alias?: Record<string, string>;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}>;\n\n// Empty readonly config and plugins array for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\nconst EMPTY_PLUGINS = [] as const satisfies readonly PluginOption[];\n\n/**\n * Generates resolve aliases mapping a package's own name to its source directory.\n * @param projectDir - Absolute path to the project directory\n * @returns A record mapping the package name to its `src/` directory\n * @see index.ts for the equivalent function used in the base config\n */\nfunction generateSelfPackageAliases(\n projectDir: string,\n): Record<string, string> {\n const packageName = readPackageName(projectDir);\n if (packageName === null) {\n return {};\n }\n return {\n [packageName]: `${projectDir}/src`,\n };\n}\n\n/**\n * Auto-detects setup files in a project directory\n * @param projectDir - The project directory path\n * @returns Array of detected setup file paths\n */\nfunction autoDetectSetupFiles(projectDir?: string): string[] {\n if (projectDir === undefined || projectDir === \"\") {\n return [];\n }\n\n const vitestSetupPath = join(projectDir, \"vitest.setup.ts\");\n if (existsSync(vitestSetupPath)) {\n return [\"./vitest.setup.ts\"];\n }\n\n const testsSetupPath = join(projectDir, \"tests/setup.ts\");\n if (existsSync(testsSetupPath)) {\n return [\"./tests/setup.ts\"];\n }\n\n return [];\n}\n\n/**\n * Auto-detects test include patterns based on project structure\n * @returns Array of include patterns\n */\nfunction autoDetectIncludePatterns(): string[] {\n return [\"tests/**/*.test.{ts,tsx,js,jsx}\"];\n}\n\n/**\n * React-specific configuration options\n */\nexport const reactConfig = {\n test: {\n environment: \"jsdom\",\n exclude: [\"**/node_modules/**\", \"**/dist/**\"],\n // Suppress vitest's unhandled error reporting for expected test errors\n silent: false,\n onConsoleLog: (): boolean | undefined => {\n // Allow packages to customize console filtering in their own config\n return undefined;\n },\n },\n};\n\n/** @overload */\n/**\n * Creates a Vitest configuration for React-based packages.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.\n * @param customConfig - Additional configuration options for the project-dir overload.\n * @returns A Vitest configuration optimized for React components.\n */\nexport function createReactConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? ({} as const);\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? ({} as const);\n }\n\n if (projectDir !== undefined) {\n return createReactConfigWithPlugins([react()], projectDir, config);\n }\n return createReactConfigWithPlugins([react()], config);\n}\n\n/** @overload */\n/**\n * Creates a Vitest configuration for React-based packages with plugins.\n * @param plugins - Array of Vite plugins to include.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.\n * @param customConfig - Additional configuration options for the project-dir overload.\n * @returns A Vitest configuration optimized for React components with plugins.\n */\nexport function createReactConfigWithPlugins(\n plugins: readonly PluginOption[] = EMPTY_PLUGINS,\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Auto-detect configuration if not explicitly provided\n const autoSetupFiles =\n config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0\n ? []\n : autoDetectSetupFiles(projectDir);\n const autoIncludePatterns =\n config.test?.include !== undefined && config.test.include.length > 0\n ? []\n : autoDetectIncludePatterns();\n\n return defineConfig({\n plugins: [...plugins],\n ...baseConfig,\n ...reactConfig,\n ...config,\n test: {\n ...baseConfig.test,\n ...reactConfig.test,\n // Auto-detect setupFiles if not explicitly provided\n ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),\n // Auto-detect include patterns if not explicitly provided\n ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),\n ...config.test,\n coverage: {\n ...baseConfig.test.coverage,\n ...config.test?.coverage,\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/tests/**\",\n \"**/*.d.ts\",\n \"**/*.config.{js,ts,mjs,mts}\",\n \"**/coverage/**\",\n \"**/examples/**\",\n \"**/src/index.ts\",\n \"**/src/*/index.ts\",\n \"**/src/types/**\",\n \"tsup.config.ts\",\n \"vitest.config.mts\",\n \"tailwind.config.mjs\",\n \"**/.next/**\",\n \"**/vitest.setup.ts\",\n \"**/types.ts\",\n ],\n },\n },\n resolve: {\n // Prefer \"source\" condition in package.json exports, allowing\n // Vitest to resolve workspace dependencies directly to TypeScript source\n // files without requiring a prior build step.\n //\n // Since Vite 6, resolve.conditions REPLACES the defaults instead of\n // extending them. We must include defaultClientConditions to preserve\n // standard conditions like \"module\", \"browser\", \"development|production\".\n conditions: [\"source\", ...defaultClientConditions],\n // Deduplicate React to prevent multiple-instance issues in tests.\n // This is Vite's standard API for singleton enforcement — no filesystem\n // paths needed, Vite resolves from the project root automatically.\n dedupe: [\n \"react\",\n \"react-dom\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n ],\n alias: {\n // Auto-generate self-package alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" &&\n generateSelfPackageAliases(projectDir)),\n // Standard \"@\" → src alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" && { \"@\": `${projectDir}/src` }),\n // Consumer-provided aliases override everything above\n ...config.resolve?.alias,\n },\n ...config.resolve,\n },\n // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is\n // independent from resolve.conditions and defaults to defaultServerConditions.\n // We must explicitly include \"source\" in ssr.resolve.conditions so\n // workspace dependencies resolve to TypeScript source during test execution.\n ssr: {\n resolve: {\n conditions: [\"source\", ...defaultServerConditions],\n externalConditions: [\"source\"],\n },\n },\n });\n}\n\nexport default createReactConfig;\n","/**\n * Base Vitest configuration for all packages\n * @module @reasonabletech/config-vitest\n */\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { defaultClientConditions, defaultServerConditions } from \"vite\";\nimport { defineConfig } from \"vitest/config\";\nimport type { InlineConfig } from \"vitest/node\";\nimport { readPackageName } from \"./workspace.js\";\n\n/** Recursively makes all properties of T readonly */\nexport type DeepReadonly<T> = {\n readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends Array<infer U>\n ? ReadonlyArray<DeepReadonly<U>>\n : T[P] extends object\n ? DeepReadonly<T[P]>\n : T[P];\n};\n\n/**\n * Immutable configuration object accepted by {@link createVitestConfig} and\n * related helpers.\n */\nexport type VitestConfig = DeepReadonly<{\n test?: InlineConfig;\n resolve?: {\n conditions?: string[];\n alias?: Record<string, string>;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}>;\n\n// Empty readonly config for default parameters\nconst EMPTY_CONFIG = {} as const satisfies VitestConfig;\n\n/**\n * Base configuration options that apply to all test environments\n */\nexport const baseConfig = {\n test: {\n testTimeout: 10000, // 10 seconds default timeout\n hookTimeout: 10000, // 10 seconds for setup/teardown hooks\n coverage: {\n provider: \"v8\" as const,\n reporter: [\"text\", \"html\", \"lcov\", \"json\"],\n reportsDirectory: \"./generated/test-coverage\",\n exclude: [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/tests/**\",\n \"**/*.d.ts\",\n \"**/*.config.{js,ts,mjs,mts}\",\n \"**/coverage/**\",\n \"**/examples/**\",\n \"**/src/index.ts\",\n \"**/src/*/index.ts\",\n \"**/src/types/**\",\n \"tsup.config.ts\",\n \"vitest.config.mts\",\n \"tailwind.config.mjs\",\n \"**/vitest.setup.ts\",\n ],\n thresholds:\n process.env.VITEST_COVERAGE_THRESHOLDS_DISABLED === \"true\"\n ? {\n lines: 0,\n functions: 0,\n branches: 0,\n statements: 0,\n }\n : {\n lines: 100,\n functions: 100,\n branches: 100,\n statements: 100,\n },\n },\n },\n};\n\n/**\n * Generates resolve aliases that map a package's own name back to its source\n * directory. This allows test files to `import { foo } from \"@reasonabletech/my-pkg\"`\n * and have Vite resolve it to the local `src/` tree instead of requiring a prior\n * build step.\n *\n * Placed before user-supplied aliases so consumers can override if needed.\n * @param projectDir - Absolute path to the project directory\n * @returns A record mapping the package name to its `src/` directory\n */\nfunction generateSelfPackageAliases(\n projectDir: string,\n): Record<string, string> {\n const packageName = readPackageName(projectDir);\n if (packageName === null) {\n return {};\n }\n return {\n [packageName]: `${projectDir}/src`,\n };\n}\n\n/**\n * Auto-detects setup files in a project directory\n * @param projectDir - The project directory path\n * @returns Array of detected setup file paths\n */\nfunction autoDetectSetupFiles(projectDir?: string): string[] {\n if (projectDir === undefined || projectDir === \"\") {\n return [];\n }\n\n const vitestSetupPath = join(projectDir, \"vitest.setup.ts\");\n if (existsSync(vitestSetupPath)) {\n return [\"./vitest.setup.ts\"];\n }\n\n const testsSetupPath = join(projectDir, \"tests/setup.ts\");\n if (existsSync(testsSetupPath)) {\n return [\"./tests/setup.ts\"];\n }\n\n return [];\n}\n\n/**\n * Auto-detects test include patterns based on project structure\n * @returns Array of include patterns\n */\nfunction autoDetectIncludePatterns(): string[] {\n return [\"tests/**/*.test.{ts,tsx,js,jsx}\"];\n}\n\n/** @overload */\n/**\n * Creates a merged configuration from the base and any custom options.\n * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.\n * @param customConfig - Additional configuration to merge when a project directory is provided.\n * @returns A merged Vitest configuration tailored for the caller.\n */\nexport function createVitestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n // Auto-detect configuration if not explicitly provided\n const autoSetupFiles =\n config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0\n ? []\n : autoDetectSetupFiles(projectDir);\n const autoIncludePatterns =\n config.test?.include !== undefined && config.test.include.length > 0\n ? []\n : autoDetectIncludePatterns();\n\n return defineConfig({\n ...baseConfig,\n ...config,\n test: {\n ...baseConfig.test,\n // Auto-detect setupFiles if not explicitly provided\n ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),\n // Auto-detect include patterns if not explicitly provided\n ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),\n ...config.test,\n coverage: {\n ...baseConfig.test.coverage,\n ...config.test?.coverage,\n },\n },\n resolve: {\n // Prefer \"source\" condition in package.json exports, allowing\n // Vitest to resolve workspace dependencies directly to TypeScript source\n // files without requiring a prior build step.\n //\n // Since Vite 6, resolve.conditions REPLACES the defaults instead of\n // extending them. We must include defaultClientConditions to preserve\n // standard conditions like \"module\", \"browser\", \"development|production\".\n conditions: [\"source\", ...defaultClientConditions],\n alias: {\n // Work around packages whose \"module\" entry is bundler-oriented but not\n // directly Node.js ESM-resolvable in Vitest (e.g. extensionless internal\n // specifiers). Prefer the Node/CJS build for tests.\n \"@opentelemetry/api\": \"@opentelemetry/api/build/src/index.js\",\n // Auto-generate self-package alias (e.g. \"@reasonabletech/utils\" → \"./src\")\n ...(projectDir !== undefined &&\n projectDir !== \"\" &&\n generateSelfPackageAliases(projectDir)),\n // Standard \"@\" → src alias\n ...(projectDir !== undefined &&\n projectDir !== \"\" && { \"@\": `${projectDir}/src` }),\n // Consumer-provided aliases override everything above\n ...config.resolve?.alias,\n },\n ...config.resolve,\n },\n // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is\n // independent from resolve.conditions and defaults to defaultServerConditions.\n // We must explicitly include \"source\" in ssr.resolve.conditions so\n // workspace dependencies resolve to TypeScript source during test execution.\n // Additionally, ssr.resolve.externalConditions passes \"source\" to\n // Node.js when it natively resolves externalized packages.\n //\n // Note: We intentionally omit the \"module\" export condition for SSR tests.\n // Some third-party packages expose a \"module\" build that is bundler-friendly\n // but not directly Node.js ESM-resolvable (for example: extensionless internal\n // specifiers). For tests, preferring Node-compatible (\"node\"/\"default\") entry\n // points avoids runtime import failures.\n ssr: {\n resolve: {\n conditions: [\n \"source\",\n ...defaultServerConditions.filter(\n (condition) => condition !== \"module\",\n ),\n ],\n externalConditions: [\"source\"],\n },\n },\n });\n}\n\n/** @overload */\n/**\n * Creates a configuration with extended timeouts for long-running tests.\n * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to extend.\n * @param customConfig - Additional configuration to merge when a project directory is supplied.\n * @returns A Vitest configuration suited for long-running suites.\n */\nexport function createLongRunningTestConfig(\n projectDirOrConfig?: string | VitestConfig,\n customConfig?: VitestConfig,\n): ReturnType<typeof defineConfig> {\n // Handle overloaded parameters\n let projectDir: string | undefined;\n let config: VitestConfig;\n\n if (typeof projectDirOrConfig === \"string\") {\n projectDir = projectDirOrConfig;\n config = customConfig ?? EMPTY_CONFIG;\n } else {\n projectDir = undefined;\n config = projectDirOrConfig ?? EMPTY_CONFIG;\n }\n\n const longRunningConfig: VitestConfig = {\n ...config,\n test: {\n testTimeout: 30000, // 30 seconds for long-running tests\n hookTimeout: 30000, // 30 seconds for setup/teardown hooks\n ...config.test,\n },\n };\n\n if (projectDir !== undefined) {\n return createVitestConfig(projectDir, longRunningConfig);\n }\n return createVitestConfig(longRunningConfig);\n}\n\n// Re-export for convenience\nexport { createReactConfig, createReactConfigWithPlugins } from \"./react.js\";\n\nexport default createVitestConfig;\n","/**\n * Workspace utility functions for discovering monorepo structure\n *\n * These are extracted from global-setup.ts so they can be reused by\n * the Vitest config factories (e.g. auto self-package aliasing).\n * @module @reasonabletech/config-vitest/workspace\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Walks up the directory tree from `startDir` until it finds a directory\n * containing `pnpm-workspace.yaml`, which marks the monorepo root.\n *\n * Returns `startDir` unchanged if no workspace root is found (e.g. when\n * running outside the monorepo).\n * @param startDir - The directory to start searching from\n * @returns The absolute path to the monorepo root, or `startDir` if not found\n */\nexport function findRepoRoot(startDir: string): string {\n let currentDir = startDir;\n for (;;) {\n if (existsSync(path.join(currentDir, \"pnpm-workspace.yaml\"))) {\n return currentDir;\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return startDir;\n }\n currentDir = parentDir;\n }\n}\n\n/**\n * Reads the `name` field from the `package.json` in the given directory.\n *\n * Returns `null` when:\n * - No `package.json` exists at `packageDir`\n * - The file cannot be parsed as JSON\n * - The `name` field is missing, empty, or not a string\n * @param packageDir - The directory containing the package.json to read\n * @returns The package name string, or `null` if unavailable\n */\nexport function readPackageName(packageDir: string): string | null {\n const packageJsonPath = path.join(packageDir, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n name?: unknown;\n };\n return typeof parsed.name === \"string\" && parsed.name.length > 0\n ? parsed.name\n : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["existsSync","join","defaultClientConditions","defaultServerConditions","defineConfig","existsSync","join","join","existsSync","defineConfig","defaultClientConditions","defaultServerConditions"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAarD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgBjE"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/workspace.ts"],"sourcesContent":["/**\n * Workspace utility functions for discovering monorepo structure\n *\n * These are extracted from global-setup.ts so they can be reused by\n * the Vitest config factories (e.g. auto self-package aliasing).\n * @module @reasonabletech/config-vitest/workspace\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\n\n/**\n * Walks up the directory tree from `startDir` until it finds a directory\n * containing `pnpm-workspace.yaml`, which marks the monorepo root.\n *\n * Returns `startDir` unchanged if no workspace root is found (e.g. when\n * running outside the monorepo).\n * @param startDir - The directory to start searching from\n * @returns The absolute path to the monorepo root, or `startDir` if not found\n */\nexport function findRepoRoot(startDir: string): string {\n let currentDir = startDir;\n for (;;) {\n if (existsSync(path.join(currentDir, \"pnpm-workspace.yaml\"))) {\n return currentDir;\n }\n\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n return startDir;\n }\n currentDir = parentDir;\n }\n}\n\n/**\n * Reads the `name` field from the `package.json` in the given directory.\n *\n * Returns `null` when:\n * - No `package.json` exists at `packageDir`\n * - The file cannot be parsed as JSON\n * - The `name` field is missing, empty, or not a string\n * @param packageDir - The directory containing the package.json to read\n * @returns The package name string, or `null` if unavailable\n */\nexport function readPackageName(packageDir: string): string | null {\n const packageJsonPath = path.join(packageDir, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as {\n name?: unknown;\n };\n return typeof parsed.name === \"string\" && parsed.name.length > 0\n ? parsed.name\n : null;\n } catch {\n return null;\n }\n}\n"],"mappings":";AAQA,SAAS,YAAY,oBAAoB;AACzC,YAAY,UAAU;AAWf,SAAS,aAAa,UAA0B;AACrD,MAAI,aAAa;AACjB,aAAS;AACP,QAAI,WAAgB,UAAK,YAAY,qBAAqB,CAAC,GAAG;AAC5D,aAAO;AAAA,IACT;AAEA,UAAM,YAAiB,aAAQ,UAAU;AACzC,QAAI,cAAc,YAAY;AAC5B,aAAO;AAAA,IACT;AACA,iBAAa;AAAA,EACf;AACF;AAYO,SAAS,gBAAgB,YAAmC;AACjE,QAAM,kBAAuB,UAAK,YAAY,cAAc;AAC5D,MAAI,CAAC,WAAW,eAAe,GAAG;AAChC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;AAGhE,WAAO,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,IAC3D,OAAO,OACP;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1,58 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import { findRepoRoot, readPackageName } from "./workspace.js";
3
-
4
- /**
5
- * Builds the pnpm + turbo CLI arguments needed to build all upstream
6
- * workspace dependencies of the given package.
7
- * @param packageName - The package name (e.g. `@reasonabletech/utils`)
8
- * @returns An array of CLI arguments for `spawnSync("pnpm", ...)`
9
- */
10
- export function getTurboBuildArgs(packageName: string): readonly string[] {
11
- return [
12
- "-w",
13
- "turbo",
14
- "run",
15
- "build",
16
- `--filter=${packageName}^...`,
17
- ] as const;
18
- }
19
-
20
- /**
21
- * Vitest global setup that builds workspace dependencies before tests run.
22
- *
23
- * Skipped when `RT_VITEST_SKIP_DEP_BUILD=true` is set or when
24
- * a parent build is already in progress (re-entrancy guard).
25
- */
26
- export default function globalSetup(): void {
27
- if (process.env.RT_VITEST_SKIP_DEP_BUILD === "true") {
28
- return;
29
- }
30
-
31
- if (process.env.RT_VITEST_DEP_BUILD_RUNNING === "true") {
32
- return;
33
- }
34
-
35
- const packageDir = process.cwd();
36
- const packageName = readPackageName(packageDir);
37
- if (packageName === null) {
38
- return;
39
- }
40
-
41
- const repoRoot = findRepoRoot(packageDir);
42
- const args = getTurboBuildArgs(packageName);
43
-
44
- const result = spawnSync("pnpm", args, {
45
- cwd: repoRoot,
46
- stdio: "inherit",
47
- env: {
48
- ...process.env,
49
- RT_VITEST_DEP_BUILD_RUNNING: "true",
50
- },
51
- });
52
-
53
- if (result.status !== 0) {
54
- throw new Error(
55
- `Failed to build workspace dependencies (exit code: ${result.status ?? "unknown"})`,
56
- );
57
- }
58
- }
package/src/index.ts DELETED
@@ -1,280 +0,0 @@
1
- /**
2
- * Base Vitest configuration for all packages
3
- * @module @reasonabletech/config-vitest
4
- */
5
-
6
- import { existsSync } from "node:fs";
7
- import { join } from "node:path";
8
- import { defaultClientConditions, defaultServerConditions } from "vite";
9
- import { defineConfig } from "vitest/config";
10
- import type { InlineConfig } from "vitest/node";
11
- import { readPackageName } from "./workspace.js";
12
-
13
- /** Recursively makes all properties of T readonly */
14
- export type DeepReadonly<T> = {
15
- readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U>
16
- ? ReadonlyArray<DeepReadonly<U>>
17
- : T[P] extends Array<infer U>
18
- ? ReadonlyArray<DeepReadonly<U>>
19
- : T[P] extends object
20
- ? DeepReadonly<T[P]>
21
- : T[P];
22
- };
23
-
24
- /**
25
- * Immutable configuration object accepted by {@link createVitestConfig} and
26
- * related helpers.
27
- */
28
- export type VitestConfig = DeepReadonly<{
29
- test?: InlineConfig;
30
- resolve?: {
31
- conditions?: string[];
32
- alias?: Record<string, string>;
33
- [key: string]: unknown;
34
- };
35
- [key: string]: unknown;
36
- }>;
37
-
38
- // Empty readonly config for default parameters
39
- const EMPTY_CONFIG = {} as const satisfies VitestConfig;
40
-
41
- /**
42
- * Base configuration options that apply to all test environments
43
- */
44
- export const baseConfig = {
45
- test: {
46
- testTimeout: 10000, // 10 seconds default timeout
47
- hookTimeout: 10000, // 10 seconds for setup/teardown hooks
48
- coverage: {
49
- provider: "v8" as const,
50
- reporter: ["text", "html", "lcov", "json"],
51
- reportsDirectory: "./generated/test-coverage",
52
- exclude: [
53
- "**/node_modules/**",
54
- "**/dist/**",
55
- "**/tests/**",
56
- "**/*.d.ts",
57
- "**/*.config.{js,ts,mjs,mts}",
58
- "**/coverage/**",
59
- "**/examples/**",
60
- "**/src/index.ts",
61
- "**/src/*/index.ts",
62
- "**/src/types/**",
63
- "tsup.config.ts",
64
- "vitest.config.mts",
65
- "tailwind.config.mjs",
66
- "**/vitest.setup.ts",
67
- ],
68
- thresholds:
69
- process.env.VITEST_COVERAGE_THRESHOLDS_DISABLED === "true"
70
- ? {
71
- lines: 0,
72
- functions: 0,
73
- branches: 0,
74
- statements: 0,
75
- }
76
- : {
77
- lines: 100,
78
- functions: 100,
79
- branches: 100,
80
- statements: 100,
81
- },
82
- },
83
- },
84
- };
85
-
86
- /**
87
- * Generates resolve aliases that map a package's own name back to its source
88
- * directory. This allows test files to `import { foo } from "@reasonabletech/my-pkg"`
89
- * and have Vite resolve it to the local `src/` tree instead of requiring a prior
90
- * build step.
91
- *
92
- * Placed before user-supplied aliases so consumers can override if needed.
93
- * @param projectDir - Absolute path to the project directory
94
- * @returns A record mapping the package name to its `src/` directory
95
- */
96
- function generateSelfPackageAliases(
97
- projectDir: string,
98
- ): Record<string, string> {
99
- const packageName = readPackageName(projectDir);
100
- if (packageName === null) {
101
- return {};
102
- }
103
- return {
104
- [packageName]: `${projectDir}/src`,
105
- };
106
- }
107
-
108
- /**
109
- * Auto-detects setup files in a project directory
110
- * @param projectDir - The project directory path
111
- * @returns Array of detected setup file paths
112
- */
113
- function autoDetectSetupFiles(projectDir?: string): string[] {
114
- if (projectDir === undefined || projectDir === "") {
115
- return [];
116
- }
117
-
118
- const vitestSetupPath = join(projectDir, "vitest.setup.ts");
119
- if (existsSync(vitestSetupPath)) {
120
- return ["./vitest.setup.ts"];
121
- }
122
-
123
- const testsSetupPath = join(projectDir, "tests/setup.ts");
124
- if (existsSync(testsSetupPath)) {
125
- return ["./tests/setup.ts"];
126
- }
127
-
128
- return [];
129
- }
130
-
131
- /**
132
- * Auto-detects test include patterns based on project structure
133
- * @returns Array of include patterns
134
- */
135
- function autoDetectIncludePatterns(): string[] {
136
- return ["tests/**/*.test.{ts,tsx,js,jsx}"];
137
- }
138
-
139
- /** @overload */
140
- /**
141
- * Creates a merged configuration from the base and any custom options.
142
- * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.
143
- * @param customConfig - Additional configuration to merge when a project directory is provided.
144
- * @returns A merged Vitest configuration tailored for the caller.
145
- */
146
- export function createVitestConfig(
147
- projectDirOrConfig?: string | VitestConfig,
148
- customConfig?: VitestConfig,
149
- ): ReturnType<typeof defineConfig> {
150
- // Handle overloaded parameters
151
- let projectDir: string | undefined;
152
- let config: VitestConfig;
153
-
154
- if (typeof projectDirOrConfig === "string") {
155
- projectDir = projectDirOrConfig;
156
- config = customConfig ?? EMPTY_CONFIG;
157
- } else {
158
- projectDir = undefined;
159
- config = projectDirOrConfig ?? EMPTY_CONFIG;
160
- }
161
-
162
- // Auto-detect configuration if not explicitly provided
163
- const autoSetupFiles =
164
- config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0
165
- ? []
166
- : autoDetectSetupFiles(projectDir);
167
- const autoIncludePatterns =
168
- config.test?.include !== undefined && config.test.include.length > 0
169
- ? []
170
- : autoDetectIncludePatterns();
171
-
172
- return defineConfig({
173
- ...baseConfig,
174
- ...config,
175
- test: {
176
- ...baseConfig.test,
177
- // Auto-detect setupFiles if not explicitly provided
178
- ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),
179
- // Auto-detect include patterns if not explicitly provided
180
- ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),
181
- ...config.test,
182
- coverage: {
183
- ...baseConfig.test.coverage,
184
- ...config.test?.coverage,
185
- },
186
- },
187
- resolve: {
188
- // Prefer "source" condition in package.json exports, allowing
189
- // Vitest to resolve workspace dependencies directly to TypeScript source
190
- // files without requiring a prior build step.
191
- //
192
- // Since Vite 6, resolve.conditions REPLACES the defaults instead of
193
- // extending them. We must include defaultClientConditions to preserve
194
- // standard conditions like "module", "browser", "development|production".
195
- conditions: ["source", ...defaultClientConditions],
196
- alias: {
197
- // Work around packages whose "module" entry is bundler-oriented but not
198
- // directly Node.js ESM-resolvable in Vitest (e.g. extensionless internal
199
- // specifiers). Prefer the Node/CJS build for tests.
200
- "@opentelemetry/api": "@opentelemetry/api/build/src/index.js",
201
- // Auto-generate self-package alias (e.g. "@reasonabletech/utils" → "./src")
202
- ...(projectDir !== undefined &&
203
- projectDir !== "" &&
204
- generateSelfPackageAliases(projectDir)),
205
- // Standard "@" → src alias
206
- ...(projectDir !== undefined &&
207
- projectDir !== "" && { "@": `${projectDir}/src` }),
208
- // Consumer-provided aliases override everything above
209
- ...config.resolve?.alias,
210
- },
211
- ...config.resolve,
212
- },
213
- // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is
214
- // independent from resolve.conditions and defaults to defaultServerConditions.
215
- // We must explicitly include "source" in ssr.resolve.conditions so
216
- // workspace dependencies resolve to TypeScript source during test execution.
217
- // Additionally, ssr.resolve.externalConditions passes "source" to
218
- // Node.js when it natively resolves externalized packages.
219
- //
220
- // Note: We intentionally omit the "module" export condition for SSR tests.
221
- // Some third-party packages expose a "module" build that is bundler-friendly
222
- // but not directly Node.js ESM-resolvable (for example: extensionless internal
223
- // specifiers). For tests, preferring Node-compatible ("node"/"default") entry
224
- // points avoids runtime import failures.
225
- ssr: {
226
- resolve: {
227
- conditions: [
228
- "source",
229
- ...defaultServerConditions.filter(
230
- (condition) => condition !== "module",
231
- ),
232
- ],
233
- externalConditions: ["source"],
234
- },
235
- },
236
- });
237
- }
238
-
239
- /** @overload */
240
- /**
241
- * Creates a configuration with extended timeouts for long-running tests.
242
- * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to extend.
243
- * @param customConfig - Additional configuration to merge when a project directory is supplied.
244
- * @returns A Vitest configuration suited for long-running suites.
245
- */
246
- export function createLongRunningTestConfig(
247
- projectDirOrConfig?: string | VitestConfig,
248
- customConfig?: VitestConfig,
249
- ): ReturnType<typeof defineConfig> {
250
- // Handle overloaded parameters
251
- let projectDir: string | undefined;
252
- let config: VitestConfig;
253
-
254
- if (typeof projectDirOrConfig === "string") {
255
- projectDir = projectDirOrConfig;
256
- config = customConfig ?? EMPTY_CONFIG;
257
- } else {
258
- projectDir = undefined;
259
- config = projectDirOrConfig ?? EMPTY_CONFIG;
260
- }
261
-
262
- const longRunningConfig: VitestConfig = {
263
- ...config,
264
- test: {
265
- testTimeout: 30000, // 30 seconds for long-running tests
266
- hookTimeout: 30000, // 30 seconds for setup/teardown hooks
267
- ...config.test,
268
- },
269
- };
270
-
271
- if (projectDir !== undefined) {
272
- return createVitestConfig(projectDir, longRunningConfig);
273
- }
274
- return createVitestConfig(longRunningConfig);
275
- }
276
-
277
- // Re-export for convenience
278
- export { createReactConfig, createReactConfigWithPlugins } from "./react.js";
279
-
280
- export default createVitestConfig;
package/src/node.ts DELETED
@@ -1,74 +0,0 @@
1
- /**
2
- * Node.js-specific Vitest configuration
3
- * @module @reasonabletech/config-vitest/node
4
- */
5
-
6
- import type { InlineConfig } from "vitest/node";
7
- import type { UserConfig } from "vite";
8
- import { createVitestConfig, type DeepReadonly } from "./index.js";
9
-
10
- /** Immutable Vitest configuration type combining Vite UserConfig with Vitest InlineConfig */
11
- export type VitestConfig = DeepReadonly<UserConfig> & {
12
- readonly test?: DeepReadonly<InlineConfig>;
13
- };
14
-
15
- // Empty readonly config for default parameters
16
- const EMPTY_CONFIG = {} as const satisfies VitestConfig;
17
-
18
- /**
19
- * Node.js-specific configuration options
20
- */
21
- export const nodeConfig = {
22
- test: {
23
- environment: "node" as const,
24
- include: ["tests/**/*.test.ts"],
25
- },
26
- } as const satisfies VitestConfig;
27
-
28
- /**
29
- * Creates a Vitest configuration for Node.js-based packages.
30
- * @param projectDirOrConfig - Either the absolute project directory (use `import.meta.dirname`) or a prebuilt config.
31
- * @param customConfig - Additional configuration to merge when a project directory is provided.
32
- * @returns A Vitest configuration optimized for Node.js environments
33
- */
34
- export function createNodeConfig(
35
- projectDirOrConfig?: string | VitestConfig,
36
- customConfig?: VitestConfig,
37
- ): ReturnType<typeof createVitestConfig> {
38
- // Handle overloaded parameters
39
- let projectDir: string | undefined;
40
- let config: VitestConfig;
41
-
42
- if (typeof projectDirOrConfig === "string") {
43
- projectDir = projectDirOrConfig;
44
- config = customConfig ?? EMPTY_CONFIG;
45
- } else {
46
- projectDir = undefined;
47
- config = projectDirOrConfig ?? EMPTY_CONFIG;
48
- }
49
-
50
- // Merge node-specific settings with consumer config
51
- const mergedConfig = {
52
- ...nodeConfig,
53
- ...config,
54
- test: {
55
- ...nodeConfig.test,
56
- ...config.test,
57
- },
58
- };
59
-
60
- // Delegate to createVitestConfig which handles aliasing and base config.
61
- // When projectDir is present, pass both args so createVitestConfig generates aliases.
62
- // When absent, pass the merged config as the sole argument.
63
- if (projectDir !== undefined) {
64
- return createVitestConfig(
65
- projectDir,
66
- mergedConfig as Parameters<typeof createVitestConfig>[1],
67
- );
68
- }
69
- return createVitestConfig(
70
- mergedConfig as Parameters<typeof createVitestConfig>[0],
71
- );
72
- }
73
-
74
- export default createNodeConfig;
package/src/react.ts DELETED
@@ -1,241 +0,0 @@
1
- /**
2
- * React-specific Vitest configuration
3
- * @module @reasonabletech/config-vitest/react
4
- */
5
-
6
- import { existsSync } from "node:fs";
7
- import { join } from "node:path";
8
- import {
9
- defaultClientConditions,
10
- defaultServerConditions,
11
- type PluginOption,
12
- } from "vite";
13
- import { defineConfig } from "vitest/config";
14
- import type { InlineConfig } from "vitest/node";
15
- import react from "@vitejs/plugin-react";
16
- import { baseConfig, type DeepReadonly } from "./index.js";
17
- import { readPackageName } from "./workspace.js";
18
-
19
- type VitestConfig = DeepReadonly<{
20
- test?: InlineConfig;
21
- resolve?: {
22
- alias?: Record<string, string>;
23
- [key: string]: unknown;
24
- };
25
- [key: string]: unknown;
26
- }>;
27
-
28
- // Empty readonly config and plugins array for default parameters
29
- const EMPTY_CONFIG = {} as const satisfies VitestConfig;
30
- const EMPTY_PLUGINS = [] as const satisfies readonly PluginOption[];
31
-
32
- /**
33
- * Generates resolve aliases mapping a package's own name to its source directory.
34
- * @param projectDir - Absolute path to the project directory
35
- * @returns A record mapping the package name to its `src/` directory
36
- * @see index.ts for the equivalent function used in the base config
37
- */
38
- function generateSelfPackageAliases(
39
- projectDir: string,
40
- ): Record<string, string> {
41
- const packageName = readPackageName(projectDir);
42
- if (packageName === null) {
43
- return {};
44
- }
45
- return {
46
- [packageName]: `${projectDir}/src`,
47
- };
48
- }
49
-
50
- /**
51
- * Auto-detects setup files in a project directory
52
- * @param projectDir - The project directory path
53
- * @returns Array of detected setup file paths
54
- */
55
- function autoDetectSetupFiles(projectDir?: string): string[] {
56
- if (projectDir === undefined || projectDir === "") {
57
- return [];
58
- }
59
-
60
- const vitestSetupPath = join(projectDir, "vitest.setup.ts");
61
- if (existsSync(vitestSetupPath)) {
62
- return ["./vitest.setup.ts"];
63
- }
64
-
65
- const testsSetupPath = join(projectDir, "tests/setup.ts");
66
- if (existsSync(testsSetupPath)) {
67
- return ["./tests/setup.ts"];
68
- }
69
-
70
- return [];
71
- }
72
-
73
- /**
74
- * Auto-detects test include patterns based on project structure
75
- * @returns Array of include patterns
76
- */
77
- function autoDetectIncludePatterns(): string[] {
78
- return ["tests/**/*.test.{ts,tsx,js,jsx}"];
79
- }
80
-
81
- /**
82
- * React-specific configuration options
83
- */
84
- export const reactConfig = {
85
- test: {
86
- environment: "jsdom",
87
- exclude: ["**/node_modules/**", "**/dist/**"],
88
- // Suppress vitest's unhandled error reporting for expected test errors
89
- silent: false,
90
- onConsoleLog: (): boolean | undefined => {
91
- // Allow packages to customize console filtering in their own config
92
- return undefined;
93
- },
94
- },
95
- };
96
-
97
- /** @overload */
98
- /**
99
- * Creates a Vitest configuration for React-based packages.
100
- * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.
101
- * @param customConfig - Additional configuration options for the project-dir overload.
102
- * @returns A Vitest configuration optimized for React components.
103
- */
104
- export function createReactConfig(
105
- projectDirOrConfig?: string | VitestConfig,
106
- customConfig?: VitestConfig,
107
- ): ReturnType<typeof defineConfig> {
108
- // Handle overloaded parameters
109
- let projectDir: string | undefined;
110
- let config: VitestConfig;
111
-
112
- if (typeof projectDirOrConfig === "string") {
113
- projectDir = projectDirOrConfig;
114
- config = customConfig ?? ({} as const);
115
- } else {
116
- projectDir = undefined;
117
- config = projectDirOrConfig ?? ({} as const);
118
- }
119
-
120
- if (projectDir !== undefined) {
121
- return createReactConfigWithPlugins([react()], projectDir, config);
122
- }
123
- return createReactConfigWithPlugins([react()], config);
124
- }
125
-
126
- /** @overload */
127
- /**
128
- * Creates a Vitest configuration for React-based packages with plugins.
129
- * @param plugins - Array of Vite plugins to include.
130
- * @param projectDirOrConfig - Either the absolute project directory or an existing configuration to merge.
131
- * @param customConfig - Additional configuration options for the project-dir overload.
132
- * @returns A Vitest configuration optimized for React components with plugins.
133
- */
134
- export function createReactConfigWithPlugins(
135
- plugins: readonly PluginOption[] = EMPTY_PLUGINS,
136
- projectDirOrConfig?: string | VitestConfig,
137
- customConfig?: VitestConfig,
138
- ): ReturnType<typeof defineConfig> {
139
- // Handle overloaded parameters
140
- let projectDir: string | undefined;
141
- let config: VitestConfig;
142
-
143
- if (typeof projectDirOrConfig === "string") {
144
- projectDir = projectDirOrConfig;
145
- config = customConfig ?? EMPTY_CONFIG;
146
- } else {
147
- projectDir = undefined;
148
- config = projectDirOrConfig ?? EMPTY_CONFIG;
149
- }
150
-
151
- // Auto-detect configuration if not explicitly provided
152
- const autoSetupFiles =
153
- config.test?.setupFiles !== undefined && config.test.setupFiles.length > 0
154
- ? []
155
- : autoDetectSetupFiles(projectDir);
156
- const autoIncludePatterns =
157
- config.test?.include !== undefined && config.test.include.length > 0
158
- ? []
159
- : autoDetectIncludePatterns();
160
-
161
- return defineConfig({
162
- plugins: [...plugins],
163
- ...baseConfig,
164
- ...reactConfig,
165
- ...config,
166
- test: {
167
- ...baseConfig.test,
168
- ...reactConfig.test,
169
- // Auto-detect setupFiles if not explicitly provided
170
- ...(autoSetupFiles.length > 0 && { setupFiles: autoSetupFiles }),
171
- // Auto-detect include patterns if not explicitly provided
172
- ...(autoIncludePatterns.length > 0 && { include: autoIncludePatterns }),
173
- ...config.test,
174
- coverage: {
175
- ...baseConfig.test.coverage,
176
- ...config.test?.coverage,
177
- exclude: [
178
- "**/node_modules/**",
179
- "**/dist/**",
180
- "**/tests/**",
181
- "**/*.d.ts",
182
- "**/*.config.{js,ts,mjs,mts}",
183
- "**/coverage/**",
184
- "**/examples/**",
185
- "**/src/index.ts",
186
- "**/src/*/index.ts",
187
- "**/src/types/**",
188
- "tsup.config.ts",
189
- "vitest.config.mts",
190
- "tailwind.config.mjs",
191
- "**/.next/**",
192
- "**/vitest.setup.ts",
193
- "**/types.ts",
194
- ],
195
- },
196
- },
197
- resolve: {
198
- // Prefer "source" condition in package.json exports, allowing
199
- // Vitest to resolve workspace dependencies directly to TypeScript source
200
- // files without requiring a prior build step.
201
- //
202
- // Since Vite 6, resolve.conditions REPLACES the defaults instead of
203
- // extending them. We must include defaultClientConditions to preserve
204
- // standard conditions like "module", "browser", "development|production".
205
- conditions: ["source", ...defaultClientConditions],
206
- // Deduplicate React to prevent multiple-instance issues in tests.
207
- // This is Vite's standard API for singleton enforcement — no filesystem
208
- // paths needed, Vite resolves from the project root automatically.
209
- dedupe: [
210
- "react",
211
- "react-dom",
212
- "react/jsx-runtime",
213
- "react/jsx-dev-runtime",
214
- ],
215
- alias: {
216
- // Auto-generate self-package alias
217
- ...(projectDir !== undefined &&
218
- projectDir !== "" &&
219
- generateSelfPackageAliases(projectDir)),
220
- // Standard "@" → src alias
221
- ...(projectDir !== undefined &&
222
- projectDir !== "" && { "@": `${projectDir}/src` }),
223
- // Consumer-provided aliases override everything above
224
- ...config.resolve?.alias,
225
- },
226
- ...config.resolve,
227
- },
228
- // Vitest runs in Vite's SSR mode. Since Vite 6, ssr.resolve.conditions is
229
- // independent from resolve.conditions and defaults to defaultServerConditions.
230
- // We must explicitly include "source" in ssr.resolve.conditions so
231
- // workspace dependencies resolve to TypeScript source during test execution.
232
- ssr: {
233
- resolve: {
234
- conditions: ["source", ...defaultServerConditions],
235
- externalConditions: ["source"],
236
- },
237
- },
238
- });
239
- }
240
-
241
- export default createReactConfig;
package/src/workspace.ts DELETED
@@ -1,62 +0,0 @@
1
- /**
2
- * Workspace utility functions for discovering monorepo structure
3
- *
4
- * These are extracted from global-setup.ts so they can be reused by
5
- * the Vitest config factories (e.g. auto self-package aliasing).
6
- * @module @reasonabletech/config-vitest/workspace
7
- */
8
-
9
- import { existsSync, readFileSync } from "node:fs";
10
- import * as path from "node:path";
11
-
12
- /**
13
- * Walks up the directory tree from `startDir` until it finds a directory
14
- * containing `pnpm-workspace.yaml`, which marks the monorepo root.
15
- *
16
- * Returns `startDir` unchanged if no workspace root is found (e.g. when
17
- * running outside the monorepo).
18
- * @param startDir - The directory to start searching from
19
- * @returns The absolute path to the monorepo root, or `startDir` if not found
20
- */
21
- export function findRepoRoot(startDir: string): string {
22
- let currentDir = startDir;
23
- for (;;) {
24
- if (existsSync(path.join(currentDir, "pnpm-workspace.yaml"))) {
25
- return currentDir;
26
- }
27
-
28
- const parentDir = path.dirname(currentDir);
29
- if (parentDir === currentDir) {
30
- return startDir;
31
- }
32
- currentDir = parentDir;
33
- }
34
- }
35
-
36
- /**
37
- * Reads the `name` field from the `package.json` in the given directory.
38
- *
39
- * Returns `null` when:
40
- * - No `package.json` exists at `packageDir`
41
- * - The file cannot be parsed as JSON
42
- * - The `name` field is missing, empty, or not a string
43
- * @param packageDir - The directory containing the package.json to read
44
- * @returns The package name string, or `null` if unavailable
45
- */
46
- export function readPackageName(packageDir: string): string | null {
47
- const packageJsonPath = path.join(packageDir, "package.json");
48
- if (!existsSync(packageJsonPath)) {
49
- return null;
50
- }
51
-
52
- try {
53
- const parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
54
- name?: unknown;
55
- };
56
- return typeof parsed.name === "string" && parsed.name.length > 0
57
- ? parsed.name
58
- : null;
59
- } catch {
60
- return null;
61
- }
62
- }