elit 3.3.3 → 3.3.5
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/dist/build.d.mts +1 -1
- package/dist/build.js +1 -0
- package/dist/build.js.map +1 -0
- package/dist/build.mjs +1 -0
- package/dist/build.mjs.map +1 -0
- package/dist/chokidar.js +1 -0
- package/dist/chokidar.js.map +1 -0
- package/dist/chokidar.mjs +1 -0
- package/dist/chokidar.mjs.map +1 -0
- package/dist/cli.js +4720 -37
- package/dist/config.d.mts +3 -1
- package/dist/config.d.ts +3 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +1 -0
- package/dist/config.mjs.map +1 -0
- package/dist/coverage.d.mts +85 -0
- package/dist/coverage.d.ts +76 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +1549 -0
- package/dist/coverage.js.map +1 -0
- package/dist/coverage.mjs +1520 -0
- package/dist/coverage.mjs.map +1 -0
- package/dist/database.d.mts +31 -6
- package/dist/database.d.ts +31 -6
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +60 -33
- package/dist/database.js.map +1 -0
- package/dist/database.mjs +60 -33
- package/dist/database.mjs.map +1 -0
- package/dist/dom.js +1 -0
- package/dist/dom.js.map +1 -0
- package/dist/dom.mjs +1 -0
- package/dist/dom.mjs.map +1 -0
- package/dist/el.js +1 -0
- package/dist/el.js.map +1 -0
- package/dist/el.mjs +1 -0
- package/dist/el.mjs.map +1 -0
- package/dist/fs.js +1 -0
- package/dist/fs.js.map +1 -0
- package/dist/fs.mjs +1 -0
- package/dist/fs.mjs.map +1 -0
- package/dist/hmr.js +1 -0
- package/dist/hmr.js.map +1 -0
- package/dist/hmr.mjs +1 -0
- package/dist/hmr.mjs.map +1 -0
- package/dist/http.js +1 -0
- package/dist/http.js.map +1 -0
- package/dist/http.mjs +1 -0
- package/dist/http.mjs.map +1 -0
- package/dist/https.d.mts +1 -1
- package/dist/https.js +1 -0
- package/dist/https.js.map +1 -0
- package/dist/https.mjs +1 -0
- package/dist/https.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mime-types.js +1 -0
- package/dist/mime-types.js.map +1 -0
- package/dist/mime-types.mjs +1 -0
- package/dist/mime-types.mjs.map +1 -0
- package/dist/path.js +1 -0
- package/dist/path.js.map +1 -0
- package/dist/path.mjs +1 -0
- package/dist/path.mjs.map +1 -0
- package/dist/router.js +1 -0
- package/dist/router.js.map +1 -0
- package/dist/router.mjs +1 -0
- package/dist/router.mjs.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/runtime.js.map +1 -0
- package/dist/runtime.mjs +1 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/{server-Cz3z-5ls.d.mts → server-BFTzgJpO.d.mts} +62 -1
- package/dist/{server-BG2CaVMh.d.ts → server-CIXtexNS.d.ts} +62 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +45 -3
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +45 -3
- package/dist/server.mjs.map +1 -0
- package/dist/state.d.mts +1 -1
- package/dist/state.js +1 -0
- package/dist/state.js.map +1 -0
- package/dist/state.mjs +1 -0
- package/dist/state.mjs.map +1 -0
- package/dist/style.js +1 -0
- package/dist/style.js.map +1 -0
- package/dist/style.mjs +1 -0
- package/dist/style.mjs.map +1 -0
- package/dist/test-globals.d.ts +184 -0
- package/dist/test-reporter.d.mts +77 -0
- package/dist/test-reporter.d.ts +73 -0
- package/dist/test-reporter.d.ts.map +1 -0
- package/dist/test-reporter.js +726 -0
- package/dist/test-reporter.js.map +1 -0
- package/dist/test-reporter.mjs +696 -0
- package/dist/test-reporter.mjs.map +1 -0
- package/dist/test-runtime.d.mts +122 -0
- package/dist/test-runtime.d.ts +120 -0
- package/dist/test-runtime.d.ts.map +1 -0
- package/dist/test-runtime.js +1292 -0
- package/dist/test-runtime.js.map +1 -0
- package/dist/test-runtime.mjs +1269 -0
- package/dist/test-runtime.mjs.map +1 -0
- package/dist/test.d.mts +39 -0
- package/dist/test.d.ts +38 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +4966 -0
- package/dist/test.js.map +1 -0
- package/dist/test.mjs +4944 -0
- package/dist/test.mjs.map +1 -0
- package/dist/types.d.mts +62 -1
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +24 -2
- package/dist/ws.js.map +1 -0
- package/dist/ws.mjs +24 -2
- package/dist/ws.mjs.map +1 -0
- package/dist/wss.js +24 -2
- package/dist/wss.js.map +1 -0
- package/dist/wss.mjs +24 -2
- package/dist/wss.mjs.map +1 -0
- package/package.json +37 -5
- package/src/cli.ts +165 -1
- package/src/config.ts +3 -1
- package/src/coverage.ts +1479 -0
- package/src/database.ts +71 -35
- package/src/server.ts +25 -1
- package/src/test-globals.d.ts +184 -0
- package/src/test-reporter.ts +609 -0
- package/src/test-runtime.ts +1359 -0
- package/src/test.ts +368 -0
- package/src/types.ts +59 -0
- package/src/ws.ts +32 -2
|
@@ -0,0 +1,1520 @@
|
|
|
1
|
+
import {createRequire as __createRequire} from 'module';const require = __createRequire(import.meta.url);
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/runtime.ts
|
|
10
|
+
var runtime = (() => {
|
|
11
|
+
if (typeof Deno !== "undefined") return "deno";
|
|
12
|
+
if (typeof Bun !== "undefined") return "bun";
|
|
13
|
+
return "node";
|
|
14
|
+
})();
|
|
15
|
+
var isNode = runtime === "node";
|
|
16
|
+
var isBun = runtime === "bun";
|
|
17
|
+
var isDeno = runtime === "deno";
|
|
18
|
+
|
|
19
|
+
// src/fs.ts
|
|
20
|
+
var isBunOrDeno = isBun || isDeno;
|
|
21
|
+
function parseOptions(options, defaultValue) {
|
|
22
|
+
return typeof options === "string" ? { encoding: options } : options || defaultValue;
|
|
23
|
+
}
|
|
24
|
+
function decodeContent(content, encoding) {
|
|
25
|
+
if (encoding) {
|
|
26
|
+
return new TextDecoder(encoding).decode(content);
|
|
27
|
+
}
|
|
28
|
+
return Buffer.from(content instanceof ArrayBuffer ? new Uint8Array(content) : content);
|
|
29
|
+
}
|
|
30
|
+
function dataToUint8Array(data) {
|
|
31
|
+
if (typeof data === "string") {
|
|
32
|
+
return new TextEncoder().encode(data);
|
|
33
|
+
}
|
|
34
|
+
if (data instanceof Buffer) {
|
|
35
|
+
return new Uint8Array(data);
|
|
36
|
+
}
|
|
37
|
+
return data;
|
|
38
|
+
}
|
|
39
|
+
function processDenoEntries(iterator, withFileTypes) {
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const entry of iterator) {
|
|
42
|
+
if (withFileTypes) {
|
|
43
|
+
entries.push(createDirentFromDenoEntry(entry));
|
|
44
|
+
} else {
|
|
45
|
+
entries.push(entry.name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return entries;
|
|
49
|
+
}
|
|
50
|
+
var fs;
|
|
51
|
+
var fsPromises;
|
|
52
|
+
if (isNode) {
|
|
53
|
+
fs = __require("fs");
|
|
54
|
+
fsPromises = __require("fs/promises");
|
|
55
|
+
}
|
|
56
|
+
function readFileSync(path, options) {
|
|
57
|
+
const opts = parseOptions(options, {});
|
|
58
|
+
if (isNode) {
|
|
59
|
+
return fs.readFileSync(path, opts);
|
|
60
|
+
} else if (isBun) {
|
|
61
|
+
const file = Bun.file(path);
|
|
62
|
+
const content = file.arrayBuffer();
|
|
63
|
+
return decodeContent(content, opts.encoding);
|
|
64
|
+
} else if (isDeno) {
|
|
65
|
+
const content = Deno.readFileSync(path);
|
|
66
|
+
return decodeContent(content, opts.encoding);
|
|
67
|
+
}
|
|
68
|
+
throw new Error("Unsupported runtime");
|
|
69
|
+
}
|
|
70
|
+
function writeFileSync(path, data, options) {
|
|
71
|
+
const opts = parseOptions(options, {});
|
|
72
|
+
if (isNode) {
|
|
73
|
+
fs.writeFileSync(path, data, opts);
|
|
74
|
+
} else if (isBun) {
|
|
75
|
+
Bun.write(path, data);
|
|
76
|
+
} else if (isDeno) {
|
|
77
|
+
Deno.writeFileSync(path, dataToUint8Array(data));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function existsSync(path) {
|
|
81
|
+
try {
|
|
82
|
+
statSync(path);
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function statSync(path) {
|
|
89
|
+
if (isNode) {
|
|
90
|
+
return fs.statSync(path);
|
|
91
|
+
} else if (isBun) {
|
|
92
|
+
const file = Bun.file(path);
|
|
93
|
+
const size = file.size;
|
|
94
|
+
try {
|
|
95
|
+
file.arrayBuffer();
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
98
|
+
}
|
|
99
|
+
return createStatsObject(path, size, false);
|
|
100
|
+
} else if (isDeno) {
|
|
101
|
+
const info = Deno.statSync(path);
|
|
102
|
+
return createStatsFromDenoFileInfo(info);
|
|
103
|
+
}
|
|
104
|
+
throw new Error("Unsupported runtime");
|
|
105
|
+
}
|
|
106
|
+
function mkdirSync(path, options) {
|
|
107
|
+
const opts = typeof options === "number" ? { mode: options } : options || {};
|
|
108
|
+
if (isNode) {
|
|
109
|
+
fs.mkdirSync(path, opts);
|
|
110
|
+
} else if (isBun) {
|
|
111
|
+
Deno.mkdirSync(path, { recursive: opts.recursive });
|
|
112
|
+
} else if (isDeno) {
|
|
113
|
+
Deno.mkdirSync(path, { recursive: opts.recursive });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function readdirSync(path, options) {
|
|
117
|
+
const opts = parseOptions(options, {});
|
|
118
|
+
if (isNode) {
|
|
119
|
+
return fs.readdirSync(path, opts);
|
|
120
|
+
} else if (isBunOrDeno) {
|
|
121
|
+
return processDenoEntries(Deno.readDirSync(path), opts.withFileTypes);
|
|
122
|
+
}
|
|
123
|
+
throw new Error("Unsupported runtime");
|
|
124
|
+
}
|
|
125
|
+
function createStatsObject(_path, size, isDir) {
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
return {
|
|
128
|
+
isFile: () => !isDir,
|
|
129
|
+
isDirectory: () => isDir,
|
|
130
|
+
isBlockDevice: () => false,
|
|
131
|
+
isCharacterDevice: () => false,
|
|
132
|
+
isSymbolicLink: () => false,
|
|
133
|
+
isFIFO: () => false,
|
|
134
|
+
isSocket: () => false,
|
|
135
|
+
dev: 0,
|
|
136
|
+
ino: 0,
|
|
137
|
+
mode: isDir ? 16877 : 33188,
|
|
138
|
+
nlink: 1,
|
|
139
|
+
uid: 0,
|
|
140
|
+
gid: 0,
|
|
141
|
+
rdev: 0,
|
|
142
|
+
size,
|
|
143
|
+
blksize: 4096,
|
|
144
|
+
blocks: Math.ceil(size / 512),
|
|
145
|
+
atimeMs: now,
|
|
146
|
+
mtimeMs: now,
|
|
147
|
+
ctimeMs: now,
|
|
148
|
+
birthtimeMs: now,
|
|
149
|
+
atime: new Date(now),
|
|
150
|
+
mtime: new Date(now),
|
|
151
|
+
ctime: new Date(now),
|
|
152
|
+
birthtime: new Date(now)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function createStatsFromDenoFileInfo(info) {
|
|
156
|
+
return {
|
|
157
|
+
isFile: () => info.isFile,
|
|
158
|
+
isDirectory: () => info.isDirectory,
|
|
159
|
+
isBlockDevice: () => false,
|
|
160
|
+
isCharacterDevice: () => false,
|
|
161
|
+
isSymbolicLink: () => info.isSymlink || false,
|
|
162
|
+
isFIFO: () => false,
|
|
163
|
+
isSocket: () => false,
|
|
164
|
+
dev: info.dev || 0,
|
|
165
|
+
ino: info.ino || 0,
|
|
166
|
+
mode: info.mode || 0,
|
|
167
|
+
nlink: info.nlink || 1,
|
|
168
|
+
uid: info.uid || 0,
|
|
169
|
+
gid: info.gid || 0,
|
|
170
|
+
rdev: 0,
|
|
171
|
+
size: info.size,
|
|
172
|
+
blksize: info.blksize || 4096,
|
|
173
|
+
blocks: info.blocks || Math.ceil(info.size / 512),
|
|
174
|
+
atimeMs: info.atime?.getTime() || Date.now(),
|
|
175
|
+
mtimeMs: info.mtime?.getTime() || Date.now(),
|
|
176
|
+
ctimeMs: info.birthtime?.getTime() || Date.now(),
|
|
177
|
+
birthtimeMs: info.birthtime?.getTime() || Date.now(),
|
|
178
|
+
atime: info.atime || /* @__PURE__ */ new Date(),
|
|
179
|
+
mtime: info.mtime || /* @__PURE__ */ new Date(),
|
|
180
|
+
ctime: info.birthtime || /* @__PURE__ */ new Date(),
|
|
181
|
+
birthtime: info.birthtime || /* @__PURE__ */ new Date()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function createDirentFromDenoEntry(entry) {
|
|
185
|
+
return {
|
|
186
|
+
name: entry.name,
|
|
187
|
+
isFile: () => entry.isFile,
|
|
188
|
+
isDirectory: () => entry.isDirectory,
|
|
189
|
+
isBlockDevice: () => false,
|
|
190
|
+
isCharacterDevice: () => false,
|
|
191
|
+
isSymbolicLink: () => entry.isSymlink || false,
|
|
192
|
+
isFIFO: () => false,
|
|
193
|
+
isSocket: () => false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/path.ts
|
|
198
|
+
function getSeparator(isWin) {
|
|
199
|
+
return isWin ? "\\" : "/";
|
|
200
|
+
}
|
|
201
|
+
function getCwd() {
|
|
202
|
+
if (isNode || isBun) {
|
|
203
|
+
return process.cwd();
|
|
204
|
+
} else if (isDeno) {
|
|
205
|
+
return Deno.cwd();
|
|
206
|
+
}
|
|
207
|
+
return "/";
|
|
208
|
+
}
|
|
209
|
+
function findLastSeparator(path) {
|
|
210
|
+
return Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
211
|
+
}
|
|
212
|
+
function createPathOps(isWin) {
|
|
213
|
+
return {
|
|
214
|
+
sep: getSeparator(isWin),
|
|
215
|
+
delimiter: isWin ? ";" : ":",
|
|
216
|
+
normalize: (path) => normalizePath(path, isWin),
|
|
217
|
+
join: (...paths) => joinPaths(paths, isWin),
|
|
218
|
+
resolve: (...paths) => resolvePaths(paths, isWin),
|
|
219
|
+
isAbsolute: (path) => isWin ? isAbsoluteWin(path) : isAbsolutePosix(path),
|
|
220
|
+
relative: (from, to) => relativePath(from, to, isWin),
|
|
221
|
+
dirname: (path) => getDirname(path, isWin),
|
|
222
|
+
basename: (path, ext) => getBasename(path, ext, isWin),
|
|
223
|
+
extname: (path) => getExtname(path),
|
|
224
|
+
parse: (path) => parsePath(path, isWin),
|
|
225
|
+
format: (pathObject) => formatPath(pathObject, isWin)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function isAbsolutePosix(path) {
|
|
229
|
+
return path.length > 0 && path[0] === "/";
|
|
230
|
+
}
|
|
231
|
+
function isAbsoluteWin(path) {
|
|
232
|
+
const len = path.length;
|
|
233
|
+
if (len === 0) return false;
|
|
234
|
+
const code = path.charCodeAt(0);
|
|
235
|
+
if (code === 47 || code === 92) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (code >= 65 && code <= 90 || code >= 97 && code <= 122) {
|
|
239
|
+
if (len > 2 && path.charCodeAt(1) === 58) {
|
|
240
|
+
const code2 = path.charCodeAt(2);
|
|
241
|
+
if (code2 === 47 || code2 === 92) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
var isWindows = (() => {
|
|
249
|
+
if (isNode) {
|
|
250
|
+
return process.platform === "win32";
|
|
251
|
+
} else if (isDeno) {
|
|
252
|
+
return Deno.build.os === "windows";
|
|
253
|
+
}
|
|
254
|
+
return typeof process !== "undefined" && process.platform === "win32";
|
|
255
|
+
})();
|
|
256
|
+
var posix = createPathOps(false);
|
|
257
|
+
var win32 = createPathOps(true);
|
|
258
|
+
function normalizePath(path, isWin) {
|
|
259
|
+
if (path.length === 0) return ".";
|
|
260
|
+
const separator = getSeparator(isWin);
|
|
261
|
+
const isAbsolute = isWin ? isAbsoluteWin(path) : isAbsolutePosix(path);
|
|
262
|
+
const trailingSeparator = path[path.length - 1] === separator || isWin && path[path.length - 1] === "/";
|
|
263
|
+
let normalized = path.replace(isWin ? /[\/\\]+/g : /\/+/g, separator);
|
|
264
|
+
const parts = normalized.split(separator);
|
|
265
|
+
const result = [];
|
|
266
|
+
for (let i = 0; i < parts.length; i++) {
|
|
267
|
+
const part = parts[i];
|
|
268
|
+
if (part === "" || part === ".") {
|
|
269
|
+
if (i === 0 && isAbsolute) result.push("");
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (part === "..") {
|
|
273
|
+
if (result.length > 0 && result[result.length - 1] !== "..") {
|
|
274
|
+
if (!(result.length === 1 && result[0] === "")) {
|
|
275
|
+
result.pop();
|
|
276
|
+
}
|
|
277
|
+
} else if (!isAbsolute) {
|
|
278
|
+
result.push("..");
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
result.push(part);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let final = result.join(separator);
|
|
285
|
+
if (final.length === 0) {
|
|
286
|
+
return isAbsolute ? separator : ".";
|
|
287
|
+
}
|
|
288
|
+
if (trailingSeparator && final[final.length - 1] !== separator) {
|
|
289
|
+
final += separator;
|
|
290
|
+
}
|
|
291
|
+
return final;
|
|
292
|
+
}
|
|
293
|
+
function joinPaths(paths, isWin) {
|
|
294
|
+
if (paths.length === 0) return ".";
|
|
295
|
+
const separator = getSeparator(isWin);
|
|
296
|
+
let joined = "";
|
|
297
|
+
for (let i = 0; i < paths.length; i++) {
|
|
298
|
+
const path = paths[i];
|
|
299
|
+
if (path && path.length > 0) {
|
|
300
|
+
if (joined.length === 0) {
|
|
301
|
+
joined = path;
|
|
302
|
+
} else {
|
|
303
|
+
joined += separator + path;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (joined.length === 0) return ".";
|
|
308
|
+
return normalizePath(joined, isWin);
|
|
309
|
+
}
|
|
310
|
+
function resolvePaths(paths, isWin) {
|
|
311
|
+
const separator = getSeparator(isWin);
|
|
312
|
+
let resolved = "";
|
|
313
|
+
let isAbsolute = false;
|
|
314
|
+
for (let i = paths.length - 1; i >= 0 && !isAbsolute; i--) {
|
|
315
|
+
const path = paths[i];
|
|
316
|
+
if (path && path.length > 0) {
|
|
317
|
+
resolved = path + (resolved.length > 0 ? separator + resolved : "");
|
|
318
|
+
isAbsolute = isWin ? isAbsoluteWin(resolved) : isAbsolutePosix(resolved);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (!isAbsolute) {
|
|
322
|
+
const cwd = getCwd();
|
|
323
|
+
resolved = cwd + (resolved.length > 0 ? separator + resolved : "");
|
|
324
|
+
}
|
|
325
|
+
return normalizePath(resolved, isWin);
|
|
326
|
+
}
|
|
327
|
+
function relativePath(from, to, isWin) {
|
|
328
|
+
from = resolvePaths([from], isWin);
|
|
329
|
+
to = resolvePaths([to], isWin);
|
|
330
|
+
if (from === to) return "";
|
|
331
|
+
const separator = getSeparator(isWin);
|
|
332
|
+
const fromParts = from.split(separator).filter((p) => p.length > 0);
|
|
333
|
+
const toParts = to.split(separator).filter((p) => p.length > 0);
|
|
334
|
+
let commonLength = 0;
|
|
335
|
+
const minLength = Math.min(fromParts.length, toParts.length);
|
|
336
|
+
for (let i = 0; i < minLength; i++) {
|
|
337
|
+
if (fromParts[i] === toParts[i]) {
|
|
338
|
+
commonLength++;
|
|
339
|
+
} else {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const upCount = fromParts.length - commonLength;
|
|
344
|
+
const result = [];
|
|
345
|
+
for (let i = 0; i < upCount; i++) {
|
|
346
|
+
result.push("..");
|
|
347
|
+
}
|
|
348
|
+
for (let i = commonLength; i < toParts.length; i++) {
|
|
349
|
+
result.push(toParts[i]);
|
|
350
|
+
}
|
|
351
|
+
return result.join(separator) || ".";
|
|
352
|
+
}
|
|
353
|
+
function getDirname(path, isWin) {
|
|
354
|
+
if (path.length === 0) return ".";
|
|
355
|
+
const separator = getSeparator(isWin);
|
|
356
|
+
const normalized = normalizePath(path, isWin);
|
|
357
|
+
const lastSepIndex = normalized.lastIndexOf(separator);
|
|
358
|
+
if (lastSepIndex === -1) return ".";
|
|
359
|
+
if (lastSepIndex === 0) return separator;
|
|
360
|
+
return normalized.slice(0, lastSepIndex);
|
|
361
|
+
}
|
|
362
|
+
function getBasename(path, ext, isWin) {
|
|
363
|
+
if (path.length === 0) return "";
|
|
364
|
+
const lastSepIndex = isWin ? findLastSeparator(path) : path.lastIndexOf("/");
|
|
365
|
+
let base = lastSepIndex === -1 ? path : path.slice(lastSepIndex + 1);
|
|
366
|
+
if (ext && base.endsWith(ext)) {
|
|
367
|
+
base = base.slice(0, base.length - ext.length);
|
|
368
|
+
}
|
|
369
|
+
return base;
|
|
370
|
+
}
|
|
371
|
+
function getExtname(path) {
|
|
372
|
+
const lastDotIndex = path.lastIndexOf(".");
|
|
373
|
+
const lastSepIndex = findLastSeparator(path);
|
|
374
|
+
if (lastDotIndex === -1 || lastDotIndex < lastSepIndex || lastDotIndex === path.length - 1) {
|
|
375
|
+
return "";
|
|
376
|
+
}
|
|
377
|
+
return path.slice(lastDotIndex);
|
|
378
|
+
}
|
|
379
|
+
function parsePath(path, isWin) {
|
|
380
|
+
let root = "";
|
|
381
|
+
if (isWin) {
|
|
382
|
+
if (path.length >= 2 && path[1] === ":") {
|
|
383
|
+
root = path.slice(0, 2);
|
|
384
|
+
if (path.length > 2 && (path[2] === "\\" || path[2] === "/")) {
|
|
385
|
+
root += "\\";
|
|
386
|
+
}
|
|
387
|
+
} else if (path[0] === "\\" || path[0] === "/") {
|
|
388
|
+
root = "\\";
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
if (path[0] === "/") {
|
|
392
|
+
root = "/";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const dir = getDirname(path, isWin);
|
|
396
|
+
const base = getBasename(path, void 0, isWin);
|
|
397
|
+
const ext = getExtname(path);
|
|
398
|
+
const name = ext ? base.slice(0, base.length - ext.length) : base;
|
|
399
|
+
return { root, dir, base, ext, name };
|
|
400
|
+
}
|
|
401
|
+
function formatPath(pathObject, isWin) {
|
|
402
|
+
const separator = getSeparator(isWin);
|
|
403
|
+
const dir = pathObject.dir || pathObject.root || "";
|
|
404
|
+
const base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
|
|
405
|
+
if (!dir) return base;
|
|
406
|
+
if (dir === pathObject.root) return dir + base;
|
|
407
|
+
return dir + separator + base;
|
|
408
|
+
}
|
|
409
|
+
function join(...paths) {
|
|
410
|
+
return joinPaths(paths, isWindows);
|
|
411
|
+
}
|
|
412
|
+
function relative(from, to) {
|
|
413
|
+
return relativePath(from, to, isWindows);
|
|
414
|
+
}
|
|
415
|
+
function dirname(path) {
|
|
416
|
+
return getDirname(path, isWindows);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/coverage.ts
|
|
420
|
+
var executedLinesMap = /* @__PURE__ */ new Map();
|
|
421
|
+
var totalLinesMap = /* @__PURE__ */ new Map();
|
|
422
|
+
function getExecutableLines(filePath) {
|
|
423
|
+
const executableLines = /* @__PURE__ */ new Set();
|
|
424
|
+
try {
|
|
425
|
+
const sourceCode = readFileSync(filePath, "utf-8").toString();
|
|
426
|
+
const lines = sourceCode.split("\n");
|
|
427
|
+
for (let i = 0; i < lines.length; i++) {
|
|
428
|
+
const line = lines[i];
|
|
429
|
+
const trimmed = line.trim();
|
|
430
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*") || trimmed.startsWith("*/") || trimmed.startsWith("import ") || trimmed.startsWith("export ") && !trimmed.includes("function") && !trimmed.includes("class") && !trimmed.includes("const") && !trimmed.includes("let") && !trimmed.includes("var") || trimmed.startsWith("interface ") || trimmed.startsWith("type ") || trimmed.startsWith("enum ") || trimmed.match(/^class\s+\w+.*{?\s*$/) || trimmed === "{" || trimmed === "}" || trimmed === "();") {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
executableLines.add(i + 1);
|
|
434
|
+
}
|
|
435
|
+
} catch (e) {
|
|
436
|
+
}
|
|
437
|
+
return executableLines;
|
|
438
|
+
}
|
|
439
|
+
function markFileAsCovered(_filePath) {
|
|
440
|
+
}
|
|
441
|
+
function markLineExecuted(filePath, lineNumber) {
|
|
442
|
+
if (!executedLinesMap.has(filePath)) {
|
|
443
|
+
executedLinesMap.set(filePath, /* @__PURE__ */ new Set());
|
|
444
|
+
}
|
|
445
|
+
executedLinesMap.get(filePath).add(lineNumber);
|
|
446
|
+
}
|
|
447
|
+
function getExecutedLines(filePath) {
|
|
448
|
+
return executedLinesMap.get(filePath) || /* @__PURE__ */ new Set();
|
|
449
|
+
}
|
|
450
|
+
function calculateUncoveredLines(filePath) {
|
|
451
|
+
const executableLines = getExecutableLines(filePath);
|
|
452
|
+
const executedLines = getExecutedLines(filePath);
|
|
453
|
+
const uncovered = [];
|
|
454
|
+
for (const line of executableLines) {
|
|
455
|
+
if (!executedLines.has(line)) {
|
|
456
|
+
uncovered.push(line);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return uncovered.sort((a, b) => a - b);
|
|
460
|
+
}
|
|
461
|
+
function resetCoverageTracking() {
|
|
462
|
+
executedLinesMap.clear();
|
|
463
|
+
totalLinesMap.clear();
|
|
464
|
+
}
|
|
465
|
+
function initializeCoverageTracking() {
|
|
466
|
+
resetCoverageTracking();
|
|
467
|
+
}
|
|
468
|
+
function globToRegex(pattern) {
|
|
469
|
+
let regexStr = "^";
|
|
470
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
471
|
+
const char = pattern[i];
|
|
472
|
+
switch (char) {
|
|
473
|
+
case ".":
|
|
474
|
+
regexStr += "\\.";
|
|
475
|
+
break;
|
|
476
|
+
case "*":
|
|
477
|
+
if (i + 1 < pattern.length && pattern[i + 1] === "*") {
|
|
478
|
+
regexStr += "(?:[^/]*(?:/|$))*";
|
|
479
|
+
i++;
|
|
480
|
+
} else {
|
|
481
|
+
regexStr += "[^/]*";
|
|
482
|
+
}
|
|
483
|
+
break;
|
|
484
|
+
case "?":
|
|
485
|
+
regexStr += "[^/]";
|
|
486
|
+
break;
|
|
487
|
+
case "/":
|
|
488
|
+
regexStr += "/";
|
|
489
|
+
break;
|
|
490
|
+
// Escape special regex characters
|
|
491
|
+
case "^":
|
|
492
|
+
case "$":
|
|
493
|
+
case "+":
|
|
494
|
+
case "(":
|
|
495
|
+
case ")":
|
|
496
|
+
case "[":
|
|
497
|
+
case "]":
|
|
498
|
+
case "{":
|
|
499
|
+
case "}":
|
|
500
|
+
case "|":
|
|
501
|
+
case "\\":
|
|
502
|
+
regexStr += "\\" + char;
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
regexStr += char;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
regexStr += "$";
|
|
510
|
+
return new RegExp(regexStr);
|
|
511
|
+
}
|
|
512
|
+
function matchesInclude(filePath, include) {
|
|
513
|
+
if (include.length === 0) return true;
|
|
514
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
515
|
+
for (const pattern of include) {
|
|
516
|
+
const regex = globToRegex(pattern);
|
|
517
|
+
if (regex.test(normalizedPath)) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
function matchesExclude(filePath, exclude) {
|
|
524
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
525
|
+
for (const pattern of exclude) {
|
|
526
|
+
const regex = globToRegex(pattern);
|
|
527
|
+
if (regex.test(normalizedPath)) {
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
function findAllTypeScriptFiles(dir, include, exclude) {
|
|
534
|
+
const files = [];
|
|
535
|
+
if (!existsSync(dir)) {
|
|
536
|
+
return files;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
540
|
+
for (const entry of entries) {
|
|
541
|
+
if (typeof entry === "string") continue;
|
|
542
|
+
const fullPath = join(dir, entry.name);
|
|
543
|
+
if (entry.isDirectory()) {
|
|
544
|
+
if (matchesExclude(fullPath, exclude)) continue;
|
|
545
|
+
files.push(...findAllTypeScriptFiles(fullPath, include, exclude));
|
|
546
|
+
} else if (entry.isFile() && fullPath.endsWith(".ts")) {
|
|
547
|
+
if (matchesInclude(fullPath, include) && !matchesExclude(fullPath, exclude)) {
|
|
548
|
+
files.push(fullPath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} catch (e) {
|
|
553
|
+
}
|
|
554
|
+
return files;
|
|
555
|
+
}
|
|
556
|
+
function analyzeSourceFile(filePath) {
|
|
557
|
+
try {
|
|
558
|
+
const sourceCode = readFileSync(filePath, "utf-8").toString();
|
|
559
|
+
const lines = sourceCode.split("\n");
|
|
560
|
+
let statements = 0;
|
|
561
|
+
let branches = 0;
|
|
562
|
+
let functions = 0;
|
|
563
|
+
let executableLines = 0;
|
|
564
|
+
const branchKeywords = ["if", "else if", "for", "while", "switch", "case", "catch", "?", "&&", "||"];
|
|
565
|
+
const functionPatterns = [/function\s+\w+/, /(\w+)\s*\([^)]*\)\s*{/, /\(\s*\w+\s*(?:,\s*\w+\s*)*\)\s*=>/];
|
|
566
|
+
for (const line of lines) {
|
|
567
|
+
const trimmed = line.trim();
|
|
568
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*") || trimmed.startsWith("import ") || trimmed.startsWith("export ") || trimmed.startsWith("interface ") || trimmed.startsWith("type ") || trimmed.startsWith("enum ") || trimmed.match(/^class\s+\w+/)) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
for (const keyword of branchKeywords) {
|
|
572
|
+
if (trimmed.includes(keyword)) {
|
|
573
|
+
branches++;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (const pattern of functionPatterns) {
|
|
578
|
+
if (pattern.test(trimmed)) {
|
|
579
|
+
functions++;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const codeOnly = trimmed.replace(/\{|\}|\(|\)|;$/g, "").replace(/^import\s+.*$/, "").replace(/^export\s+.*$/, "").replace(/^interface\s+.*$/, "").replace(/^type\s+.*$/, "").replace(/^enum\s+.*$/, "").replace(/^class\s+\w+.*$/, "").trim();
|
|
584
|
+
if (codeOnly && codeOnly.length > 0) {
|
|
585
|
+
statements++;
|
|
586
|
+
executableLines++;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return { statements, branches, functions, lines: executableLines };
|
|
590
|
+
} catch (e) {
|
|
591
|
+
return { statements: 0, branches: 0, functions: 0, lines: 0 };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async function processCoverage(options) {
|
|
595
|
+
const {
|
|
596
|
+
include = ["**/*.ts"],
|
|
597
|
+
exclude = ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**", "**/dist/**", "**/coverage/**"],
|
|
598
|
+
coveredFiles
|
|
599
|
+
} = options;
|
|
600
|
+
const coverageMap = /* @__PURE__ */ new Map();
|
|
601
|
+
const allTsFiles = findAllTypeScriptFiles(process.cwd(), include, exclude);
|
|
602
|
+
for (const tsFile of allTsFiles) {
|
|
603
|
+
const isCovered = coveredFiles?.has(tsFile) || false;
|
|
604
|
+
const analysis = analyzeSourceFile(tsFile);
|
|
605
|
+
const executableLines = getExecutableLines(tsFile);
|
|
606
|
+
const executedLines = isCovered ? executableLines : /* @__PURE__ */ new Set();
|
|
607
|
+
const uncoveredLinesArray = [];
|
|
608
|
+
for (const line of executableLines) {
|
|
609
|
+
if (!executedLines.has(line)) {
|
|
610
|
+
uncoveredLinesArray.push(line);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const uncoveredLines = uncoveredLinesArray.length > 0 ? uncoveredLinesArray.sort((a, b) => a - b) : void 0;
|
|
614
|
+
const coveredLinesCount = executedLines.size;
|
|
615
|
+
coverageMap.set(tsFile, {
|
|
616
|
+
path: tsFile,
|
|
617
|
+
statements: analysis.statements,
|
|
618
|
+
coveredStatements: isCovered ? analysis.statements : 0,
|
|
619
|
+
branches: analysis.branches,
|
|
620
|
+
coveredBranches: isCovered ? analysis.branches : 0,
|
|
621
|
+
functions: analysis.functions,
|
|
622
|
+
coveredFunctions: isCovered ? analysis.functions : 0,
|
|
623
|
+
lines: executableLines.size,
|
|
624
|
+
coveredLines: coveredLinesCount,
|
|
625
|
+
uncoveredLines
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (coveredFiles) {
|
|
629
|
+
for (const coveredFile of coveredFiles) {
|
|
630
|
+
if (coverageMap.has(coveredFile)) continue;
|
|
631
|
+
const relativePath2 = relative(process.cwd(), coveredFile);
|
|
632
|
+
const isOutsideProject = relativePath2.startsWith("..");
|
|
633
|
+
if (!coveredFile.includes("node_modules") && !coveredFile.includes("dist") && !isOutsideProject) {
|
|
634
|
+
const analysis = analyzeSourceFile(coveredFile);
|
|
635
|
+
const executableLines = getExecutableLines(coveredFile);
|
|
636
|
+
const executedLines = executableLines;
|
|
637
|
+
const uncoveredLinesArray = [];
|
|
638
|
+
for (const line of executableLines) {
|
|
639
|
+
if (!executedLines.has(line)) {
|
|
640
|
+
uncoveredLinesArray.push(line);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const uncoveredLines = uncoveredLinesArray.length > 0 ? uncoveredLinesArray.sort((a, b) => a - b) : void 0;
|
|
644
|
+
const coveredLinesCount = executedLines.size;
|
|
645
|
+
coverageMap.set(coveredFile, {
|
|
646
|
+
path: coveredFile,
|
|
647
|
+
statements: analysis.statements,
|
|
648
|
+
coveredStatements: analysis.statements,
|
|
649
|
+
branches: analysis.branches,
|
|
650
|
+
coveredBranches: analysis.branches,
|
|
651
|
+
functions: analysis.functions,
|
|
652
|
+
coveredFunctions: analysis.functions,
|
|
653
|
+
lines: executableLines.size,
|
|
654
|
+
coveredLines: coveredLinesCount,
|
|
655
|
+
uncoveredLines
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return coverageMap;
|
|
661
|
+
}
|
|
662
|
+
var colors = {
|
|
663
|
+
reset: "\x1B[0m",
|
|
664
|
+
bold: "\x1B[1m",
|
|
665
|
+
dim: "\x1B[2m",
|
|
666
|
+
red: "\x1B[31m",
|
|
667
|
+
green: "\x1B[32m",
|
|
668
|
+
yellow: "\x1B[33m",
|
|
669
|
+
cyan: "\x1B[36m"
|
|
670
|
+
};
|
|
671
|
+
function getColorForPercentage(pct) {
|
|
672
|
+
if (pct >= 80) return colors.green;
|
|
673
|
+
if (pct >= 50) return colors.yellow;
|
|
674
|
+
return colors.red;
|
|
675
|
+
}
|
|
676
|
+
function calculateFileCoverage(file) {
|
|
677
|
+
const stmtPct = file.statements > 0 ? file.coveredStatements / file.statements * 100 : 0;
|
|
678
|
+
const branchPct = file.branches > 0 ? file.coveredBranches / file.branches * 100 : 0;
|
|
679
|
+
const funcPct = file.functions > 0 ? file.coveredFunctions / file.functions * 100 : 0;
|
|
680
|
+
const linePct = file.lines > 0 ? file.coveredLines / file.lines * 100 : 0;
|
|
681
|
+
return {
|
|
682
|
+
statements: { total: file.statements, covered: file.coveredStatements, percentage: stmtPct },
|
|
683
|
+
branches: { total: file.branches, covered: file.coveredBranches, percentage: branchPct },
|
|
684
|
+
functions: { total: file.functions, covered: file.coveredFunctions, percentage: funcPct },
|
|
685
|
+
lines: { total: file.lines, covered: file.coveredLines, percentage: linePct }
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function stripAnsi(str) {
|
|
689
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
690
|
+
}
|
|
691
|
+
function getVisibleWidth(str) {
|
|
692
|
+
return stripAnsi(str).length;
|
|
693
|
+
}
|
|
694
|
+
function formatMetricFixedWidth(covered, total, percentage, includeSeparator = false) {
|
|
695
|
+
const color = getColorForPercentage(percentage);
|
|
696
|
+
const pctStr = percentage.toFixed(2);
|
|
697
|
+
const pct = color + pctStr + "%" + colors.reset;
|
|
698
|
+
const coveredPadded = covered.toString().padStart(4);
|
|
699
|
+
const totalPadded = total.toString().padStart(4);
|
|
700
|
+
const count = `${colors.dim}${coveredPadded}${colors.reset}/${totalPadded}`;
|
|
701
|
+
const metric = `${pct} (${count})`;
|
|
702
|
+
const visibleWidth = getVisibleWidth(metric);
|
|
703
|
+
const padding = " ".repeat(Math.max(0, 19 - visibleWidth));
|
|
704
|
+
const separator = includeSeparator ? `${colors.dim}\u2502${colors.reset}` : " ";
|
|
705
|
+
return metric + padding + separator;
|
|
706
|
+
}
|
|
707
|
+
function formatUncoveredLines(uncoveredLines) {
|
|
708
|
+
if (!uncoveredLines || uncoveredLines.length === 0) {
|
|
709
|
+
return "";
|
|
710
|
+
}
|
|
711
|
+
const ranges = [];
|
|
712
|
+
let start = uncoveredLines[0];
|
|
713
|
+
let end = uncoveredLines[0];
|
|
714
|
+
for (let i = 1; i < uncoveredLines.length; i++) {
|
|
715
|
+
if (uncoveredLines[i] === end + 1) {
|
|
716
|
+
end = uncoveredLines[i];
|
|
717
|
+
} else {
|
|
718
|
+
if (start === end) {
|
|
719
|
+
ranges.push(start.toString());
|
|
720
|
+
} else {
|
|
721
|
+
ranges.push(`${start}-${end}`);
|
|
722
|
+
}
|
|
723
|
+
start = uncoveredLines[i];
|
|
724
|
+
end = uncoveredLines[i];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (start === end) {
|
|
728
|
+
ranges.push(start.toString());
|
|
729
|
+
} else {
|
|
730
|
+
ranges.push(`${start}-${end}`);
|
|
731
|
+
}
|
|
732
|
+
return ranges.join(",");
|
|
733
|
+
}
|
|
734
|
+
function generateTextReport(coverageMap, testResults) {
|
|
735
|
+
let output = "\n";
|
|
736
|
+
void testResults;
|
|
737
|
+
let totalStatements = 0, coveredStatements = 0;
|
|
738
|
+
let totalBranches = 0, coveredBranches = 0;
|
|
739
|
+
let totalFunctions = 0, coveredFunctions = 0;
|
|
740
|
+
let totalLines = 0, coveredLines = 0;
|
|
741
|
+
for (const coverage of coverageMap.values()) {
|
|
742
|
+
totalStatements += coverage.statements;
|
|
743
|
+
coveredStatements += coverage.coveredStatements;
|
|
744
|
+
totalBranches += coverage.branches;
|
|
745
|
+
coveredBranches += coverage.coveredBranches;
|
|
746
|
+
totalFunctions += coverage.functions;
|
|
747
|
+
coveredFunctions += coverage.coveredFunctions;
|
|
748
|
+
totalLines += coverage.lines;
|
|
749
|
+
coveredLines += coverage.coveredLines;
|
|
750
|
+
}
|
|
751
|
+
const pctStmts = totalStatements > 0 ? coveredStatements / totalStatements * 100 : 0;
|
|
752
|
+
const pctBranch = totalBranches > 0 ? coveredBranches / totalBranches * 100 : 0;
|
|
753
|
+
const pctFunc = totalFunctions > 0 ? coveredFunctions / totalFunctions * 100 : 0;
|
|
754
|
+
const pctLines = totalLines > 0 ? coveredLines / totalLines * 100 : 0;
|
|
755
|
+
output += `${colors.bold}% Coverage report from v8\x1B[0m
|
|
756
|
+
`;
|
|
757
|
+
output += `
|
|
758
|
+
`;
|
|
759
|
+
output += `${colors.dim}${colors.bold}All files\x1B[0m`;
|
|
760
|
+
const maxFileNameLength = Math.max(...Array.from(coverageMap.keys()).map((f) => relative(process.cwd(), f).length));
|
|
761
|
+
const namePadding = Math.max(45, maxFileNameLength + 2);
|
|
762
|
+
output += " ".repeat(namePadding - 9);
|
|
763
|
+
const stmtsMetric = formatMetricFixedWidth(coveredStatements, totalStatements, pctStmts, true);
|
|
764
|
+
const branchMetric = formatMetricFixedWidth(coveredBranches, totalBranches, pctBranch, true);
|
|
765
|
+
const funcsMetric = formatMetricFixedWidth(coveredFunctions, totalFunctions, pctFunc, true);
|
|
766
|
+
const linesMetric = formatMetricFixedWidth(coveredLines, totalLines, pctLines, true);
|
|
767
|
+
output += `${stmtsMetric}${branchMetric}${funcsMetric}${linesMetric}
|
|
768
|
+
`;
|
|
769
|
+
output += `${colors.dim}`;
|
|
770
|
+
output += " ".repeat(namePadding);
|
|
771
|
+
output += " ".repeat(5) + "Statements";
|
|
772
|
+
output += " ".repeat(12) + "Branch";
|
|
773
|
+
output += " ".repeat(12) + "Functions";
|
|
774
|
+
output += " ".repeat(13) + "Lines";
|
|
775
|
+
output += " ".repeat(12) + "Uncovered";
|
|
776
|
+
output += `${colors.reset}
|
|
777
|
+
`;
|
|
778
|
+
output += `${colors.dim}`;
|
|
779
|
+
output += "\u2500".repeat(namePadding);
|
|
780
|
+
output += "\u2500".repeat(19);
|
|
781
|
+
output += "\u253C";
|
|
782
|
+
output += "\u2500".repeat(19);
|
|
783
|
+
output += "\u253C";
|
|
784
|
+
output += "\u2500".repeat(19);
|
|
785
|
+
output += "\u253C";
|
|
786
|
+
output += "\u2500".repeat(19);
|
|
787
|
+
output += "\u253C";
|
|
788
|
+
output += "\u2500".repeat(19);
|
|
789
|
+
output += `${colors.reset}
|
|
790
|
+
`;
|
|
791
|
+
const groupedFiles = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const [filePath, coverage] of coverageMap.entries()) {
|
|
793
|
+
const dir = dirname(filePath);
|
|
794
|
+
if (!groupedFiles.has(dir)) {
|
|
795
|
+
groupedFiles.set(dir, []);
|
|
796
|
+
}
|
|
797
|
+
groupedFiles.get(dir).push({ path: filePath, coverage });
|
|
798
|
+
}
|
|
799
|
+
const cwd = process.cwd();
|
|
800
|
+
const toRelative = (path) => relative(cwd, path).replace(/\\/g, "/");
|
|
801
|
+
for (const [dir, files] of groupedFiles.entries()) {
|
|
802
|
+
const relDir = toRelative(dir);
|
|
803
|
+
if (relDir !== ".") {
|
|
804
|
+
output += `
|
|
805
|
+
${colors.cyan}${relDir}/${colors.reset}
|
|
806
|
+
`;
|
|
807
|
+
}
|
|
808
|
+
for (const { path, coverage } of files) {
|
|
809
|
+
const stats = calculateFileCoverage(coverage);
|
|
810
|
+
const relPath = toRelative(path);
|
|
811
|
+
let displayName = relPath;
|
|
812
|
+
if (displayName.length > namePadding - 2) {
|
|
813
|
+
displayName = "..." + displayName.slice(-(namePadding - 5));
|
|
814
|
+
}
|
|
815
|
+
output += displayName.padEnd(namePadding);
|
|
816
|
+
output += formatMetricFixedWidth(
|
|
817
|
+
stats.statements.covered,
|
|
818
|
+
stats.statements.total,
|
|
819
|
+
stats.statements.percentage,
|
|
820
|
+
true
|
|
821
|
+
// Include separator
|
|
822
|
+
);
|
|
823
|
+
output += formatMetricFixedWidth(
|
|
824
|
+
stats.branches.covered,
|
|
825
|
+
stats.branches.total,
|
|
826
|
+
stats.branches.percentage,
|
|
827
|
+
true
|
|
828
|
+
// Include separator
|
|
829
|
+
);
|
|
830
|
+
output += formatMetricFixedWidth(
|
|
831
|
+
stats.functions.covered,
|
|
832
|
+
stats.functions.total,
|
|
833
|
+
stats.functions.percentage,
|
|
834
|
+
true
|
|
835
|
+
// Include separator
|
|
836
|
+
);
|
|
837
|
+
output += formatMetricFixedWidth(
|
|
838
|
+
stats.lines.covered,
|
|
839
|
+
stats.lines.total,
|
|
840
|
+
stats.lines.percentage,
|
|
841
|
+
true
|
|
842
|
+
// Include separator
|
|
843
|
+
);
|
|
844
|
+
const uncoveredStr = formatUncoveredLines(coverage.uncoveredLines);
|
|
845
|
+
output += `${colors.red}${uncoveredStr}${colors.reset}`;
|
|
846
|
+
output += "\n";
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
output += `
|
|
850
|
+
`;
|
|
851
|
+
output += `${colors.dim}${colors.bold}Test Files\x1B[0m ${coverageMap.size} passed (100%)
|
|
852
|
+
`;
|
|
853
|
+
output += `${colors.dim}${colors.bold}Tests\x1B[0m ${coverageMap.size} passed (100%)
|
|
854
|
+
`;
|
|
855
|
+
output += `
|
|
856
|
+
`;
|
|
857
|
+
output += `${colors.dim}${colors.bold}Statements\x1B[0m ${colors.green}${coveredStatements}${colors.reset} ${colors.dim}/${colors.reset} ${totalStatements}
|
|
858
|
+
`;
|
|
859
|
+
output += `${colors.dim}${colors.bold}Branches\x1B[0m ${colors.green}${coveredBranches}${colors.reset} ${colors.dim}/${colors.reset} ${totalBranches}
|
|
860
|
+
`;
|
|
861
|
+
output += `${colors.dim}${colors.bold}Functions\x1B[0m ${colors.green}${coveredFunctions}${colors.reset} ${colors.dim}/${colors.reset} ${totalFunctions}
|
|
862
|
+
`;
|
|
863
|
+
output += `${colors.dim}${colors.bold}Lines\x1B[0m ${colors.green}${coveredLines}${colors.reset} ${colors.dim}/${colors.reset} ${totalLines}
|
|
864
|
+
`;
|
|
865
|
+
return output;
|
|
866
|
+
}
|
|
867
|
+
function generateHtmlReport(coverageMap, reportsDir) {
|
|
868
|
+
if (!existsSync(reportsDir)) {
|
|
869
|
+
mkdirSync(reportsDir, { recursive: true });
|
|
870
|
+
}
|
|
871
|
+
let totalStatements = 0, coveredStatements = 0;
|
|
872
|
+
let totalBranches = 0, coveredBranches = 0;
|
|
873
|
+
let totalFunctions = 0, coveredFunctions = 0;
|
|
874
|
+
let totalLines = 0, coveredLines = 0;
|
|
875
|
+
for (const coverage of coverageMap.values()) {
|
|
876
|
+
totalStatements += coverage.statements;
|
|
877
|
+
coveredStatements += coverage.coveredStatements;
|
|
878
|
+
totalBranches += coverage.branches;
|
|
879
|
+
coveredBranches += coverage.coveredBranches;
|
|
880
|
+
totalFunctions += coverage.functions;
|
|
881
|
+
coveredFunctions += coverage.coveredFunctions;
|
|
882
|
+
totalLines += coverage.lines;
|
|
883
|
+
coveredLines += coverage.coveredLines;
|
|
884
|
+
}
|
|
885
|
+
const pctStmts = totalStatements > 0 ? coveredStatements / totalStatements * 100 : 0;
|
|
886
|
+
const pctBranch = totalBranches > 0 ? coveredBranches / totalBranches * 100 : 0;
|
|
887
|
+
const pctFunc = totalFunctions > 0 ? coveredFunctions / totalFunctions * 100 : 0;
|
|
888
|
+
const pctLines = totalLines > 0 ? coveredLines / totalLines * 100 : 0;
|
|
889
|
+
const overallPct = (pctStmts + pctBranch + pctFunc + pctLines) / 4;
|
|
890
|
+
const totalFiles = coverageMap.size;
|
|
891
|
+
const coveredFiles = Array.from(coverageMap.values()).filter((c) => c.coveredStatements > 0).length;
|
|
892
|
+
const cwd = process.cwd();
|
|
893
|
+
const toRelative = (path) => relative(cwd, path).replace(/\\/g, "/");
|
|
894
|
+
const indexHtml = `<!DOCTYPE html>
|
|
895
|
+
<html>
|
|
896
|
+
<head>
|
|
897
|
+
<meta charset="utf-8">
|
|
898
|
+
<title>Coverage Report</title>
|
|
899
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%236366f1'/%3E%3Cstop offset='100%25' stop-color='%238b5cf6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='100' height='100' rx='20' fill='url(%23grad)'/%3E%3Crect x='28' y='25' width='44' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='46' width='32' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='67' width='44' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='25' width='8' height='50' rx='4' fill='white'/%3E%3Ccircle cx='72' cy='50' r='6' fill='white' opacity='0.5'/%3E%3C/svg%3E">
|
|
900
|
+
<style>
|
|
901
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
902
|
+
body {
|
|
903
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
904
|
+
background: #0d1117;
|
|
905
|
+
color: #c9d1d9;
|
|
906
|
+
padding: 20px;
|
|
907
|
+
}
|
|
908
|
+
.container { max-width: 1400px; margin: 0 auto; }
|
|
909
|
+
h1 {
|
|
910
|
+
font-size: 24px;
|
|
911
|
+
font-weight: 600;
|
|
912
|
+
margin-bottom: 20px;
|
|
913
|
+
color: #58a6ff;
|
|
914
|
+
}
|
|
915
|
+
.overall-bar {
|
|
916
|
+
background: #161b22;
|
|
917
|
+
border: 1px solid #30363d;
|
|
918
|
+
border-radius: 6px;
|
|
919
|
+
padding: 15px 20px;
|
|
920
|
+
margin-bottom: 20px;
|
|
921
|
+
}
|
|
922
|
+
.overall-bar-inner {
|
|
923
|
+
display: flex;
|
|
924
|
+
align-items: center;
|
|
925
|
+
gap: 15px;
|
|
926
|
+
}
|
|
927
|
+
.overall-bar-label {
|
|
928
|
+
font-size: 14px;
|
|
929
|
+
font-weight: 600;
|
|
930
|
+
color: #8b949e;
|
|
931
|
+
min-width: 140px;
|
|
932
|
+
}
|
|
933
|
+
.overall-bar-visual {
|
|
934
|
+
flex: 1;
|
|
935
|
+
height: 24px;
|
|
936
|
+
background: #21262d;
|
|
937
|
+
border-radius: 4px;
|
|
938
|
+
overflow: hidden;
|
|
939
|
+
position: relative;
|
|
940
|
+
}
|
|
941
|
+
.overall-bar-fill {
|
|
942
|
+
height: 100%;
|
|
943
|
+
background: ${overallPct >= 80 ? "#3fb950" : overallPct >= 50 ? "#d29922" : "#f85149"};
|
|
944
|
+
display: flex;
|
|
945
|
+
align-items: center;
|
|
946
|
+
justify-content: center;
|
|
947
|
+
transition: width 0.3s ease;
|
|
948
|
+
}
|
|
949
|
+
.overall-bar-text {
|
|
950
|
+
position: absolute;
|
|
951
|
+
right: 10px;
|
|
952
|
+
top: 50%;
|
|
953
|
+
transform: translateY(-50%);
|
|
954
|
+
font-size: 12px;
|
|
955
|
+
font-weight: 600;
|
|
956
|
+
color: #ffffff;
|
|
957
|
+
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
|
|
958
|
+
}
|
|
959
|
+
.files-info {
|
|
960
|
+
font-size: 13px;
|
|
961
|
+
color: #8b949e;
|
|
962
|
+
margin-top: 8px;
|
|
963
|
+
}
|
|
964
|
+
.files-info span { color: #58a6ff; font-weight: 600; }
|
|
965
|
+
.summary {
|
|
966
|
+
background: #161b22;
|
|
967
|
+
border: 1px solid #30363d;
|
|
968
|
+
border-radius: 6px;
|
|
969
|
+
padding: 20px;
|
|
970
|
+
margin-bottom: 20px;
|
|
971
|
+
}
|
|
972
|
+
.summary-title {
|
|
973
|
+
font-size: 16px;
|
|
974
|
+
font-weight: 600;
|
|
975
|
+
margin-bottom: 15px;
|
|
976
|
+
color: #c9d1d9;
|
|
977
|
+
}
|
|
978
|
+
.metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
|
|
979
|
+
.metric {
|
|
980
|
+
background: #21262d;
|
|
981
|
+
border: 1px solid #30363d;
|
|
982
|
+
border-radius: 6px;
|
|
983
|
+
padding: 15px;
|
|
984
|
+
text-align: center;
|
|
985
|
+
}
|
|
986
|
+
.metric-label { font-size: 12px; color: #8b949e; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
987
|
+
.metric-value { font-size: 24px; font-weight: 700; }
|
|
988
|
+
.metric-value.high { color: #3fb950; }
|
|
989
|
+
.metric-value.medium { color: #d29922; }
|
|
990
|
+
.metric-value.low { color: #f85149; }
|
|
991
|
+
.progress-bar {
|
|
992
|
+
height: 8px;
|
|
993
|
+
background: #21262d;
|
|
994
|
+
border-radius: 4px;
|
|
995
|
+
overflow: hidden;
|
|
996
|
+
margin-top: 8px;
|
|
997
|
+
}
|
|
998
|
+
.progress-fill { height: 100%; transition: width 0.3s ease; }
|
|
999
|
+
.progress-fill.high { background: #3fb950; }
|
|
1000
|
+
.progress-fill.medium { background: #d29922; }
|
|
1001
|
+
.progress-fill.low { background: #f85149; }
|
|
1002
|
+
.metric-count { font-size: 11px; color: #8b949e; margin-top: 5px; }
|
|
1003
|
+
.file-list {
|
|
1004
|
+
background: #161b22;
|
|
1005
|
+
border: 1px solid #30363d;
|
|
1006
|
+
border-radius: 6px;
|
|
1007
|
+
overflow: hidden;
|
|
1008
|
+
}
|
|
1009
|
+
.file-header {
|
|
1010
|
+
display: grid;
|
|
1011
|
+
grid-template-columns: 1fr 80px 80px 80px 80px;
|
|
1012
|
+
padding: 12px 15px;
|
|
1013
|
+
background: #21262d;
|
|
1014
|
+
font-size: 12px;
|
|
1015
|
+
font-weight: 600;
|
|
1016
|
+
color: #8b949e;
|
|
1017
|
+
border-bottom: 1px solid #30363d;
|
|
1018
|
+
}
|
|
1019
|
+
.file-row {
|
|
1020
|
+
display: grid;
|
|
1021
|
+
grid-template-columns: 1fr 80px 80px 80px 80px;
|
|
1022
|
+
padding: 10px 15px;
|
|
1023
|
+
border-bottom: 1px solid #21262d;
|
|
1024
|
+
font-size: 13px;
|
|
1025
|
+
}
|
|
1026
|
+
.file-row:hover { background: #21262d; }
|
|
1027
|
+
.file-row:last-child { border-bottom: none; }
|
|
1028
|
+
.file-name { color: #58a6ff; text-decoration: none; cursor: pointer; }
|
|
1029
|
+
.file-name:hover { text-decoration: underline; }
|
|
1030
|
+
.percentage { font-weight: 600; }
|
|
1031
|
+
.percentage.high { color: #3fb950; }
|
|
1032
|
+
.percentage.medium { color: #d29922; }
|
|
1033
|
+
.percentage.low { color: #f85149; }
|
|
1034
|
+
.metric-detail { font-size: 11px; color: #8b949e; margin-top: 2px; }
|
|
1035
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; margin-left: 8px; }
|
|
1036
|
+
.badge.covered { background: #238636; color: #fff; }
|
|
1037
|
+
.badge.uncovered { background: #da3633; color: #fff; }
|
|
1038
|
+
.coverage-cell { text-align: center; }
|
|
1039
|
+
.coverage-percent { font-weight: 600; }
|
|
1040
|
+
.coverage-percent.high { color: #3fb950; }
|
|
1041
|
+
.coverage-percent.medium { color: #d29922; }
|
|
1042
|
+
.coverage-percent.low { color: #f85149; }
|
|
1043
|
+
.coverage-count { font-size: 11px; color: #8b949e; margin-top: 2px; }
|
|
1044
|
+
.search-container {
|
|
1045
|
+
background: #161b22;
|
|
1046
|
+
border: 1px solid #30363d;
|
|
1047
|
+
border-radius: 6px;
|
|
1048
|
+
padding: 15px;
|
|
1049
|
+
margin-bottom: 20px;
|
|
1050
|
+
}
|
|
1051
|
+
.search-input {
|
|
1052
|
+
width: 100%;
|
|
1053
|
+
padding: 10px 15px;
|
|
1054
|
+
background: #21262d;
|
|
1055
|
+
border: 1px solid #30363d;
|
|
1056
|
+
border-radius: 6px;
|
|
1057
|
+
color: #c9d1d9;
|
|
1058
|
+
font-size: 14px;
|
|
1059
|
+
font-family: inherit;
|
|
1060
|
+
outline: none;
|
|
1061
|
+
transition: border-color 0.2s ease;
|
|
1062
|
+
}
|
|
1063
|
+
.search-input:focus {
|
|
1064
|
+
border-color: #58a6ff;
|
|
1065
|
+
}
|
|
1066
|
+
.search-input::placeholder {
|
|
1067
|
+
color: #8b949e;
|
|
1068
|
+
}
|
|
1069
|
+
.hidden { display: none !important; }
|
|
1070
|
+
.no-results {
|
|
1071
|
+
padding: 20px;
|
|
1072
|
+
text-align: center;
|
|
1073
|
+
color: #8b949e;
|
|
1074
|
+
font-size: 14px;
|
|
1075
|
+
}
|
|
1076
|
+
</style>
|
|
1077
|
+
</head>
|
|
1078
|
+
<body>
|
|
1079
|
+
<div class="container">
|
|
1080
|
+
<h1>Coverage Report</h1>
|
|
1081
|
+
|
|
1082
|
+
<div class="overall-bar">
|
|
1083
|
+
<div class="overall-bar-inner">
|
|
1084
|
+
<div class="overall-bar-label">Overall Coverage</div>
|
|
1085
|
+
<div class="overall-bar-visual">
|
|
1086
|
+
<div class="overall-bar-fill" style="width: ${overallPct}%"></div>
|
|
1087
|
+
<div class="overall-bar-text">${overallPct.toFixed(2)}%</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="files-info"><span>${coveredFiles}</span> of ${totalFiles} files covered</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<div class="summary">
|
|
1094
|
+
<div class="summary-title">Coverage Metrics</div>
|
|
1095
|
+
<div class="metrics">
|
|
1096
|
+
<div class="metric">
|
|
1097
|
+
<div class="metric-label">Statements</div>
|
|
1098
|
+
<div class="metric-value ${pctStmts >= 80 ? "high" : pctStmts >= 50 ? "medium" : "low"}">${pctStmts.toFixed(2)}%</div>
|
|
1099
|
+
<div class="progress-bar">
|
|
1100
|
+
<div class="progress-fill ${pctStmts >= 80 ? "high" : pctStmts >= 50 ? "medium" : "low"}" style="width: ${pctStmts}%"></div>
|
|
1101
|
+
</div>
|
|
1102
|
+
<div class="metric-count">${coveredStatements}/${totalStatements}</div>
|
|
1103
|
+
</div>
|
|
1104
|
+
<div class="metric">
|
|
1105
|
+
<div class="metric-label">Branches</div>
|
|
1106
|
+
<div class="metric-value ${pctBranch >= 80 ? "high" : pctBranch >= 50 ? "medium" : "low"}">${pctBranch.toFixed(2)}%</div>
|
|
1107
|
+
<div class="progress-bar">
|
|
1108
|
+
<div class="progress-fill ${pctBranch >= 80 ? "high" : pctBranch >= 50 ? "medium" : "low"}" style="width: ${pctBranch}%"></div>
|
|
1109
|
+
</div>
|
|
1110
|
+
<div class="metric-count">${coveredBranches}/${totalBranches}</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
<div class="metric">
|
|
1113
|
+
<div class="metric-label">Functions</div>
|
|
1114
|
+
<div class="metric-value ${pctFunc >= 80 ? "high" : pctFunc >= 50 ? "medium" : "low"}">${pctFunc.toFixed(2)}%</div>
|
|
1115
|
+
<div class="progress-bar">
|
|
1116
|
+
<div class="progress-fill ${pctFunc >= 80 ? "high" : pctFunc >= 50 ? "medium" : "low"}" style="width: ${pctFunc}%"></div>
|
|
1117
|
+
</div>
|
|
1118
|
+
<div class="metric-count">${coveredFunctions}/${totalFunctions}</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
<div class="metric">
|
|
1121
|
+
<div class="metric-label">Lines</div>
|
|
1122
|
+
<div class="metric-value ${pctLines >= 80 ? "high" : pctLines >= 50 ? "medium" : "low"}">${pctLines.toFixed(2)}%</div>
|
|
1123
|
+
<div class="progress-bar">
|
|
1124
|
+
<div class="progress-fill ${pctLines >= 80 ? "high" : pctLines >= 50 ? "medium" : "low"}" style="width: ${pctLines}%"></div>
|
|
1125
|
+
</div>
|
|
1126
|
+
<div class="metric-count">${coveredLines}/${totalLines}</div>
|
|
1127
|
+
</div>
|
|
1128
|
+
</div>
|
|
1129
|
+
</div>
|
|
1130
|
+
|
|
1131
|
+
<div class="search-container">
|
|
1132
|
+
<input type="text" id="search-input" class="search-input" placeholder="\u{1F50D} Search files..." autocomplete="off">
|
|
1133
|
+
</div>
|
|
1134
|
+
|
|
1135
|
+
<div class="file-list">
|
|
1136
|
+
<div class="file-header">
|
|
1137
|
+
<div>File</div>
|
|
1138
|
+
<div style="text-align: center">Stmts</div>
|
|
1139
|
+
<div style="text-align: center">Branch</div>
|
|
1140
|
+
<div style="text-align: center">Funcs</div>
|
|
1141
|
+
<div style="text-align: center">Lines</div>
|
|
1142
|
+
</div>
|
|
1143
|
+
<div id="file-rows">
|
|
1144
|
+
${Array.from(coverageMap.entries()).map(([filePath, coverage]) => {
|
|
1145
|
+
const stats = calculateFileCoverage(coverage);
|
|
1146
|
+
const fileName = toRelative(filePath);
|
|
1147
|
+
const safeFileName = fileName.replace(/[\/\\]/g, "_") + ".html";
|
|
1148
|
+
const isCovered = coverage.coveredStatements > 0;
|
|
1149
|
+
return `
|
|
1150
|
+
<div class="file-row" onclick="window.location.href='${safeFileName}'">
|
|
1151
|
+
<div>
|
|
1152
|
+
<span class="file-name">${fileName}</span>
|
|
1153
|
+
${isCovered ? '<span class="badge covered">Covered</span>' : '<span class="badge uncovered">Not Covered</span>'}
|
|
1154
|
+
</div>
|
|
1155
|
+
<div class="coverage-cell">
|
|
1156
|
+
<div class="coverage-percent ${stats.statements.percentage >= 80 ? "high" : stats.statements.percentage >= 50 ? "medium" : "low"}">${stats.statements.percentage.toFixed(2)}%</div>
|
|
1157
|
+
<div class="coverage-count">${coverage.coveredStatements}/${coverage.statements}</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
<div class="coverage-cell">
|
|
1160
|
+
<div class="coverage-percent ${stats.branches.percentage >= 80 ? "high" : stats.branches.percentage >= 50 ? "medium" : "low"}">${stats.branches.percentage.toFixed(2)}%</div>
|
|
1161
|
+
<div class="coverage-count">${coverage.coveredBranches}/${coverage.branches}</div>
|
|
1162
|
+
</div>
|
|
1163
|
+
<div class="coverage-cell">
|
|
1164
|
+
<div class="coverage-percent ${stats.functions.percentage >= 80 ? "high" : stats.functions.percentage >= 50 ? "medium" : "low"}">${stats.functions.percentage.toFixed(2)}%</div>
|
|
1165
|
+
<div class="coverage-count">${coverage.coveredFunctions}/${coverage.functions}</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
<div class="coverage-cell">
|
|
1168
|
+
<div class="coverage-percent ${stats.lines.percentage >= 80 ? "high" : stats.lines.percentage >= 50 ? "medium" : "low"}">${stats.lines.percentage.toFixed(2)}%</div>
|
|
1169
|
+
<div class="coverage-count">${coverage.coveredLines}/${coverage.lines}</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
</div>
|
|
1172
|
+
`;
|
|
1173
|
+
}).join("")}
|
|
1174
|
+
</div>
|
|
1175
|
+
<div id="no-results" class="no-results hidden">No files found matching your search</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
</div>
|
|
1178
|
+
|
|
1179
|
+
<script>
|
|
1180
|
+
const searchInput = document.getElementById('search-input');
|
|
1181
|
+
const fileRows = document.getElementById('file-rows');
|
|
1182
|
+
const noResults = document.getElementById('no-results');
|
|
1183
|
+
const fileRowElements = fileRows.querySelectorAll('.file-row');
|
|
1184
|
+
|
|
1185
|
+
searchInput.addEventListener('input', function() {
|
|
1186
|
+
const searchTerm = this.value.toLowerCase().trim();
|
|
1187
|
+
let visibleCount = 0;
|
|
1188
|
+
|
|
1189
|
+
fileRowElements.forEach(function(row) {
|
|
1190
|
+
const fileName = row.querySelector('.file-name').textContent.toLowerCase();
|
|
1191
|
+
if (fileName.includes(searchTerm)) {
|
|
1192
|
+
row.classList.remove('hidden');
|
|
1193
|
+
visibleCount++;
|
|
1194
|
+
} else {
|
|
1195
|
+
row.classList.add('hidden');
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
if (visibleCount === 0) {
|
|
1200
|
+
noResults.classList.remove('hidden');
|
|
1201
|
+
} else {
|
|
1202
|
+
noResults.classList.add('hidden');
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
</script>
|
|
1206
|
+
</body>
|
|
1207
|
+
</html>`;
|
|
1208
|
+
writeFileSync(join(reportsDir, "index.html"), indexHtml, "utf-8");
|
|
1209
|
+
for (const [filePath, coverage] of coverageMap.entries()) {
|
|
1210
|
+
generateFileDetailPage(filePath, coverage, reportsDir, toRelative);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function generateFileDetailPage(filePath, coverage, reportsDir, toRelative) {
|
|
1214
|
+
const fileName = toRelative(filePath);
|
|
1215
|
+
const safeFileName = fileName.replace(/[\/\\]/g, "_") + ".html";
|
|
1216
|
+
const stats = calculateFileCoverage(coverage);
|
|
1217
|
+
let sourceLines = [];
|
|
1218
|
+
try {
|
|
1219
|
+
const sourceCode = readFileSync(filePath, "utf-8").toString();
|
|
1220
|
+
sourceLines = sourceCode.split("\n");
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
sourceLines = ["// Unable to read source file"];
|
|
1223
|
+
}
|
|
1224
|
+
const uncoveredSet = new Set(coverage.uncoveredLines || []);
|
|
1225
|
+
const fileHtml = `<!DOCTYPE html>
|
|
1226
|
+
<html>
|
|
1227
|
+
<head>
|
|
1228
|
+
<meta charset="utf-8">
|
|
1229
|
+
<title>Coverage: ${fileName}</title>
|
|
1230
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%236366f1'/%3E%3Cstop offset='100%25' stop-color='%238b5cf6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='100' height='100' rx='20' fill='url(%23grad)'/%3E%3Crect x='28' y='25' width='44' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='46' width='32' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='67' width='44' height='8' rx='4' fill='white'/%3E%3Crect x='28' y='25' width='8' height='50' rx='4' fill='white'/%3E%3Ccircle cx='72' cy='50' r='6' fill='white' opacity='0.5'/%3E%3C/svg%3E">
|
|
1231
|
+
<style>
|
|
1232
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1233
|
+
body {
|
|
1234
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
1235
|
+
background: #0d1117;
|
|
1236
|
+
color: #c9d1d9;
|
|
1237
|
+
padding: 20px;
|
|
1238
|
+
}
|
|
1239
|
+
.container { max-width: 1400px; margin: 0 auto; }
|
|
1240
|
+
a { color: #58a6ff; text-decoration: none; }
|
|
1241
|
+
a:hover { text-decoration: underline; }
|
|
1242
|
+
h1 {
|
|
1243
|
+
font-size: 24px;
|
|
1244
|
+
font-weight: 600;
|
|
1245
|
+
margin-bottom: 10px;
|
|
1246
|
+
color: #58a6ff;
|
|
1247
|
+
}
|
|
1248
|
+
.breadcrumb {
|
|
1249
|
+
font-size: 14px;
|
|
1250
|
+
color: #8b949e;
|
|
1251
|
+
margin-bottom: 20px;
|
|
1252
|
+
}
|
|
1253
|
+
.breadcrumb a { color: #58a6ff; }
|
|
1254
|
+
.summary {
|
|
1255
|
+
background: #161b22;
|
|
1256
|
+
border: 1px solid #30363d;
|
|
1257
|
+
border-radius: 6px;
|
|
1258
|
+
padding: 20px;
|
|
1259
|
+
margin-bottom: 20px;
|
|
1260
|
+
}
|
|
1261
|
+
.summary-title {
|
|
1262
|
+
font-size: 16px;
|
|
1263
|
+
font-weight: 600;
|
|
1264
|
+
margin-bottom: 15px;
|
|
1265
|
+
color: #c9d1d9;
|
|
1266
|
+
}
|
|
1267
|
+
.metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
|
|
1268
|
+
.metric {
|
|
1269
|
+
background: #21262d;
|
|
1270
|
+
border: 1px solid #30363d;
|
|
1271
|
+
border-radius: 6px;
|
|
1272
|
+
padding: 15px;
|
|
1273
|
+
text-align: center;
|
|
1274
|
+
}
|
|
1275
|
+
.metric-label { font-size: 12px; color: #8b949e; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
1276
|
+
.metric-value { font-size: 24px; font-weight: 700; }
|
|
1277
|
+
.metric-value.high { color: #3fb950; }
|
|
1278
|
+
.metric-value.medium { color: #d29922; }
|
|
1279
|
+
.metric-value.low { color: #f85149; }
|
|
1280
|
+
.progress-bar {
|
|
1281
|
+
height: 8px;
|
|
1282
|
+
background: #21262d;
|
|
1283
|
+
border-radius: 4px;
|
|
1284
|
+
overflow: hidden;
|
|
1285
|
+
margin-top: 8px;
|
|
1286
|
+
}
|
|
1287
|
+
.progress-fill { height: 100%; transition: width 0.3s ease; }
|
|
1288
|
+
.progress-fill.high { background: #3fb950; }
|
|
1289
|
+
.progress-fill.medium { background: #d29922; }
|
|
1290
|
+
.progress-fill.low { background: #f85149; }
|
|
1291
|
+
.metric-count { font-size: 11px; color: #8b949e; margin-top: 5px; }
|
|
1292
|
+
.code-container {
|
|
1293
|
+
background: #161b22;
|
|
1294
|
+
border: 1px solid #30363d;
|
|
1295
|
+
border-radius: 6px;
|
|
1296
|
+
overflow: hidden;
|
|
1297
|
+
}
|
|
1298
|
+
.code-header {
|
|
1299
|
+
padding: 10px 15px;
|
|
1300
|
+
background: #21262d;
|
|
1301
|
+
border-bottom: 1px solid #30363d;
|
|
1302
|
+
font-size: 13px;
|
|
1303
|
+
color: #8b949e;
|
|
1304
|
+
display: flex;
|
|
1305
|
+
justify-content: space-between;
|
|
1306
|
+
}
|
|
1307
|
+
.legend { display: flex; gap: 15px; font-size: 12px; }
|
|
1308
|
+
.legend-item { display: flex; align-items: center; gap: 5px; }
|
|
1309
|
+
.legend-box { width: 12px; height: 12px; border-radius: 2px; }
|
|
1310
|
+
.legend-box.covered { background: rgba(63, 185, 80, 0.2); border: 1px solid #3fb950; }
|
|
1311
|
+
.legend-box.uncovered { background: rgba(248, 81, 73, 0.2); border: 1px solid #f85149; }
|
|
1312
|
+
.code-table { width: 100%; border-collapse: collapse; }
|
|
1313
|
+
.code-table td { padding: 0; }
|
|
1314
|
+
.line-number {
|
|
1315
|
+
width: 50px;
|
|
1316
|
+
text-align: right;
|
|
1317
|
+
padding: 0 15px;
|
|
1318
|
+
color: #8b949e;
|
|
1319
|
+
font-size: 12px;
|
|
1320
|
+
user-select: none;
|
|
1321
|
+
border-right: 1px solid #30363d;
|
|
1322
|
+
}
|
|
1323
|
+
.line-content {
|
|
1324
|
+
padding: 0 15px;
|
|
1325
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
1326
|
+
font-size: 13px;
|
|
1327
|
+
line-height: 20px;
|
|
1328
|
+
white-space: pre;
|
|
1329
|
+
}
|
|
1330
|
+
tr.covered .line-content { background: rgba(63, 185, 80, 0.1); }
|
|
1331
|
+
tr.uncovered .line-content { background: rgba(248, 81, 73, 0.15); }
|
|
1332
|
+
tr.uncovered .line-number { color: #f85149; }
|
|
1333
|
+
tr:hover td { background: rgba(88, 166, 255, 0.1); }
|
|
1334
|
+
</style>
|
|
1335
|
+
</head>
|
|
1336
|
+
<body>
|
|
1337
|
+
<div class="container">
|
|
1338
|
+
<div class="breadcrumb">
|
|
1339
|
+
<a href="index.html">\u2190 Back to Coverage Report</a>
|
|
1340
|
+
</div>
|
|
1341
|
+
|
|
1342
|
+
<h1>${fileName}</h1>
|
|
1343
|
+
|
|
1344
|
+
<div class="summary">
|
|
1345
|
+
<div class="summary-title">Coverage Metrics</div>
|
|
1346
|
+
<div class="metrics">
|
|
1347
|
+
<div class="metric">
|
|
1348
|
+
<div class="metric-label">Statements</div>
|
|
1349
|
+
<div class="metric-value ${stats.statements.percentage >= 80 ? "high" : stats.statements.percentage >= 50 ? "medium" : "low"}">${stats.statements.percentage.toFixed(2)}%</div>
|
|
1350
|
+
<div class="progress-bar">
|
|
1351
|
+
<div class="progress-fill ${stats.statements.percentage >= 80 ? "high" : stats.statements.percentage >= 50 ? "medium" : "low"}" style="width: ${stats.statements.percentage}%"></div>
|
|
1352
|
+
</div>
|
|
1353
|
+
<div class="metric-count">${coverage.coveredStatements}/${coverage.statements}</div>
|
|
1354
|
+
</div>
|
|
1355
|
+
<div class="metric">
|
|
1356
|
+
<div class="metric-label">Branches</div>
|
|
1357
|
+
<div class="metric-value ${stats.branches.percentage >= 80 ? "high" : stats.branches.percentage >= 50 ? "medium" : "low"}">${stats.branches.percentage.toFixed(2)}%</div>
|
|
1358
|
+
<div class="progress-bar">
|
|
1359
|
+
<div class="progress-fill ${stats.branches.percentage >= 80 ? "high" : stats.branches.percentage >= 50 ? "medium" : "low"}" style="width: ${stats.branches.percentage}%"></div>
|
|
1360
|
+
</div>
|
|
1361
|
+
<div class="metric-count">${coverage.coveredBranches}/${coverage.branches}</div>
|
|
1362
|
+
</div>
|
|
1363
|
+
<div class="metric">
|
|
1364
|
+
<div class="metric-label">Functions</div>
|
|
1365
|
+
<div class="metric-value ${stats.functions.percentage >= 80 ? "high" : stats.functions.percentage >= 50 ? "medium" : "low"}">${stats.functions.percentage.toFixed(2)}%</div>
|
|
1366
|
+
<div class="progress-bar">
|
|
1367
|
+
<div class="progress-fill ${stats.functions.percentage >= 80 ? "high" : stats.functions.percentage >= 50 ? "medium" : "low"}" style="width: ${stats.functions.percentage}%"></div>
|
|
1368
|
+
</div>
|
|
1369
|
+
<div class="metric-count">${coverage.coveredFunctions}/${coverage.functions}</div>
|
|
1370
|
+
</div>
|
|
1371
|
+
<div class="metric">
|
|
1372
|
+
<div class="metric-label">Lines</div>
|
|
1373
|
+
<div class="metric-value ${stats.lines.percentage >= 80 ? "high" : stats.lines.percentage >= 50 ? "medium" : "low"}">${stats.lines.percentage.toFixed(2)}%</div>
|
|
1374
|
+
<div class="progress-bar">
|
|
1375
|
+
<div class="progress-fill ${stats.lines.percentage >= 80 ? "high" : stats.lines.percentage >= 50 ? "medium" : "low"}" style="width: ${stats.lines.percentage}%"></div>
|
|
1376
|
+
</div>
|
|
1377
|
+
<div class="metric-count">${coverage.coveredLines}/${coverage.lines}</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
</div>
|
|
1381
|
+
|
|
1382
|
+
${coverage.uncoveredLines && coverage.uncoveredLines.length > 0 ? `
|
|
1383
|
+
<div class="summary">
|
|
1384
|
+
<div class="summary-title">Uncovered Lines</div>
|
|
1385
|
+
<div style="font-size: 13px; color: #f85149;">${formatUncoveredLines(coverage.uncoveredLines)}</div>
|
|
1386
|
+
</div>
|
|
1387
|
+
` : ""}
|
|
1388
|
+
|
|
1389
|
+
<div class="code-container">
|
|
1390
|
+
<div class="code-header">
|
|
1391
|
+
<span>Source Code</span>
|
|
1392
|
+
<div class="legend">
|
|
1393
|
+
<div class="legend-item"><div class="legend-box covered"></div><span>Covered</span></div>
|
|
1394
|
+
<div class="legend-item"><div class="legend-box uncovered"></div><span>Uncovered</span></div>
|
|
1395
|
+
</div>
|
|
1396
|
+
</div>
|
|
1397
|
+
<table class="code-table">
|
|
1398
|
+
${sourceLines.map((line, index) => {
|
|
1399
|
+
const lineNum = index + 1;
|
|
1400
|
+
const isUncovered = uncoveredSet.has(lineNum);
|
|
1401
|
+
const isExecutable = coverage.lines > 0;
|
|
1402
|
+
const rowClass = isExecutable ? isUncovered ? "uncovered" : "covered" : "";
|
|
1403
|
+
return `
|
|
1404
|
+
<tr class="${rowClass}">
|
|
1405
|
+
<td class="line-number">${lineNum}</td>
|
|
1406
|
+
<td class="line-content">${escapeHtml(line) || " "}</td>
|
|
1407
|
+
</tr>
|
|
1408
|
+
`;
|
|
1409
|
+
}).join("")}
|
|
1410
|
+
</table>
|
|
1411
|
+
</div>
|
|
1412
|
+
</div>
|
|
1413
|
+
</body>
|
|
1414
|
+
</html>`;
|
|
1415
|
+
writeFileSync(join(reportsDir, safeFileName), fileHtml, "utf-8");
|
|
1416
|
+
}
|
|
1417
|
+
function escapeHtml(text) {
|
|
1418
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1419
|
+
}
|
|
1420
|
+
function escapeXml(text) {
|
|
1421
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1422
|
+
}
|
|
1423
|
+
function generateCoverageFinalJson(coverageMap, reportsDir) {
|
|
1424
|
+
const coverageData = {};
|
|
1425
|
+
for (const [filePath, coverage] of coverageMap.entries()) {
|
|
1426
|
+
const relativePath2 = relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1427
|
+
const lineMap = {};
|
|
1428
|
+
const executableLines = getExecutableLines(filePath);
|
|
1429
|
+
for (const line of executableLines) {
|
|
1430
|
+
const isCovered = coverage.coveredStatements > 0;
|
|
1431
|
+
lineMap[line] = isCovered ? 1 : 0;
|
|
1432
|
+
}
|
|
1433
|
+
coverageData[relativePath2] = {
|
|
1434
|
+
lines: lineMap
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
const jsonData = JSON.stringify(coverageData, null, 2);
|
|
1438
|
+
writeFileSync(join(reportsDir, "coverage-final.json"), jsonData, "utf-8");
|
|
1439
|
+
}
|
|
1440
|
+
function generateCloverXml(coverageMap, reportsDir) {
|
|
1441
|
+
const timestamp = Date.now();
|
|
1442
|
+
let totalFiles = 0;
|
|
1443
|
+
let totalClasses = 0;
|
|
1444
|
+
let totalElements = 0;
|
|
1445
|
+
let coveredElements = 0;
|
|
1446
|
+
let totalStatements = 0;
|
|
1447
|
+
let coveredStatements = 0;
|
|
1448
|
+
let totalBranches = 0;
|
|
1449
|
+
let coveredBranches = 0;
|
|
1450
|
+
let totalFunctions = 0;
|
|
1451
|
+
let coveredFunctions = 0;
|
|
1452
|
+
let totalLines = 0;
|
|
1453
|
+
let coveredLines = 0;
|
|
1454
|
+
const fileEntries = [];
|
|
1455
|
+
for (const [filePath, coverage] of coverageMap.entries()) {
|
|
1456
|
+
const relativePath2 = relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
1457
|
+
totalFiles++;
|
|
1458
|
+
totalClasses++;
|
|
1459
|
+
totalStatements += coverage.statements;
|
|
1460
|
+
coveredStatements += coverage.coveredStatements;
|
|
1461
|
+
totalBranches += coverage.branches;
|
|
1462
|
+
coveredBranches += coverage.coveredBranches;
|
|
1463
|
+
totalFunctions += coverage.functions;
|
|
1464
|
+
coveredFunctions += coverage.coveredFunctions;
|
|
1465
|
+
totalLines += coverage.lines;
|
|
1466
|
+
coveredLines += coverage.coveredLines;
|
|
1467
|
+
const fileElements = coverage.statements + coverage.branches + coverage.functions;
|
|
1468
|
+
const fileCoveredElements = coverage.coveredStatements + coverage.coveredBranches + coverage.coveredFunctions;
|
|
1469
|
+
totalElements += fileElements;
|
|
1470
|
+
coveredElements += fileCoveredElements;
|
|
1471
|
+
const escapedPath = escapeXml(relativePath2);
|
|
1472
|
+
fileEntries.push(`
|
|
1473
|
+
<file name="${escapedPath}">
|
|
1474
|
+
<class name="${escapedPath}">
|
|
1475
|
+
<metrics complexity="0" elements="${fileElements}" coveredelements="${fileCoveredElements}"
|
|
1476
|
+
methods="${coverage.functions}" coveredmethods="${coverage.coveredFunctions}"
|
|
1477
|
+
statements="${coverage.statements}" coveredstatements="${coverage.coveredStatements}" />
|
|
1478
|
+
</class>
|
|
1479
|
+
<line num="1" type="stmt" count="${coverage.coveredStatements > 0 ? 1 : 0}" />
|
|
1480
|
+
</file>`);
|
|
1481
|
+
}
|
|
1482
|
+
const complexity = 0;
|
|
1483
|
+
const elements = totalElements;
|
|
1484
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1485
|
+
<coverage generated="${timestamp}" clover="3.2.0">
|
|
1486
|
+
<project timestamp="${timestamp}" name="Coverage">
|
|
1487
|
+
<metrics complexity="${complexity}" elements="${elements}" coveredelements="${coveredElements}"
|
|
1488
|
+
conditionals="${totalBranches}" coveredconditionals="${coveredBranches}"
|
|
1489
|
+
statements="${totalStatements}" coveredstatements="${coveredStatements}"
|
|
1490
|
+
methods="${totalFunctions}" coveredmethods="${coveredFunctions}"
|
|
1491
|
+
classes="${totalClasses}" coveredclasses="${coverageMap.size > 0 ? Array.from(coverageMap.values()).filter((c) => c.coveredStatements > 0).length : 0}"
|
|
1492
|
+
files="${totalFiles}" loc="${totalLines}" ncloc="${totalLines - coveredLines}"
|
|
1493
|
+
packages="${totalFiles}" classes="${totalClasses}" />
|
|
1494
|
+
<package name="root">
|
|
1495
|
+
<metrics complexity="${complexity}" elements="${elements}" coveredelements="${coveredElements}"
|
|
1496
|
+
conditionals="${totalBranches}" coveredconditionals="${coveredBranches}"
|
|
1497
|
+
statements="${totalStatements}" coveredstatements="${coveredStatements}"
|
|
1498
|
+
methods="${totalFunctions}" coveredmethods="${coveredFunctions}"
|
|
1499
|
+
classes="${totalClasses}" coveredclasses="${coverageMap.size > 0 ? Array.from(coverageMap.values()).filter((c) => c.coveredStatements > 0).length : 0}"
|
|
1500
|
+
files="${totalFiles}" loc="${totalLines}" ncloc="${totalLines - coveredLines}" />
|
|
1501
|
+
${fileEntries.join("")}
|
|
1502
|
+
</package>
|
|
1503
|
+
</project>
|
|
1504
|
+
</coverage>`;
|
|
1505
|
+
writeFileSync(join(reportsDir, "clover.xml"), xml, "utf-8");
|
|
1506
|
+
}
|
|
1507
|
+
export {
|
|
1508
|
+
calculateUncoveredLines,
|
|
1509
|
+
generateCloverXml,
|
|
1510
|
+
generateCoverageFinalJson,
|
|
1511
|
+
generateHtmlReport,
|
|
1512
|
+
generateTextReport,
|
|
1513
|
+
getExecutedLines,
|
|
1514
|
+
initializeCoverageTracking,
|
|
1515
|
+
markFileAsCovered,
|
|
1516
|
+
markLineExecuted,
|
|
1517
|
+
processCoverage,
|
|
1518
|
+
resetCoverageTracking
|
|
1519
|
+
};
|
|
1520
|
+
//# sourceMappingURL=coverage.mjs.map
|