Skip to content

Commit d69e116

Browse files
committed
fix(coverage): resolve imports to src/*.ts for accurate v8 coverage attribution
Coverage was reporting ~50% because imports resolved to dist/*.js CJS bundles instead of src/*.ts source files. The v8 provider instruments runtime execution, so coverage was attributed to dist/ files which the coverage config excluded from reporting. Root causes: - Vitest's internal config hooks override resolve.alias and resolve.conditions, so user-provided aliases were never applied - The cover script did not pass --config to vitest, so the vitest config file was never loaded for coverage runs Fix: - Add "source" export condition to all 147 package.json export entries, pointing to the corresponding src/*.ts file - Replace resolve.alias with a Vite plugin (sourceResolverPlugin) that uses resolveId to intercept @socketsecurity/lib/* imports and map them to src/*.ts, bypassing vitest's config override behavior - Fix cover script to pass --config .config/vitest.config.mts - Remove single-thread constraint for coverage (no longer needed) - Increase archive test timeouts for coverage mode compatibility - Raise coverage thresholds to match new baseline (~80% lines) Coverage: 50% -> 81% lines, 34% -> 70% branches, 53% -> 90% functions
1 parent d91fe94 commit d69e116

5 files changed

Lines changed: 227 additions & 34 deletions

File tree

.config/vitest.config.mts

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
* @fileoverview Vitest configuration for socket-lib
33
*/
44

5+
import fs from 'node:fs'
56
import path from 'node:path'
67
import { fileURLToPath } from 'node:url'
78
import process from 'node:process'
89
import { defineConfig } from 'vitest/config'
910

11+
import type { Plugin } from 'vitest/config'
12+
1013
const __dirname = path.dirname(fileURLToPath(import.meta.url))
1114
const projectRoot = path.resolve(__dirname, '..')
1215

@@ -19,28 +22,73 @@ const isCoverageEnabled =
1922
process.env.npm_lifecycle_event?.includes('coverage') ||
2023
process.argv.some(arg => arg.includes('coverage'))
2124

25+
// Vite plugin that resolves @socketsecurity/lib/* imports to src/*.ts source
26+
// files and applies module aliases. Vitest's internal config hooks override
27+
// resolve.alias and resolve.conditions, so we use a plugin to ensure our
28+
// resolution takes effect.
29+
function sourceResolverPlugin(): Plugin {
30+
const LIB_PREFIX = '@socketsecurity/lib/'
31+
const LIB_EXACT = '@socketsecurity/lib'
32+
const aliasMap: Record<string, string> = {
33+
cacache: path.resolve(projectRoot, 'src/external/cacache'),
34+
'make-fetch-happen': path.resolve(
35+
projectRoot,
36+
'src/external/make-fetch-happen',
37+
),
38+
'fast-sort': path.resolve(projectRoot, 'src/external/fast-sort'),
39+
pacote: path.resolve(projectRoot, 'src/external/pacote'),
40+
'@socketregistry/scripts': path.resolve(projectRoot, 'scripts'),
41+
}
42+
43+
return {
44+
name: 'vitest:source-resolver',
45+
enforce: 'pre',
46+
resolveId(source) {
47+
// Resolve @socketsecurity/lib/* to src/*.ts
48+
if (source.startsWith(LIB_PREFIX)) {
49+
const subpath = source.slice(LIB_PREFIX.length)
50+
// Try direct .ts file first, then index.ts in subdirectory
51+
const directPath = path.resolve(projectRoot, 'src', `${subpath}.ts`)
52+
if (fs.existsSync(directPath)) return directPath
53+
const indexPath = path.resolve(projectRoot, 'src', subpath, 'index.ts')
54+
if (fs.existsSync(indexPath)) return indexPath
55+
return undefined
56+
}
57+
if (source === LIB_EXACT) {
58+
return path.resolve(projectRoot, 'src/index.ts')
59+
}
60+
// Resolve aliased external dependencies
61+
for (const [alias, target] of Object.entries(aliasMap)) {
62+
if (source === alias || source.startsWith(`${alias}/`)) {
63+
const rest = source === alias ? '' : source.slice(alias.length)
64+
return `${target}${rest}`
65+
}
66+
}
67+
return undefined
68+
},
69+
// Inject 'source' export condition into ssr resolve for worker processes
70+
config() {
71+
if (!isCoverageEnabled) return undefined
72+
return {
73+
ssr: {
74+
resolve: {
75+
conditions: ['source'],
76+
externalConditions: ['source'],
77+
},
78+
},
79+
}
80+
},
81+
}
82+
}
83+
2284
const vitestConfig = defineConfig({
2385
cacheDir: path.resolve(projectRoot, '.cache/vitest'),
86+
plugins: [sourceResolverPlugin()],
2487
resolve: {
2588
preserveSymlinks: false,
2689
extensions: isCoverageEnabled
2790
? ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs', '.json']
2891
: ['.mts', '.ts', '.mjs', '.js', '.json'],
29-
alias: {
30-
cacache: path.resolve(projectRoot, 'src/external/cacache'),
31-
'make-fetch-happen': path.resolve(
32-
projectRoot,
33-
'src/external/make-fetch-happen',
34-
),
35-
'fast-sort': path.resolve(projectRoot, 'src/external/fast-sort'),
36-
pacote: path.resolve(projectRoot, 'src/external/pacote'),
37-
'@socketregistry/scripts': path.resolve(projectRoot, 'scripts'),
38-
'@socketsecurity/lib/stdio/prompts': path.resolve(
39-
projectRoot,
40-
'src/stdio/prompts/index.ts',
41-
),
42-
'@socketsecurity/lib': path.resolve(projectRoot, 'src'),
43-
},
4492
},
4593
test: {
4694
globalSetup: [path.resolve(__dirname, 'vitest-global-setup.mts')],
@@ -75,11 +123,8 @@ const vitestConfig = defineConfig({
75123
pool: process.env.CI ? 'forks' : 'threads',
76124
poolOptions: {
77125
threads: {
78-
// Maximize parallelism for speed
79-
// During coverage, use single thread for deterministic execution
80-
singleThread: isCoverageEnabled,
81-
maxThreads: isCoverageEnabled ? 1 : 16,
82-
minThreads: isCoverageEnabled ? 1 : 4,
126+
maxThreads: 16,
127+
minThreads: 4,
83128
// IMPORTANT: isolate: false for performance and test compatibility
84129
//
85130
// Tradeoff Analysis:
@@ -99,9 +144,8 @@ const vitestConfig = defineConfig({
99144
forks: {
100145
// CI: Use forks for stability (no worker timeout issues)
101146
// Limit forks in CI to prevent file system contention on Windows
102-
singleFork: isCoverageEnabled,
103-
maxForks: isCoverageEnabled ? 1 : process.env.CI ? 4 : 16,
104-
minForks: isCoverageEnabled ? 1 : process.env.CI ? 2 : 4,
147+
maxForks: process.env.CI ? 4 : 16,
148+
minForks: process.env.CI ? 2 : 4,
105149
isolate: true,
106150
},
107151
},
@@ -165,10 +209,10 @@ const vitestConfig = defineConfig({
165209
skipFull: false,
166210
ignoreClassMethods: ['constructor'],
167211
thresholds: {
168-
lines: 68,
169-
functions: 70,
170-
branches: 70,
171-
statements: 68,
212+
lines: 80,
213+
functions: 88,
214+
branches: 68,
215+
statements: 80,
172216
},
173217
},
174218
},

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Socket Badge](https://socket.dev/api/badge/npm/package/@socketsecurity/lib)](https://socket.dev/npm/package/@socketsecurity/lib)
44
[![CI](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml/badge.svg)](https://github.com/SocketDev/socket-lib/actions/workflows/ci.yml)
5-
![Coverage](https://img.shields.io/badge/coverage-84%25-brightgreen)
5+
![Coverage](https://img.shields.io/badge/coverage-81%25-brightgreen)
66

77
[![Follow @SocketSecurity](https://img.shields.io/twitter/follow/SocketSecurity?style=social)](https://twitter.com/SocketSecurity)
88
[![Follow @socket.dev on Bluesky](https://img.shields.io/badge/Follow-@socket.dev-1DA1F2?style=social&logo=bluesky)](https://bsky.app/profile/socket.dev)

0 commit comments

Comments
 (0)