@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +20 -20
  3. package/README.md +160 -95
  4. package/dist/{RuleContext-BZhuy-zS.d.cts → RuleContext-DQ8o_n1D.d.ts} +62 -62
  5. package/dist/globals.d.ts +99 -99
  6. package/dist/{index-Blmp569T.d.cts → index-sbV15ohX.d.ts} +21 -2
  7. package/dist/index.d.ts +7 -5
  8. package/dist/index.js +1062 -159
  9. package/dist/reporter/index.d.ts +2 -2
  10. package/dist/reporter/index.js +629 -84
  11. package/package.json +14 -12
  12. package/tools/livedoc-setup.mjs +172 -164
  13. package/tools/skills/SKILL.md +339 -244
  14. package/tools/skills/VALIDATION.md +37 -29
  15. package/tools/skills/examples/routing.md +75 -60
  16. package/tools/skills/resources/anti-patterns.md +19 -0
  17. package/tools/skills/resources/bdd-features.md +231 -231
  18. package/tools/skills/resources/partial-testing.md +77 -0
  19. package/tools/skills/resources/playwright.md +148 -148
  20. package/tools/skills/resources/reporter-config.md +213 -163
  21. package/tools/skills/resources/specifications.md +159 -159
  22. package/tools/skills/resources/test-strategy.md +103 -0
  23. package/tools/skills/resources/web-testing.md +62 -0
  24. package/dist/RuleContext-BZhuy-zS.d.ts +0 -206
  25. package/dist/globals.cjs +0 -2
  26. package/dist/globals.d.cts +0 -104
  27. package/dist/index-CysiWbtk.d.ts +0 -687
  28. package/dist/index.cjs +0 -10024
  29. package/dist/index.d.cts +0 -291
  30. package/dist/playwright/index.cjs +0 -103
  31. package/dist/playwright/index.d.cts +0 -129
  32. package/dist/reporter/index.cjs +0 -8676
  33. package/dist/reporter/index.d.cts +0 -7
  34. package/dist/setup.cjs +0 -14
  35. package/dist/setup.d.cts +0 -2
@@ -1,7 +1,7 @@
1
1
  import chalk, { Chalk } from 'chalk';
2
- import { existsSync, unlinkSync, appendFileSync, mkdirSync, writeFileSync, promises } from 'fs';
3
- import * as path from 'path';
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 path.dirname(strs[0]);
1290
+ return path2.dirname(strs[0]);
1291
1291
  for (let i = 0; i < strs.length; i++) {
1292
- strs[i] = path.dirname(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 = path.parse(stripPath).dir;
1331
- if (dirPath.startsWith(path.sep)) {
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: path3, errorMaps, issueData } = params;
2901
- const fullPath = [...path3, ...issueData.path || []];
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, path3, key) {
3026
+ constructor(parent, value, path4, key) {
3018
3027
  this._cachedPath = [];
3019
3028
  this.parent = parent;
3020
3029
  this.data = value;
3021
- this._path = path3;
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: path3, title, kind, parentId, keyword, index } = params;
6482
+ const { project, path: path4, title, kind, parentId, keyword, index } = params;
6474
6483
  if (!parentId) {
6475
- return simpleHash(`${project}:${path3 || ""}:${title}`);
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
- sessionId: external_exports.string().optional(),
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
- sessionId: external_exports.string(),
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 V1WsSessionUpdatedSchema = external_exports.object({
6727
- type: external_exports.literal("session:v1:updated"),
6728
- sessionId: external_exports.string(),
6729
- project: external_exports.string(),
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
- V1WsSessionUpdatedSchema
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 || "default",
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) => path.isAbsolute(p) ? p : path.resolve(process.cwd(), p)).map((p) => p.replace(/\\/g, "/"));
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 ? path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), 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
- if (!livedoc.options.publish.enabled) {
7885
- const envServerUrl = process.env.LIVEDOC_SERVER_URL || process.env.LIVEDOC_PUBLISH_SERVER;
7886
- if (envServerUrl) {
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 = envServerUrl;
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 rawOptions = this.options.rawOptions || {};
7976
- if (!rawOptions.postReporters) {
7977
- rawOptions.postReporters = [];
8495
+ const rawOptions2 = this.options.rawOptions || {};
8496
+ if (!rawOptions2.postReporters) {
8497
+ rawOptions2.postReporters = [];
7978
8498
  }
7979
- rawOptions.postReporters.push(viewerReporter);
8499
+ rawOptions2.postReporters.push(viewerReporter);
7980
8500
  }
7981
- await this.liveDocSpec.executionEnd(results, this.options.rawOptions);
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 || "default";
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
- specifications.push(this.buildSpecificationFromSuite(suite, file.filepath));
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
- features.push(this.buildFeatureFromSuite(suite, file.filepath));
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";