@swedevtools/livedoc-vitest 0.2.0 → 0.3.0
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 +34 -0
- package/LICENSE +20 -20
- package/README.md +160 -95
- package/dist/{RuleContext-BZhuy-zS.d.cts → RuleContext-DQ8o_n1D.d.ts} +62 -62
- package/dist/globals.d.ts +99 -99
- package/dist/{index-Blmp569T.d.cts → index-sbV15ohX.d.ts} +21 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1062 -159
- package/dist/reporter/index.d.ts +2 -2
- package/dist/reporter/index.js +629 -84
- package/package.json +14 -12
- package/tools/livedoc-setup.mjs +172 -164
- package/tools/skills/SKILL.md +339 -244
- package/tools/skills/VALIDATION.md +37 -29
- package/tools/skills/examples/routing.md +75 -60
- package/tools/skills/resources/anti-patterns.md +19 -0
- package/tools/skills/resources/bdd-features.md +231 -231
- package/tools/skills/resources/partial-testing.md +77 -0
- package/tools/skills/resources/playwright.md +148 -148
- package/tools/skills/resources/reporter-config.md +213 -163
- package/tools/skills/resources/specifications.md +159 -159
- package/tools/skills/resources/test-strategy.md +103 -0
- package/tools/skills/resources/web-testing.md +62 -0
- package/dist/RuleContext-BZhuy-zS.d.ts +0 -206
- package/dist/globals.cjs +0 -2
- package/dist/globals.d.cts +0 -104
- package/dist/index-CysiWbtk.d.ts +0 -687
- package/dist/index.cjs +0 -10024
- package/dist/index.d.cts +0 -291
- package/dist/playwright/index.cjs +0 -103
- package/dist/playwright/index.d.cts +0 -129
- package/dist/reporter/index.cjs +0 -8676
- package/dist/reporter/index.d.cts +0 -7
- package/dist/setup.cjs +0 -14
- package/dist/setup.d.cts +0 -2
package/dist/reporter/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk, { Chalk } from 'chalk';
|
|
2
|
-
import { existsSync, unlinkSync, appendFileSync, mkdirSync, writeFileSync, promises } from 'fs';
|
|
3
|
-
import * as
|
|
4
|
-
import { resolve, dirname } from 'path';
|
|
2
|
+
import { existsSync, unlinkSync, appendFileSync, mkdirSync, writeFileSync, promises, statSync, readFileSync } from 'fs';
|
|
3
|
+
import * as path2 from 'path';
|
|
4
|
+
import path2__default, { resolve, dirname } from 'path';
|
|
5
5
|
import CliTable3 from 'cli-table3';
|
|
6
6
|
import * as diff from 'diff';
|
|
7
7
|
import wordwrap from 'wordwrap';
|
|
@@ -1287,9 +1287,9 @@ var LiveDocReporter = class {
|
|
|
1287
1287
|
if (!strs || strs.length === 0)
|
|
1288
1288
|
return "";
|
|
1289
1289
|
if (strs.length === 1)
|
|
1290
|
-
return
|
|
1290
|
+
return path2.dirname(strs[0]);
|
|
1291
1291
|
for (let i = 0; i < strs.length; i++) {
|
|
1292
|
-
strs[i] =
|
|
1292
|
+
strs[i] = path2.dirname(strs[i]);
|
|
1293
1293
|
}
|
|
1294
1294
|
let shortestString = "";
|
|
1295
1295
|
let shortestLength = Number.MAX_SAFE_INTEGER;
|
|
@@ -1327,8 +1327,8 @@ var LiveDocReporter = class {
|
|
|
1327
1327
|
if (rootPath.length === 0 || stripPath === "") {
|
|
1328
1328
|
return "";
|
|
1329
1329
|
} else {
|
|
1330
|
-
let dirPath =
|
|
1331
|
-
if (dirPath.startsWith(
|
|
1330
|
+
let dirPath = path2.parse(stripPath).dir;
|
|
1331
|
+
if (dirPath.startsWith(path2.sep)) {
|
|
1332
1332
|
return dirPath.substr(1);
|
|
1333
1333
|
} else {
|
|
1334
1334
|
return dirPath;
|
|
@@ -1548,6 +1548,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
1548
1548
|
}
|
|
1549
1549
|
outputSpecificationDetails(spec) {
|
|
1550
1550
|
this.writeLine(this.formatKeywordTitle("Specification", spec.title, this.colorTheme.keyword, this.colorTheme.featureTitle, 2));
|
|
1551
|
+
if (spec.tags.length > 0) {
|
|
1552
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(spec.tags), 4));
|
|
1553
|
+
}
|
|
1551
1554
|
if (spec.description) {
|
|
1552
1555
|
this.writeLine(this.formatDescription(spec.description, 4, this.colorTheme.featureDescription));
|
|
1553
1556
|
}
|
|
@@ -1557,6 +1560,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
1557
1560
|
const status = this.getStatusIndicator(rule.status);
|
|
1558
1561
|
const title = this.highlight(rule.title, /<[^>]+>/g, this.colorTheme.valuePlaceholders);
|
|
1559
1562
|
this.writeLine(` ${status} ${this.colorTheme.keyword("Rule Outline:")} ${this.colorTheme.scenarioTitle(title)}`);
|
|
1563
|
+
if (rule.tags.length > 0) {
|
|
1564
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(rule.tags), 6));
|
|
1565
|
+
}
|
|
1560
1566
|
if (rule.description) {
|
|
1561
1567
|
this.writeLine(this.formatDescription(rule.description, 6, this.colorTheme.scenarioDescription));
|
|
1562
1568
|
}
|
|
@@ -1585,6 +1591,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
1585
1591
|
} else {
|
|
1586
1592
|
const status = this.getStatusIndicator(rule.status);
|
|
1587
1593
|
this.writeLine(` ${status} ${this.colorTheme.keyword("Rule:")} ${this.colorTheme.stepTitle(rule.title)}`);
|
|
1594
|
+
if (rule.tags.length > 0) {
|
|
1595
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(rule.tags), 6));
|
|
1596
|
+
}
|
|
1588
1597
|
}
|
|
1589
1598
|
}
|
|
1590
1599
|
this.writeLine(" ");
|
|
@@ -2897,8 +2906,8 @@ function getErrorMap() {
|
|
|
2897
2906
|
|
|
2898
2907
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
2899
2908
|
var makeIssue = (params) => {
|
|
2900
|
-
const { data, path:
|
|
2901
|
-
const fullPath = [...
|
|
2909
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
2910
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
2902
2911
|
const fullIssue = {
|
|
2903
2912
|
...issueData,
|
|
2904
2913
|
path: fullPath
|
|
@@ -3014,11 +3023,11 @@ var errorUtil;
|
|
|
3014
3023
|
|
|
3015
3024
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
3016
3025
|
var ParseInputLazyPath = class {
|
|
3017
|
-
constructor(parent, value,
|
|
3026
|
+
constructor(parent, value, path4, key) {
|
|
3018
3027
|
this._cachedPath = [];
|
|
3019
3028
|
this.parent = parent;
|
|
3020
3029
|
this.data = value;
|
|
3021
|
-
this._path =
|
|
3030
|
+
this._path = path4;
|
|
3022
3031
|
this._key = key;
|
|
3023
3032
|
}
|
|
3024
3033
|
get path() {
|
|
@@ -6470,9 +6479,9 @@ function simpleHash(str) {
|
|
|
6470
6479
|
return Math.abs(hash).toString(36);
|
|
6471
6480
|
}
|
|
6472
6481
|
function generateStabilityId(params) {
|
|
6473
|
-
const { project, path:
|
|
6482
|
+
const { project, path: path4, title, kind, parentId, keyword, index } = params;
|
|
6474
6483
|
if (!parentId) {
|
|
6475
|
-
return simpleHash(`${project}:${
|
|
6484
|
+
return simpleHash(`${project}:${path4 || ""}:${title}`);
|
|
6476
6485
|
}
|
|
6477
6486
|
if (kind === "Step") {
|
|
6478
6487
|
return `${parentId}:${simpleHash(`${keyword || ""}:${title}:${index ?? 0}`)}`;
|
|
@@ -6538,6 +6547,53 @@ var V1StatisticsSchema = external_exports.object({
|
|
|
6538
6547
|
pending: external_exports.number().int().nonnegative(),
|
|
6539
6548
|
skipped: external_exports.number().int().nonnegative()
|
|
6540
6549
|
});
|
|
6550
|
+
var V1CoverageMetricNameSchema = external_exports.enum(["lines", "branches", "functions", "statements"]);
|
|
6551
|
+
var V1CoverageMetricSchema = external_exports.object({
|
|
6552
|
+
covered: external_exports.number().nonnegative(),
|
|
6553
|
+
total: external_exports.number().nonnegative(),
|
|
6554
|
+
skipped: external_exports.number().nonnegative().optional(),
|
|
6555
|
+
pct: external_exports.number().min(0).max(100).nullable()
|
|
6556
|
+
});
|
|
6557
|
+
var V1CoverageSummarySchema = external_exports.object({
|
|
6558
|
+
lines: V1CoverageMetricSchema.optional(),
|
|
6559
|
+
branches: V1CoverageMetricSchema.optional(),
|
|
6560
|
+
functions: V1CoverageMetricSchema.optional(),
|
|
6561
|
+
statements: V1CoverageMetricSchema.optional()
|
|
6562
|
+
});
|
|
6563
|
+
var V1CoverageProvenanceSchema = external_exports.object({
|
|
6564
|
+
tool: external_exports.string().optional(),
|
|
6565
|
+
format: external_exports.string(),
|
|
6566
|
+
path: external_exports.string().optional(),
|
|
6567
|
+
detected: external_exports.enum(["auto", "configured"]),
|
|
6568
|
+
generatedAt: external_exports.string().optional()
|
|
6569
|
+
});
|
|
6570
|
+
var V1CoverageFileSchema = external_exports.object({
|
|
6571
|
+
path: external_exports.string(),
|
|
6572
|
+
module: external_exports.string().optional(),
|
|
6573
|
+
summary: V1CoverageSummarySchema,
|
|
6574
|
+
detailRef: external_exports.string().optional()
|
|
6575
|
+
});
|
|
6576
|
+
var V1CoverageDiagnosticSchema = external_exports.object({
|
|
6577
|
+
severity: external_exports.enum(["info", "warning", "error"]),
|
|
6578
|
+
code: external_exports.string(),
|
|
6579
|
+
message: external_exports.string(),
|
|
6580
|
+
path: external_exports.string().optional(),
|
|
6581
|
+
details: external_exports.array(external_exports.string()).optional()
|
|
6582
|
+
});
|
|
6583
|
+
var V1CoverageThresholdSchema = external_exports.object({
|
|
6584
|
+
metric: V1CoverageMetricNameSchema,
|
|
6585
|
+
minimum: external_exports.number().min(0).max(100),
|
|
6586
|
+
actual: external_exports.number().min(0).max(100).nullable(),
|
|
6587
|
+
status: external_exports.enum(["passed", "warning"])
|
|
6588
|
+
});
|
|
6589
|
+
var V1CoverageReportSchema = external_exports.object({
|
|
6590
|
+
status: external_exports.enum(["available", "not-collected", "partial", "invalid"]),
|
|
6591
|
+
summary: V1CoverageSummarySchema.optional(),
|
|
6592
|
+
files: external_exports.array(V1CoverageFileSchema).optional(),
|
|
6593
|
+
diagnostics: external_exports.array(V1CoverageDiagnosticSchema).optional(),
|
|
6594
|
+
provenance: V1CoverageProvenanceSchema.optional(),
|
|
6595
|
+
thresholds: external_exports.array(V1CoverageThresholdSchema).optional()
|
|
6596
|
+
});
|
|
6541
6597
|
var V1BaseTestSchema = external_exports.object({
|
|
6542
6598
|
id: external_exports.string(),
|
|
6543
6599
|
kind: external_exports.string(),
|
|
@@ -6601,10 +6657,12 @@ var V1TestCaseSchema = external_exports.object({
|
|
|
6601
6657
|
ruleViolations: external_exports.array(V1RuleViolationSchema).optional()
|
|
6602
6658
|
});
|
|
6603
6659
|
var V1FrameworkSchema = external_exports.string();
|
|
6660
|
+
var V1RunTypeSchema = external_exports.enum(["full", "partial"]);
|
|
6604
6661
|
external_exports.object({
|
|
6605
6662
|
protocolVersion: external_exports.literal("1.0"),
|
|
6606
6663
|
runId: external_exports.string(),
|
|
6607
|
-
|
|
6664
|
+
runType: V1RunTypeSchema.optional(),
|
|
6665
|
+
baselineRunId: external_exports.string().optional(),
|
|
6608
6666
|
project: external_exports.string(),
|
|
6609
6667
|
environment: external_exports.string(),
|
|
6610
6668
|
framework: V1FrameworkSchema,
|
|
@@ -6612,18 +6670,19 @@ external_exports.object({
|
|
|
6612
6670
|
duration: external_exports.number().nonnegative(),
|
|
6613
6671
|
status: V1StatusSchema,
|
|
6614
6672
|
summary: V1StatisticsSchema,
|
|
6615
|
-
documents: external_exports.array(V1TestCaseSchema)
|
|
6673
|
+
documents: external_exports.array(V1TestCaseSchema),
|
|
6674
|
+
coverage: V1CoverageReportSchema.optional()
|
|
6616
6675
|
});
|
|
6617
6676
|
external_exports.object({
|
|
6618
6677
|
project: external_exports.string(),
|
|
6619
6678
|
environment: external_exports.string(),
|
|
6620
6679
|
framework: external_exports.string(),
|
|
6621
|
-
timestamp: external_exports.string().optional()
|
|
6680
|
+
timestamp: external_exports.string().optional(),
|
|
6681
|
+
runType: V1RunTypeSchema.optional()
|
|
6622
6682
|
});
|
|
6623
6683
|
external_exports.object({
|
|
6624
6684
|
protocolVersion: external_exports.literal("1.0"),
|
|
6625
6685
|
runId: external_exports.string(),
|
|
6626
|
-
sessionId: external_exports.string().optional(),
|
|
6627
6686
|
websocketUrl: external_exports.string()
|
|
6628
6687
|
});
|
|
6629
6688
|
external_exports.object({
|
|
@@ -6634,7 +6693,8 @@ external_exports.object({
|
|
|
6634
6693
|
complete: external_exports.object({
|
|
6635
6694
|
status: V1StatusSchema,
|
|
6636
6695
|
duration: external_exports.number().nonnegative(),
|
|
6637
|
-
summary: V1StatisticsSchema.optional()
|
|
6696
|
+
summary: V1StatisticsSchema.optional(),
|
|
6697
|
+
coverage: V1CoverageReportSchema.optional()
|
|
6638
6698
|
}).optional()
|
|
6639
6699
|
});
|
|
6640
6700
|
external_exports.object({
|
|
@@ -6663,31 +6723,17 @@ var V1UpsertOutlineExampleResultsRequestSchema = external_exports.object({
|
|
|
6663
6723
|
external_exports.object({
|
|
6664
6724
|
status: V1StatusSchema,
|
|
6665
6725
|
duration: external_exports.number().nonnegative(),
|
|
6666
|
-
summary: V1StatisticsSchema.optional()
|
|
6667
|
-
|
|
6668
|
-
var V1SessionRunInfoSchema = external_exports.object({
|
|
6669
|
-
runId: external_exports.string(),
|
|
6670
|
-
framework: external_exports.string(),
|
|
6671
|
-
status: V1StatusSchema,
|
|
6672
|
-
timestamp: external_exports.string(),
|
|
6673
|
-
duration: external_exports.number().nonnegative(),
|
|
6674
|
-
summary: V1StatisticsSchema,
|
|
6675
|
-
documentCount: external_exports.number().int().nonnegative()
|
|
6726
|
+
summary: V1StatisticsSchema.optional(),
|
|
6727
|
+
coverage: V1CoverageReportSchema.optional()
|
|
6676
6728
|
});
|
|
6677
6729
|
external_exports.object({
|
|
6678
|
-
|
|
6679
|
-
project: external_exports.string(),
|
|
6680
|
-
environment: external_exports.string(),
|
|
6681
|
-
status: V1StatusSchema,
|
|
6682
|
-
timestamp: external_exports.string(),
|
|
6683
|
-
duration: external_exports.number().nonnegative(),
|
|
6684
|
-
summary: V1StatisticsSchema,
|
|
6685
|
-
runs: external_exports.array(V1SessionRunInfoSchema),
|
|
6686
|
-
documents: external_exports.array(V1TestCaseSchema)
|
|
6730
|
+
coverage: V1CoverageReportSchema
|
|
6687
6731
|
});
|
|
6688
6732
|
var V1WsRunStartedSchema = external_exports.object({
|
|
6689
6733
|
type: external_exports.literal("run:v1:started"),
|
|
6690
6734
|
runId: external_exports.string(),
|
|
6735
|
+
runType: V1RunTypeSchema.optional(),
|
|
6736
|
+
baselineRunId: external_exports.string().optional(),
|
|
6691
6737
|
project: external_exports.string(),
|
|
6692
6738
|
environment: external_exports.string(),
|
|
6693
6739
|
framework: external_exports.string(),
|
|
@@ -6721,15 +6767,13 @@ var V1WsRunCompletedSchema = external_exports.object({
|
|
|
6721
6767
|
runId: external_exports.string(),
|
|
6722
6768
|
status: V1StatusSchema,
|
|
6723
6769
|
duration: external_exports.number().nonnegative(),
|
|
6724
|
-
summary: V1StatisticsSchema
|
|
6770
|
+
summary: V1StatisticsSchema,
|
|
6771
|
+
coverage: V1CoverageReportSchema.optional()
|
|
6725
6772
|
});
|
|
6726
|
-
var
|
|
6727
|
-
type: external_exports.literal("
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
environment: external_exports.string(),
|
|
6731
|
-
status: V1StatusSchema,
|
|
6732
|
-
summary: V1StatisticsSchema
|
|
6773
|
+
var V1WsRunCoverageSchema = external_exports.object({
|
|
6774
|
+
type: external_exports.literal("run:v1:coverage"),
|
|
6775
|
+
runId: external_exports.string(),
|
|
6776
|
+
coverage: V1CoverageReportSchema
|
|
6733
6777
|
});
|
|
6734
6778
|
external_exports.union([
|
|
6735
6779
|
V1WsRunStartedSchema,
|
|
@@ -6738,19 +6782,360 @@ external_exports.union([
|
|
|
6738
6782
|
V1WsTestExecutionSchema,
|
|
6739
6783
|
V1WsOutlineExampleResultsSchema,
|
|
6740
6784
|
V1WsRunCompletedSchema,
|
|
6741
|
-
|
|
6785
|
+
V1WsRunCoverageSchema
|
|
6742
6786
|
]);
|
|
6787
|
+
var metricNames = ["lines", "branches", "functions", "statements"];
|
|
6788
|
+
function collectCoverageReport(options = {}) {
|
|
6789
|
+
const envEnabled = parseBoolean(process.env.LIVEDOC_COVERAGE);
|
|
6790
|
+
const configuredPath = firstNonEmpty(options.artifactPath, process.env.LIVEDOC_COVERAGE_PATH);
|
|
6791
|
+
const thresholds = resolveThresholds(options.thresholds);
|
|
6792
|
+
const rootDir = path2__default.resolve(options.rootDir || process.cwd());
|
|
6793
|
+
const reportsDirectory = options.reportsDirectory ? resolvePath(rootDir, options.reportsDirectory) : void 0;
|
|
6794
|
+
const explicit = envEnabled === true || options.enabled === true || !!configuredPath;
|
|
6795
|
+
if (options.coverageMap !== void 0) {
|
|
6796
|
+
try {
|
|
6797
|
+
const report = parseIstanbulCoverageMap(options.coverageMap, rootDir);
|
|
6798
|
+
const applied = applyThresholds(report, thresholds);
|
|
6799
|
+
return {
|
|
6800
|
+
...applied,
|
|
6801
|
+
provenance: {
|
|
6802
|
+
tool: "vitest",
|
|
6803
|
+
format: "istanbul-coverage-map",
|
|
6804
|
+
detected: "auto",
|
|
6805
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6806
|
+
}
|
|
6807
|
+
};
|
|
6808
|
+
} catch (error) {
|
|
6809
|
+
return diagnosticReport(
|
|
6810
|
+
"parse-failed",
|
|
6811
|
+
"warning",
|
|
6812
|
+
`LiveDoc could not parse Vitest's in-memory coverage map: ${error instanceof Error ? error.message : String(error)}`
|
|
6813
|
+
);
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
const candidates = findCandidates(rootDir, reportsDirectory, configuredPath);
|
|
6817
|
+
if (candidates.length === 0) {
|
|
6818
|
+
return explicit ? diagnosticReport("artifact-missing", "warning", "Coverage was enabled, but LiveDoc could not find a supported coverage artifact.") : void 0;
|
|
6819
|
+
}
|
|
6820
|
+
const stale = [];
|
|
6821
|
+
for (const candidate of candidates) {
|
|
6822
|
+
if (!existsSync(candidate.path)) continue;
|
|
6823
|
+
let fileStats;
|
|
6824
|
+
try {
|
|
6825
|
+
fileStats = statSync(candidate.path);
|
|
6826
|
+
} catch (error) {
|
|
6827
|
+
return diagnosticReport(
|
|
6828
|
+
"parse-failed",
|
|
6829
|
+
"warning",
|
|
6830
|
+
`LiveDoc could not read the coverage artifact metadata: ${error instanceof Error ? error.message : String(error)}`,
|
|
6831
|
+
candidate.path
|
|
6832
|
+
);
|
|
6833
|
+
}
|
|
6834
|
+
const generatedAt = fileStats.mtime.toISOString();
|
|
6835
|
+
if (isStale(fileStats, options.runStartedAt)) {
|
|
6836
|
+
stale.push({
|
|
6837
|
+
severity: "warning",
|
|
6838
|
+
code: "stale",
|
|
6839
|
+
message: "LiveDoc found a coverage artifact, but it appears older than the current test run.",
|
|
6840
|
+
path: candidate.path
|
|
6841
|
+
});
|
|
6842
|
+
if (candidate.detected === "configured") {
|
|
6843
|
+
return { status: "invalid", diagnostics: stale };
|
|
6844
|
+
}
|
|
6845
|
+
continue;
|
|
6846
|
+
}
|
|
6847
|
+
if (candidate.format === "unsupported") {
|
|
6848
|
+
return diagnosticReport(
|
|
6849
|
+
"unsupported-format",
|
|
6850
|
+
"warning",
|
|
6851
|
+
`LiveDoc does not support this coverage artifact format yet: ${path2__default.basename(candidate.path)}.`,
|
|
6852
|
+
candidate.path
|
|
6853
|
+
);
|
|
6854
|
+
}
|
|
6855
|
+
try {
|
|
6856
|
+
const report = candidate.format === "istanbul-json-summary" ? parseIstanbulSummary(candidate.path, rootDir) : parseLcov(candidate.path, rootDir);
|
|
6857
|
+
const applied = applyThresholds(report, thresholds);
|
|
6858
|
+
return {
|
|
6859
|
+
...applied,
|
|
6860
|
+
provenance: {
|
|
6861
|
+
tool: candidate.format === "lcov" ? "lcov" : "istanbul",
|
|
6862
|
+
format: candidate.format,
|
|
6863
|
+
path: candidate.path,
|
|
6864
|
+
detected: candidate.detected,
|
|
6865
|
+
generatedAt
|
|
6866
|
+
}
|
|
6867
|
+
};
|
|
6868
|
+
} catch (error) {
|
|
6869
|
+
return diagnosticReport(
|
|
6870
|
+
"parse-failed",
|
|
6871
|
+
"warning",
|
|
6872
|
+
`LiveDoc could not parse the coverage artifact: ${error instanceof Error ? error.message : String(error)}`,
|
|
6873
|
+
candidate.path
|
|
6874
|
+
);
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
if (stale.length > 0 && explicit) {
|
|
6878
|
+
return { status: "invalid", diagnostics: stale };
|
|
6879
|
+
}
|
|
6880
|
+
return void 0;
|
|
6881
|
+
}
|
|
6882
|
+
function parseIstanbulSummary(filePath, rootDir) {
|
|
6883
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
6884
|
+
if (!isRecord(data)) {
|
|
6885
|
+
throw new Error("Istanbul summary root must be an object.");
|
|
6886
|
+
}
|
|
6887
|
+
const total = data.total;
|
|
6888
|
+
if (!isRecord(total)) {
|
|
6889
|
+
throw new Error("Istanbul summary is missing the total coverage block.");
|
|
6890
|
+
}
|
|
6891
|
+
const files = Object.entries(data).filter(([key]) => key !== "total").map(([file, value]) => ({
|
|
6892
|
+
path: normalizeCoveragePath(file, rootDir),
|
|
6893
|
+
summary: normalizeSummary(value)
|
|
6894
|
+
})).filter((file) => Object.keys(file.summary).length > 0);
|
|
6895
|
+
return {
|
|
6896
|
+
status: "available",
|
|
6897
|
+
summary: normalizeSummary(total),
|
|
6898
|
+
files
|
|
6899
|
+
};
|
|
6900
|
+
}
|
|
6901
|
+
function parseIstanbulCoverageMap(value, rootDir) {
|
|
6902
|
+
if (!isIstanbulCoverageMap(value)) {
|
|
6903
|
+
throw new Error("Coverage value does not expose the Istanbul CoverageMap API.");
|
|
6904
|
+
}
|
|
6905
|
+
const files = value.files().map((file) => {
|
|
6906
|
+
const fileCoverage = value.fileCoverageFor(file);
|
|
6907
|
+
const summary = fileCoverage.toSummary().toJSON();
|
|
6908
|
+
return {
|
|
6909
|
+
path: normalizeCoveragePath(file, rootDir),
|
|
6910
|
+
summary: normalizeSummary(summary)
|
|
6911
|
+
};
|
|
6912
|
+
}).filter((file) => Object.keys(file.summary).length > 0);
|
|
6913
|
+
return {
|
|
6914
|
+
status: "available",
|
|
6915
|
+
summary: normalizeSummary(value.getCoverageSummary().toJSON()),
|
|
6916
|
+
files
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
6919
|
+
function parseLcov(filePath, rootDir) {
|
|
6920
|
+
const text = readFileSync(filePath, "utf8");
|
|
6921
|
+
const files = [];
|
|
6922
|
+
let current;
|
|
6923
|
+
const flush = () => {
|
|
6924
|
+
if (!current?.path) return;
|
|
6925
|
+
const lineValues = Array.from(current.lines.values());
|
|
6926
|
+
const branchValues = current.branches;
|
|
6927
|
+
const functionValues = Array.from(current.functions.values());
|
|
6928
|
+
const summary = {
|
|
6929
|
+
lines: metricFromHits(lineValues)
|
|
6930
|
+
};
|
|
6931
|
+
if (branchValues.length > 0) summary.branches = metricFromHits(branchValues);
|
|
6932
|
+
if (functionValues.length > 0) summary.functions = metricFromHits(functionValues);
|
|
6933
|
+
files.push({ path: normalizeCoveragePath(current.path, rootDir), summary });
|
|
6934
|
+
};
|
|
6935
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
6936
|
+
const line = rawLine.trim();
|
|
6937
|
+
if (!line) continue;
|
|
6938
|
+
if (line === "end_of_record") {
|
|
6939
|
+
flush();
|
|
6940
|
+
current = void 0;
|
|
6941
|
+
continue;
|
|
6942
|
+
}
|
|
6943
|
+
if (line.startsWith("SF:")) {
|
|
6944
|
+
flush();
|
|
6945
|
+
current = { path: line.slice(3), lines: /* @__PURE__ */ new Map(), branches: [], functions: /* @__PURE__ */ new Map() };
|
|
6946
|
+
continue;
|
|
6947
|
+
}
|
|
6948
|
+
if (!current) continue;
|
|
6949
|
+
if (line.startsWith("DA:")) {
|
|
6950
|
+
const [lineNumber, hits] = line.slice(3).split(",");
|
|
6951
|
+
const parsedLine = Number(lineNumber);
|
|
6952
|
+
const parsedHits = Number(hits);
|
|
6953
|
+
if (Number.isFinite(parsedLine) && Number.isFinite(parsedHits)) current.lines.set(parsedLine, parsedHits);
|
|
6954
|
+
continue;
|
|
6955
|
+
}
|
|
6956
|
+
if (line.startsWith("BRDA:")) {
|
|
6957
|
+
const parts = line.slice(5).split(",");
|
|
6958
|
+
const taken = parts[3] === "-" ? null : Number(parts[3]);
|
|
6959
|
+
current.branches.push(Number.isFinite(taken) ? taken : null);
|
|
6960
|
+
continue;
|
|
6961
|
+
}
|
|
6962
|
+
if (line.startsWith("FNDA:")) {
|
|
6963
|
+
const [hits, name] = line.slice(5).split(",");
|
|
6964
|
+
const parsedHits = Number(hits);
|
|
6965
|
+
if (name && Number.isFinite(parsedHits)) current.functions.set(name, parsedHits);
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
flush();
|
|
6969
|
+
return {
|
|
6970
|
+
status: "available",
|
|
6971
|
+
summary: aggregateSummaries(files.map((file) => file.summary)),
|
|
6972
|
+
files
|
|
6973
|
+
};
|
|
6974
|
+
}
|
|
6975
|
+
function metricFromHits(values) {
|
|
6976
|
+
const total = values.length;
|
|
6977
|
+
const covered = values.filter((value) => typeof value === "number" && value > 0).length;
|
|
6978
|
+
return makeMetric(covered, total);
|
|
6979
|
+
}
|
|
6980
|
+
function normalizeSummary(value) {
|
|
6981
|
+
const summary = {};
|
|
6982
|
+
if (!isRecord(value)) return summary;
|
|
6983
|
+
for (const metric of metricNames) {
|
|
6984
|
+
const raw = value[metric];
|
|
6985
|
+
if (!isRecord(raw)) continue;
|
|
6986
|
+
const total = Number(raw.total);
|
|
6987
|
+
const covered = Number(raw.covered);
|
|
6988
|
+
const skipped = Number(raw.skipped);
|
|
6989
|
+
const pct = raw.pct === "Unknown" || raw.pct === void 0 || raw.pct === null ? calculatePct(covered, total) : Number(raw.pct);
|
|
6990
|
+
if (!Number.isFinite(total) || !Number.isFinite(covered)) continue;
|
|
6991
|
+
summary[metric] = {
|
|
6992
|
+
covered,
|
|
6993
|
+
total,
|
|
6994
|
+
skipped: Number.isFinite(skipped) ? skipped : void 0,
|
|
6995
|
+
pct: Number.isFinite(pct) ? clampPct(pct) : calculatePct(covered, total)
|
|
6996
|
+
};
|
|
6997
|
+
}
|
|
6998
|
+
return summary;
|
|
6999
|
+
}
|
|
7000
|
+
function aggregateSummaries(summaries) {
|
|
7001
|
+
const aggregate = {};
|
|
7002
|
+
for (const metric of metricNames) {
|
|
7003
|
+
let covered = 0;
|
|
7004
|
+
let total = 0;
|
|
7005
|
+
let skipped = 0;
|
|
7006
|
+
let hasMetric = false;
|
|
7007
|
+
for (const summary of summaries) {
|
|
7008
|
+
const value = summary[metric];
|
|
7009
|
+
if (!value) continue;
|
|
7010
|
+
covered += value.covered;
|
|
7011
|
+
total += value.total;
|
|
7012
|
+
skipped += value.skipped ?? 0;
|
|
7013
|
+
hasMetric = true;
|
|
7014
|
+
}
|
|
7015
|
+
if (hasMetric) aggregate[metric] = { ...makeMetric(covered, total), skipped };
|
|
7016
|
+
}
|
|
7017
|
+
return aggregate;
|
|
7018
|
+
}
|
|
7019
|
+
function applyThresholds(report, thresholds) {
|
|
7020
|
+
const entries = Object.entries(thresholds);
|
|
7021
|
+
if (entries.length === 0) return report;
|
|
7022
|
+
const diagnostics = [...report.diagnostics ?? []];
|
|
7023
|
+
const applied = entries.map(([metric, minimum]) => {
|
|
7024
|
+
const actual = report.summary?.[metric]?.pct ?? null;
|
|
7025
|
+
const status = actual !== null && actual < minimum ? "warning" : "passed";
|
|
7026
|
+
if (status === "warning") {
|
|
7027
|
+
diagnostics.push({
|
|
7028
|
+
severity: "warning",
|
|
7029
|
+
code: "threshold-warning",
|
|
7030
|
+
message: `${metric} coverage is ${actual?.toFixed(1)}%, below the configured ${minimum}% threshold.`
|
|
7031
|
+
});
|
|
7032
|
+
}
|
|
7033
|
+
return { metric, minimum, actual, status };
|
|
7034
|
+
});
|
|
7035
|
+
return {
|
|
7036
|
+
...report,
|
|
7037
|
+
thresholds: applied,
|
|
7038
|
+
diagnostics: diagnostics.length > 0 ? diagnostics : void 0
|
|
7039
|
+
};
|
|
7040
|
+
}
|
|
7041
|
+
function findCandidates(rootDir, reportsDirectory, configuredPath) {
|
|
7042
|
+
const candidates = [];
|
|
7043
|
+
const add = (candidatePath, detected) => {
|
|
7044
|
+
if (!candidatePath) return;
|
|
7045
|
+
const resolved = resolvePath(rootDir, candidatePath);
|
|
7046
|
+
if (candidates.some((candidate) => candidate.path.toLowerCase() === resolved.toLowerCase())) return;
|
|
7047
|
+
candidates.push({ path: resolved, format: detectFormat(resolved), detected });
|
|
7048
|
+
};
|
|
7049
|
+
add(configuredPath, "configured");
|
|
7050
|
+
add(reportsDirectory ? path2__default.join(reportsDirectory, "coverage-summary.json") : void 0, "auto");
|
|
7051
|
+
add(reportsDirectory ? path2__default.join(reportsDirectory, "lcov.info") : void 0, "auto");
|
|
7052
|
+
add(path2__default.join(rootDir, "coverage", "coverage-summary.json"), "auto");
|
|
7053
|
+
add(path2__default.join(rootDir, "coverage", "lcov.info"), "auto");
|
|
7054
|
+
add(path2__default.join(process.cwd(), "coverage", "coverage-summary.json"), "auto");
|
|
7055
|
+
add(path2__default.join(process.cwd(), "coverage", "lcov.info"), "auto");
|
|
7056
|
+
return candidates.filter((candidate) => existsSync(candidate.path) || candidate.detected === "configured");
|
|
7057
|
+
}
|
|
7058
|
+
function detectFormat(filePath) {
|
|
7059
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
7060
|
+
if (normalized.endsWith("/coverage-summary.json") || normalized.endsWith("\\coverage-summary.json")) return "istanbul-json-summary";
|
|
7061
|
+
if (normalized.endsWith(".info") || normalized.endsWith(".lcov")) return "lcov";
|
|
7062
|
+
return "unsupported";
|
|
7063
|
+
}
|
|
7064
|
+
function diagnosticReport(code, severity, message, filePath) {
|
|
7065
|
+
return {
|
|
7066
|
+
status: severity === "info" ? "not-collected" : "invalid",
|
|
7067
|
+
diagnostics: [{ severity, code, message, path: filePath }]
|
|
7068
|
+
};
|
|
7069
|
+
}
|
|
7070
|
+
function normalizeCoveragePath(filePath, rootDir) {
|
|
7071
|
+
const absolute = path2__default.isAbsolute(filePath) ? path2__default.normalize(filePath) : path2__default.resolve(rootDir, filePath);
|
|
7072
|
+
let relative = path2__default.relative(rootDir, absolute);
|
|
7073
|
+
if (!relative || relative.startsWith("..")) relative = filePath;
|
|
7074
|
+
return relative.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
7075
|
+
}
|
|
7076
|
+
function makeMetric(covered, total) {
|
|
7077
|
+
return { covered, total, pct: calculatePct(covered, total) };
|
|
7078
|
+
}
|
|
7079
|
+
function calculatePct(covered, total) {
|
|
7080
|
+
if (!Number.isFinite(total) || total <= 0) return null;
|
|
7081
|
+
return clampPct(covered / total * 100);
|
|
7082
|
+
}
|
|
7083
|
+
function clampPct(value) {
|
|
7084
|
+
return Math.max(0, Math.min(100, Math.round(value * 10) / 10));
|
|
7085
|
+
}
|
|
7086
|
+
function isStale(fileStats, runStartedAt) {
|
|
7087
|
+
if (!runStartedAt || !Number.isFinite(runStartedAt)) return false;
|
|
7088
|
+
return fileStats.mtimeMs + 2e3 < runStartedAt;
|
|
7089
|
+
}
|
|
7090
|
+
function resolveThresholds(configured) {
|
|
7091
|
+
const thresholds = {};
|
|
7092
|
+
for (const metric of metricNames) {
|
|
7093
|
+
const fromOptions = configured?.[metric];
|
|
7094
|
+
const fromEnv = process.env[`LIVEDOC_COVERAGE_THRESHOLD_${metric.toUpperCase()}`];
|
|
7095
|
+
const parsed = Number(fromOptions ?? fromEnv);
|
|
7096
|
+
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 100) thresholds[metric] = parsed;
|
|
7097
|
+
}
|
|
7098
|
+
return thresholds;
|
|
7099
|
+
}
|
|
7100
|
+
function parseBoolean(value) {
|
|
7101
|
+
if (value === void 0) return void 0;
|
|
7102
|
+
const normalized = value.trim().toLowerCase();
|
|
7103
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
7104
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
7105
|
+
return void 0;
|
|
7106
|
+
}
|
|
7107
|
+
function firstNonEmpty(...values) {
|
|
7108
|
+
for (const value of values) {
|
|
7109
|
+
const trimmed = value?.trim();
|
|
7110
|
+
if (trimmed) return trimmed;
|
|
7111
|
+
}
|
|
7112
|
+
return void 0;
|
|
7113
|
+
}
|
|
7114
|
+
function resolvePath(rootDir, value) {
|
|
7115
|
+
return path2__default.isAbsolute(value) ? path2__default.normalize(value) : path2__default.resolve(rootDir, value);
|
|
7116
|
+
}
|
|
7117
|
+
function isRecord(value) {
|
|
7118
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7119
|
+
}
|
|
7120
|
+
function isIstanbulCoverageMap(value) {
|
|
7121
|
+
if (!isRecord(value)) return false;
|
|
7122
|
+
return typeof value.getCoverageSummary === "function" && typeof value.files === "function" && typeof value.fileCoverageFor === "function";
|
|
7123
|
+
}
|
|
6743
7124
|
|
|
6744
7125
|
// _src/app/reporter/LiveDocViewerReporterV1.ts
|
|
6745
7126
|
var LiveDocViewerReporter = class {
|
|
6746
7127
|
options;
|
|
7128
|
+
hasExplicitRunType;
|
|
6747
7129
|
constructor(options) {
|
|
7130
|
+
this.hasExplicitRunType = options?.runType !== void 0;
|
|
6748
7131
|
this.options = {
|
|
6749
7132
|
server: options?.server || "http://localhost:3100",
|
|
6750
|
-
project: options?.project || "
|
|
7133
|
+
project: options?.project || "livedoc",
|
|
6751
7134
|
environment: options?.environment || "local",
|
|
7135
|
+
runType: this.parseRunType(options?.runType ?? process.env.LIVEDOC_RUN_TYPE),
|
|
6752
7136
|
timeout: options?.timeout || 1e4,
|
|
6753
|
-
silent: options?.silent ?? true
|
|
7137
|
+
silent: options?.silent ?? true,
|
|
7138
|
+
coverage: options?.coverage || {}
|
|
6754
7139
|
};
|
|
6755
7140
|
}
|
|
6756
7141
|
async execute(results, rawOptions) {
|
|
@@ -6776,7 +7161,7 @@ var LiveDocViewerReporter = class {
|
|
|
6776
7161
|
const testCase = this.buildSuiteTestCase(suite, pathContext);
|
|
6777
7162
|
await this.upsertTestCase(runId, testCase);
|
|
6778
7163
|
}
|
|
6779
|
-
await this.completeRun(runId, results);
|
|
7164
|
+
await this.completeRun(runId, results, rawOptions);
|
|
6780
7165
|
} catch (error) {
|
|
6781
7166
|
if (!this.options.silent) {
|
|
6782
7167
|
console.error("LiveDocViewerReporter error:", error);
|
|
@@ -6789,6 +7174,11 @@ var LiveDocViewerReporter = class {
|
|
|
6789
7174
|
*/
|
|
6790
7175
|
buildTestRun(results, rawOptions) {
|
|
6791
7176
|
this.applyRawOptions(rawOptions);
|
|
7177
|
+
if (this.options.runType === "partial") {
|
|
7178
|
+
throw new Error(
|
|
7179
|
+
"partial-export-unsupported: Partial runs require server history and cannot be exported directly."
|
|
7180
|
+
);
|
|
7181
|
+
}
|
|
6792
7182
|
const pathContext = this.buildPathContext(results);
|
|
6793
7183
|
const documents = [];
|
|
6794
7184
|
for (const feature of results.features) {
|
|
@@ -6802,6 +7192,7 @@ var LiveDocViewerReporter = class {
|
|
|
6802
7192
|
}
|
|
6803
7193
|
const { summary, duration } = this.calculateSummary(results);
|
|
6804
7194
|
const status = this.calculateOverallStatus(results);
|
|
7195
|
+
const coverage = this.collectCoverage(rawOptions);
|
|
6805
7196
|
return {
|
|
6806
7197
|
protocolVersion: "1.0",
|
|
6807
7198
|
runId: randomUUID(),
|
|
@@ -6812,7 +7203,8 @@ var LiveDocViewerReporter = class {
|
|
|
6812
7203
|
duration,
|
|
6813
7204
|
status,
|
|
6814
7205
|
summary,
|
|
6815
|
-
documents
|
|
7206
|
+
documents,
|
|
7207
|
+
...coverage ? { coverage } : {}
|
|
6816
7208
|
};
|
|
6817
7209
|
}
|
|
6818
7210
|
async startRunSession(rawOptions) {
|
|
@@ -6851,22 +7243,44 @@ var LiveDocViewerReporter = class {
|
|
|
6851
7243
|
if (rawOptions?.["viewer-environment"]) {
|
|
6852
7244
|
this.options.environment = rawOptions["viewer-environment"];
|
|
6853
7245
|
}
|
|
7246
|
+
if (!this.hasExplicitRunType && rawOptions?.["viewer-run-type"]) {
|
|
7247
|
+
this.options.runType = this.parseRunType(rawOptions["viewer-run-type"]);
|
|
7248
|
+
}
|
|
6854
7249
|
if (rawOptions?.["viewer-timeout"] !== void 0) {
|
|
6855
7250
|
const parsed = Number(rawOptions["viewer-timeout"]);
|
|
6856
7251
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
6857
7252
|
this.options.timeout = parsed;
|
|
6858
7253
|
}
|
|
6859
7254
|
}
|
|
7255
|
+
const coverage = rawOptions?.coverage;
|
|
7256
|
+
if (coverage && typeof coverage === "object") {
|
|
7257
|
+
this.options.coverage = {
|
|
7258
|
+
...this.options.coverage,
|
|
7259
|
+
enabled: coverage.enabled ?? this.options.coverage.enabled,
|
|
7260
|
+
path: coverage.path ?? coverage.artifactPath ?? this.options.coverage.path,
|
|
7261
|
+
thresholds: coverage.thresholds ?? this.options.coverage.thresholds
|
|
7262
|
+
};
|
|
7263
|
+
}
|
|
7264
|
+
if (rawOptions?.["coverage-path"]) {
|
|
7265
|
+
this.options.coverage.path = String(rawOptions["coverage-path"]);
|
|
7266
|
+
}
|
|
6860
7267
|
}
|
|
6861
7268
|
async startRun() {
|
|
6862
7269
|
const request = {
|
|
6863
7270
|
project: this.options.project,
|
|
6864
7271
|
environment: this.options.environment,
|
|
6865
|
-
framework: "vitest"
|
|
7272
|
+
framework: "vitest",
|
|
7273
|
+
runType: this.options.runType
|
|
6866
7274
|
};
|
|
6867
7275
|
const response = await this.post("/api/v1/runs/start", request);
|
|
6868
7276
|
return response?.runId || null;
|
|
6869
7277
|
}
|
|
7278
|
+
parseRunType(value) {
|
|
7279
|
+
if (value === void 0 || value === null || value === "") return "full";
|
|
7280
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : value;
|
|
7281
|
+
if (normalized === "full" || normalized === "partial") return normalized;
|
|
7282
|
+
throw new Error(`Invalid LiveDoc run type '${String(value)}'. Expected 'full' or 'partial'.`);
|
|
7283
|
+
}
|
|
6870
7284
|
async upsertTestCase(runId, testCase) {
|
|
6871
7285
|
await this.post(`/api/v1/runs/${runId}/testcases`, { testCase });
|
|
6872
7286
|
}
|
|
@@ -6881,7 +7295,7 @@ var LiveDocViewerReporter = class {
|
|
|
6881
7295
|
for (const s of results.suites || []) {
|
|
6882
7296
|
if (s?.filename) filenames.push(s.filename);
|
|
6883
7297
|
}
|
|
6884
|
-
const abs = filenames.filter(Boolean).map((p) =>
|
|
7298
|
+
const abs = filenames.filter(Boolean).map((p) => path2.isAbsolute(p) ? p : path2.resolve(process.cwd(), p)).map((p) => p.replace(/\\/g, "/"));
|
|
6885
7299
|
const rootPath = this.findCommonRootPath(abs);
|
|
6886
7300
|
return { rootPath };
|
|
6887
7301
|
}
|
|
@@ -6903,7 +7317,7 @@ var LiveDocViewerReporter = class {
|
|
|
6903
7317
|
}
|
|
6904
7318
|
buildFileInfo(filename, rootPath) {
|
|
6905
7319
|
const raw = filename || "";
|
|
6906
|
-
const abs = raw ?
|
|
7320
|
+
const abs = raw ? path2.isAbsolute(raw) ? raw : path2.resolve(process.cwd(), raw) : "";
|
|
6907
7321
|
const normalized = abs.replace(/\\/g, "/");
|
|
6908
7322
|
const root = (rootPath || "").replace(/\\/g, "/").replace(/\/+$/g, "");
|
|
6909
7323
|
if (!normalized) {
|
|
@@ -7457,16 +7871,31 @@ Actual: ${step.exception.actual}` : void 0)
|
|
|
7457
7871
|
if (value instanceof Date) return { value: value.toISOString(), type: "date" };
|
|
7458
7872
|
return { value, type: "object" };
|
|
7459
7873
|
}
|
|
7460
|
-
async completeRun(runId, results) {
|
|
7874
|
+
async completeRun(runId, results, rawOptions) {
|
|
7461
7875
|
const { summary, duration } = this.calculateSummary(results);
|
|
7462
7876
|
const overallStatus = this.calculateOverallStatus(results);
|
|
7877
|
+
const coverage = this.collectCoverage(rawOptions);
|
|
7463
7878
|
const request = {
|
|
7464
7879
|
status: overallStatus,
|
|
7465
7880
|
duration,
|
|
7466
|
-
summary
|
|
7881
|
+
summary,
|
|
7882
|
+
...coverage ? { coverage } : {}
|
|
7467
7883
|
};
|
|
7468
7884
|
await this.post(`/api/v1/runs/${runId}/complete`, request);
|
|
7469
7885
|
}
|
|
7886
|
+
collectCoverage(rawOptions) {
|
|
7887
|
+
const context = rawOptions?.coverageContext ?? {};
|
|
7888
|
+
const options = {
|
|
7889
|
+
enabled: this.options.coverage.enabled ?? context.enabled,
|
|
7890
|
+
coverageMap: context.coverageMap,
|
|
7891
|
+
artifactPath: this.options.coverage.path ?? context.artifactPath,
|
|
7892
|
+
rootDir: context.rootDir,
|
|
7893
|
+
reportsDirectory: context.reportsDirectory,
|
|
7894
|
+
runStartedAt: context.runStartedAt,
|
|
7895
|
+
thresholds: this.options.coverage.thresholds ?? context.thresholds
|
|
7896
|
+
};
|
|
7897
|
+
return collectCoverageReport(options);
|
|
7898
|
+
}
|
|
7470
7899
|
mapStatus(status) {
|
|
7471
7900
|
switch (status) {
|
|
7472
7901
|
case "pass" /* pass */:
|
|
@@ -7838,6 +8267,7 @@ var LiveDocSpecReporter = class {
|
|
|
7838
8267
|
liveDocSpec;
|
|
7839
8268
|
options;
|
|
7840
8269
|
exportConfig = null;
|
|
8270
|
+
coverageContext = { runStartedAt: Date.now() };
|
|
7841
8271
|
streamEnabled = true;
|
|
7842
8272
|
taskById = /* @__PURE__ */ new Map();
|
|
7843
8273
|
streamedStates = /* @__PURE__ */ new Map();
|
|
@@ -7864,6 +8294,7 @@ var LiveDocSpecReporter = class {
|
|
|
7864
8294
|
livedoc.options.publish.server = options.publish.server ?? livedoc.options.publish.server;
|
|
7865
8295
|
livedoc.options.publish.project = options.publish.project ?? livedoc.options.publish.project;
|
|
7866
8296
|
livedoc.options.publish.environment = options.publish.environment ?? livedoc.options.publish.environment;
|
|
8297
|
+
livedoc.options.publish.runType = options.publish.runType ?? livedoc.options.publish.runType;
|
|
7867
8298
|
}
|
|
7868
8299
|
if (options.export) {
|
|
7869
8300
|
this.exportConfig = {
|
|
@@ -7880,22 +8311,14 @@ var LiveDocSpecReporter = class {
|
|
|
7880
8311
|
this.setLiveDocOptions(this.options);
|
|
7881
8312
|
}
|
|
7882
8313
|
async onInit(ctx) {
|
|
8314
|
+
this.captureCoverageContext(ctx);
|
|
7883
8315
|
this.liveDocSpec.executionStart();
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
8316
|
+
const envServerUrl = this.applyPublishEnvironmentOverrides();
|
|
8317
|
+
if (!envServerUrl && !livedoc.options.publish.enabled) {
|
|
8318
|
+
const serverInfo = await this.discoverLiveDocServer();
|
|
8319
|
+
if (serverInfo) {
|
|
7887
8320
|
livedoc.options.publish.enabled = true;
|
|
7888
|
-
livedoc.options.publish.server =
|
|
7889
|
-
} else {
|
|
7890
|
-
try {
|
|
7891
|
-
const { discoverServer } = await import('@swedevtools/livedoc-server');
|
|
7892
|
-
const serverInfo = await discoverServer();
|
|
7893
|
-
if (serverInfo) {
|
|
7894
|
-
livedoc.options.publish.enabled = true;
|
|
7895
|
-
livedoc.options.publish.server = serverInfo.url;
|
|
7896
|
-
}
|
|
7897
|
-
} catch {
|
|
7898
|
-
}
|
|
8321
|
+
livedoc.options.publish.server = serverInfo.url;
|
|
7899
8322
|
}
|
|
7900
8323
|
}
|
|
7901
8324
|
if (livedoc.options.publish.enabled) {
|
|
@@ -7907,6 +8330,102 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
7907
8330
|
`);
|
|
7908
8331
|
}
|
|
7909
8332
|
}
|
|
8333
|
+
onCoverage(coverageMap) {
|
|
8334
|
+
this.coverageContext.coverageMap = coverageMap;
|
|
8335
|
+
}
|
|
8336
|
+
captureCoverageContext(ctx) {
|
|
8337
|
+
const config = ctx?.config ?? {};
|
|
8338
|
+
const coverage = config.coverage ?? {};
|
|
8339
|
+
const rawOptions = this.options.rawOptions ?? {};
|
|
8340
|
+
const reporterCoverage = rawOptions.coverage ?? {};
|
|
8341
|
+
const rootDir = config.root || process.cwd();
|
|
8342
|
+
const reportsDirectory = coverage.reportsDirectory ? resolve(rootDir, coverage.reportsDirectory) : void 0;
|
|
8343
|
+
this.coverageContext = {
|
|
8344
|
+
enabled: Boolean(coverage.enabled) || Boolean(reporterCoverage.enabled),
|
|
8345
|
+
artifactPath: reporterCoverage.path ?? reporterCoverage.artifactPath,
|
|
8346
|
+
rootDir,
|
|
8347
|
+
reportsDirectory,
|
|
8348
|
+
runStartedAt: Date.now(),
|
|
8349
|
+
thresholds: reporterCoverage.thresholds
|
|
8350
|
+
};
|
|
8351
|
+
}
|
|
8352
|
+
applyPublishEnvironmentOverrides() {
|
|
8353
|
+
const envServerUrl = this.firstEnvironmentValue(
|
|
8354
|
+
"LIVEDOC_SERVER_URL",
|
|
8355
|
+
"LIVEDOC_PUBLISH_SERVER",
|
|
8356
|
+
"LIVEDOC_VIEWER_SERVER"
|
|
8357
|
+
);
|
|
8358
|
+
const envProject = this.firstEnvironmentValue(
|
|
8359
|
+
"LIVEDOC_PROJECT",
|
|
8360
|
+
"LIVEDOC_PUBLISH_PROJECT",
|
|
8361
|
+
"LIVEDOC_VIEWER_PROJECT"
|
|
8362
|
+
);
|
|
8363
|
+
const envEnvironment = this.firstEnvironmentValue(
|
|
8364
|
+
"LIVEDOC_ENVIRONMENT",
|
|
8365
|
+
"LIVEDOC_PUBLISH_ENV",
|
|
8366
|
+
"LIVEDOC_VIEWER_ENV"
|
|
8367
|
+
);
|
|
8368
|
+
const envRunType = this.firstEnvironmentValue("LIVEDOC_RUN_TYPE");
|
|
8369
|
+
if (envProject) {
|
|
8370
|
+
livedoc.options.publish.project = envProject;
|
|
8371
|
+
}
|
|
8372
|
+
if (envEnvironment) {
|
|
8373
|
+
livedoc.options.publish.environment = envEnvironment;
|
|
8374
|
+
}
|
|
8375
|
+
if (envRunType) {
|
|
8376
|
+
const normalizedRunType = envRunType.toLowerCase();
|
|
8377
|
+
if (normalizedRunType !== "full" && normalizedRunType !== "partial") {
|
|
8378
|
+
throw new Error(
|
|
8379
|
+
`Invalid LIVEDOC_RUN_TYPE value '${envRunType}'. Expected 'full' or 'partial'.`
|
|
8380
|
+
);
|
|
8381
|
+
}
|
|
8382
|
+
livedoc.options.publish.runType = normalizedRunType;
|
|
8383
|
+
}
|
|
8384
|
+
if (envServerUrl) {
|
|
8385
|
+
livedoc.options.publish.enabled = true;
|
|
8386
|
+
livedoc.options.publish.server = envServerUrl;
|
|
8387
|
+
}
|
|
8388
|
+
return envServerUrl;
|
|
8389
|
+
}
|
|
8390
|
+
firstEnvironmentValue(...names) {
|
|
8391
|
+
for (const name of names) {
|
|
8392
|
+
const value = process.env[name]?.trim();
|
|
8393
|
+
if (value) return value;
|
|
8394
|
+
}
|
|
8395
|
+
return void 0;
|
|
8396
|
+
}
|
|
8397
|
+
async discoverLiveDocServer() {
|
|
8398
|
+
try {
|
|
8399
|
+
const { discoverServer } = await import('@swedevtools/livedoc-server');
|
|
8400
|
+
const serverInfo = await discoverServer();
|
|
8401
|
+
if (serverInfo) return serverInfo;
|
|
8402
|
+
} catch {
|
|
8403
|
+
}
|
|
8404
|
+
return await this.discoverDefaultLocalServer();
|
|
8405
|
+
}
|
|
8406
|
+
async discoverDefaultLocalServer() {
|
|
8407
|
+
const defaultPort = 3100;
|
|
8408
|
+
const defaultUrl = `http://localhost:${defaultPort}`;
|
|
8409
|
+
if (await this.isLiveDocServerHealthy(defaultUrl)) {
|
|
8410
|
+
return { url: defaultUrl, port: defaultPort };
|
|
8411
|
+
}
|
|
8412
|
+
return null;
|
|
8413
|
+
}
|
|
8414
|
+
async isLiveDocServerHealthy(serverUrl) {
|
|
8415
|
+
const controller = new AbortController();
|
|
8416
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
8417
|
+
try {
|
|
8418
|
+
const response = await fetch(`${serverUrl}/api/health`, {
|
|
8419
|
+
headers: { "Connection": "close" },
|
|
8420
|
+
signal: controller.signal
|
|
8421
|
+
});
|
|
8422
|
+
return response.ok;
|
|
8423
|
+
} catch {
|
|
8424
|
+
return false;
|
|
8425
|
+
} finally {
|
|
8426
|
+
clearTimeout(timeout);
|
|
8427
|
+
}
|
|
8428
|
+
}
|
|
7910
8429
|
onCollected(files) {
|
|
7911
8430
|
if (!this.streamEnabled) return;
|
|
7912
8431
|
this.taskById.clear();
|
|
@@ -7970,30 +8489,34 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
7970
8489
|
server: publishOptions.server,
|
|
7971
8490
|
project: publishOptions.project,
|
|
7972
8491
|
environment: publishOptions.environment,
|
|
8492
|
+
runType: publishOptions.runType,
|
|
7973
8493
|
silent: false
|
|
7974
8494
|
});
|
|
7975
|
-
const
|
|
7976
|
-
if (!
|
|
7977
|
-
|
|
8495
|
+
const rawOptions2 = this.options.rawOptions || {};
|
|
8496
|
+
if (!rawOptions2.postReporters) {
|
|
8497
|
+
rawOptions2.postReporters = [];
|
|
7978
8498
|
}
|
|
7979
|
-
|
|
8499
|
+
rawOptions2.postReporters.push(viewerReporter);
|
|
7980
8500
|
}
|
|
7981
|
-
|
|
8501
|
+
const rawOptions = this.options.rawOptions || {};
|
|
8502
|
+
rawOptions.coverageContext = this.coverageContext;
|
|
8503
|
+
await this.liveDocSpec.executionEnd(results, rawOptions);
|
|
7982
8504
|
if (this.exportConfig) {
|
|
7983
|
-
this.exportTestRunJson(results);
|
|
8505
|
+
this.exportTestRunJson(results, rawOptions);
|
|
7984
8506
|
}
|
|
7985
8507
|
}
|
|
7986
|
-
exportTestRunJson(results) {
|
|
8508
|
+
exportTestRunJson(results, rawOptions) {
|
|
7987
8509
|
const exportConfig = this.exportConfig;
|
|
7988
8510
|
const outputPath = resolve(exportConfig.output);
|
|
7989
|
-
const project = exportConfig.project || livedoc.options.publish.project || "
|
|
8511
|
+
const project = exportConfig.project || livedoc.options.publish.project || "livedoc";
|
|
7990
8512
|
const environment = exportConfig.environment || livedoc.options.publish.environment || (process.env.CI ? "ci" : "local");
|
|
7991
8513
|
const converter = new LiveDocViewerReporter({
|
|
7992
8514
|
project,
|
|
7993
8515
|
environment,
|
|
8516
|
+
runType: livedoc.options.publish.runType,
|
|
7994
8517
|
silent: true
|
|
7995
8518
|
});
|
|
7996
|
-
const testRun = converter.buildTestRun(results);
|
|
8519
|
+
const testRun = converter.buildTestRun(results, rawOptions);
|
|
7997
8520
|
try {
|
|
7998
8521
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
7999
8522
|
const json = JSON.stringify(testRun, null, 2);
|
|
@@ -8016,9 +8539,15 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8016
8539
|
for (const suite of file.tasks || []) {
|
|
8017
8540
|
if (suite.type === "suite") {
|
|
8018
8541
|
if (suite.name.startsWith("Specification:")) {
|
|
8019
|
-
|
|
8542
|
+
const specification = this.buildSpecificationFromSuite(suite, file.filepath);
|
|
8543
|
+
if (specification.rules.length > 0) {
|
|
8544
|
+
specifications.push(specification);
|
|
8545
|
+
}
|
|
8020
8546
|
} else if (suite.name.startsWith("Feature:")) {
|
|
8021
|
-
|
|
8547
|
+
const feature = this.buildFeatureFromSuite(suite, file.filepath);
|
|
8548
|
+
if (feature.scenarios.length > 0) {
|
|
8549
|
+
features.push(feature);
|
|
8550
|
+
}
|
|
8022
8551
|
} else {
|
|
8023
8552
|
suites.push(this.buildVitestSuiteFromTask(suite, file.filepath));
|
|
8024
8553
|
}
|
|
@@ -8063,6 +8592,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8063
8592
|
}
|
|
8064
8593
|
for (const task of tasks) {
|
|
8065
8594
|
if (task === backgroundSuite) continue;
|
|
8595
|
+
if (!this.hasUnfilteredLiveDocLeaf(task)) continue;
|
|
8066
8596
|
if (typeof task.name === "string" && task.name.startsWith("Scenario:")) {
|
|
8067
8597
|
const exampleSuites = (task.tasks || []).filter(
|
|
8068
8598
|
(t) => t.type === "suite" && t.name.startsWith("Example ")
|
|
@@ -8086,7 +8616,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8086
8616
|
scenarioOutline.description = parsed.description;
|
|
8087
8617
|
scenarioOutline.tags = parsed.tags;
|
|
8088
8618
|
const exampleSuites = (suite.tasks || []).filter(
|
|
8089
|
-
(t) => t.type === "suite" && t.name.startsWith("Example ")
|
|
8619
|
+
(t) => t.type === "suite" && t.name.startsWith("Example ") && this.hasUnfilteredLiveDocLeaf(t)
|
|
8090
8620
|
);
|
|
8091
8621
|
const firstExampleSuite = exampleSuites[0];
|
|
8092
8622
|
const firstExampleLiveDoc = this.findFirstLiveDocStepMetaInSuite(firstExampleSuite);
|
|
@@ -8138,7 +8668,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8138
8668
|
background.tags = parsed.tags;
|
|
8139
8669
|
const forcePending = suite?.mode === "skip" || suite?.mode === "todo";
|
|
8140
8670
|
for (const task of suite.tasks || []) {
|
|
8141
|
-
if (task.type === "test") {
|
|
8671
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
8142
8672
|
const step = this.buildStepFromTest(task, background, forcePending);
|
|
8143
8673
|
background.addStep(step);
|
|
8144
8674
|
}
|
|
@@ -8153,7 +8683,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8153
8683
|
scenario.tags = parsed.tags;
|
|
8154
8684
|
const forcePending = suite?.mode === "skip" || suite?.mode === "todo";
|
|
8155
8685
|
for (const task of suite.tasks || []) {
|
|
8156
|
-
if (task.type === "test") {
|
|
8686
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
8157
8687
|
const step = this.buildStepFromTest(task, scenario, forcePending);
|
|
8158
8688
|
scenario.addStep(step);
|
|
8159
8689
|
}
|
|
@@ -8177,7 +8707,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8177
8707
|
example.example = this.sanitizeExampleKeys(metaExampleValues);
|
|
8178
8708
|
example.exampleRaw = example.example;
|
|
8179
8709
|
for (const task of suite.tasks || []) {
|
|
8180
|
-
if (task.type === "test") {
|
|
8710
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
8181
8711
|
const step = this.buildStepFromTest(task, example, forcePending);
|
|
8182
8712
|
example.addStep(step);
|
|
8183
8713
|
}
|
|
@@ -8191,9 +8721,20 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8191
8721
|
if (!livedoc2 || typeof livedoc2 !== "object") return void 0;
|
|
8192
8722
|
return livedoc2;
|
|
8193
8723
|
}
|
|
8724
|
+
isFilteredOutTask(task) {
|
|
8725
|
+
return this.getLiveDocMetaFromTask(task)?.filter?.filteredOut === true;
|
|
8726
|
+
}
|
|
8727
|
+
hasUnfilteredLiveDocLeaf(task) {
|
|
8728
|
+
if (task?.type === "test") {
|
|
8729
|
+
const kind = this.getLiveDocMetaFromTask(task)?.kind;
|
|
8730
|
+
return (kind === "step" || kind === "rule" || kind === "ruleExample") && !this.isFilteredOutTask(task);
|
|
8731
|
+
}
|
|
8732
|
+
return Array.isArray(task?.tasks) && task.tasks.some((child) => this.hasUnfilteredLiveDocLeaf(child));
|
|
8733
|
+
}
|
|
8194
8734
|
findFirstLiveDocStepMetaInSuite(suite) {
|
|
8195
8735
|
const tasks = suite?.tasks || [];
|
|
8196
8736
|
for (const task of tasks) {
|
|
8737
|
+
if (this.isFilteredOutTask(task)) continue;
|
|
8197
8738
|
if (task?.type !== "test") continue;
|
|
8198
8739
|
const livedoc2 = this.getLiveDocMetaFromTask(task);
|
|
8199
8740
|
if (livedoc2?.kind === "step") return livedoc2;
|
|
@@ -8381,8 +8922,10 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8381
8922
|
specification.tags = parsed.tags;
|
|
8382
8923
|
specification.filename = filepath;
|
|
8383
8924
|
for (const task of suite.tasks || []) {
|
|
8925
|
+
if (!this.hasUnfilteredLiveDocLeaf(task)) continue;
|
|
8384
8926
|
if (task.type === "suite") {
|
|
8385
8927
|
const exampleTests = (task.tasks || []).filter((t) => {
|
|
8928
|
+
if (this.isFilteredOutTask(t)) return false;
|
|
8386
8929
|
if (t.type !== "test") return false;
|
|
8387
8930
|
const meta = this.getLiveDocMetaFromTask(t);
|
|
8388
8931
|
return meta?.kind === "ruleExample";
|
|
@@ -8391,6 +8934,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8391
8934
|
const ruleOutline = this.buildRuleOutlineFromSuite(task, specification);
|
|
8392
8935
|
specification.rules.push(ruleOutline);
|
|
8393
8936
|
}
|
|
8937
|
+
if (this.isFilteredOutTask(task)) continue;
|
|
8394
8938
|
} else if (task.type === "test") {
|
|
8395
8939
|
if (task.name.startsWith("Rule:")) {
|
|
8396
8940
|
const rule = this.buildRuleFromTest(task, specification);
|
|
@@ -8439,6 +8983,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
8439
8983
|
buildRuleOutlineFromSuite(suite, specification) {
|
|
8440
8984
|
const ruleOutline = new RuleOutline(specification);
|
|
8441
8985
|
const exampleTests = (suite.tasks || []).filter((t) => {
|
|
8986
|
+
if (this.isFilteredOutTask(t)) return false;
|
|
8442
8987
|
if (t.type !== "test") return false;
|
|
8443
8988
|
const meta = this.getLiveDocMetaFromTask(t);
|
|
8444
8989
|
return meta?.kind === "ruleExample";
|