@democratize-quality/qualitylens-core 0.1.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 (85) hide show
  1. package/dist/core/config.d.mts +26 -0
  2. package/dist/core/config.d.ts +26 -0
  3. package/dist/core/config.js +139 -0
  4. package/dist/core/config.js.map +1 -0
  5. package/dist/core/config.mjs +104 -0
  6. package/dist/core/config.mjs.map +1 -0
  7. package/dist/core/detector.d.mts +43 -0
  8. package/dist/core/detector.d.ts +43 -0
  9. package/dist/core/detector.js +431 -0
  10. package/dist/core/detector.js.map +1 -0
  11. package/dist/core/detector.mjs +395 -0
  12. package/dist/core/detector.mjs.map +1 -0
  13. package/dist/core/engine.d.mts +29 -0
  14. package/dist/core/engine.d.ts +29 -0
  15. package/dist/core/engine.js +151 -0
  16. package/dist/core/engine.js.map +1 -0
  17. package/dist/core/engine.mjs +126 -0
  18. package/dist/core/engine.mjs.map +1 -0
  19. package/dist/core/types.d.mts +109 -0
  20. package/dist/core/types.d.ts +109 -0
  21. package/dist/core/types.js +19 -0
  22. package/dist/core/types.js.map +1 -0
  23. package/dist/core/types.mjs +1 -0
  24. package/dist/core/types.mjs.map +1 -0
  25. package/dist/matchers/area.matcher.d.mts +38 -0
  26. package/dist/matchers/area.matcher.d.ts +38 -0
  27. package/dist/matchers/area.matcher.js +102 -0
  28. package/dist/matchers/area.matcher.js.map +1 -0
  29. package/dist/matchers/area.matcher.mjs +77 -0
  30. package/dist/matchers/area.matcher.mjs.map +1 -0
  31. package/dist/matchers/fuzzy.matcher.d.mts +83 -0
  32. package/dist/matchers/fuzzy.matcher.d.ts +83 -0
  33. package/dist/matchers/fuzzy.matcher.js +161 -0
  34. package/dist/matchers/fuzzy.matcher.js.map +1 -0
  35. package/dist/matchers/fuzzy.matcher.mjs +136 -0
  36. package/dist/matchers/fuzzy.matcher.mjs.map +1 -0
  37. package/dist/reporters/base.reporter.d.mts +19 -0
  38. package/dist/reporters/base.reporter.d.ts +19 -0
  39. package/dist/reporters/base.reporter.js +32 -0
  40. package/dist/reporters/base.reporter.js.map +1 -0
  41. package/dist/reporters/base.reporter.mjs +7 -0
  42. package/dist/reporters/base.reporter.mjs.map +1 -0
  43. package/dist/reporters/console.reporter.d.mts +16 -0
  44. package/dist/reporters/console.reporter.d.ts +16 -0
  45. package/dist/reporters/console.reporter.js +130 -0
  46. package/dist/reporters/console.reporter.js.map +1 -0
  47. package/dist/reporters/console.reporter.mjs +95 -0
  48. package/dist/reporters/console.reporter.mjs.map +1 -0
  49. package/dist/sources/base.source.d.mts +22 -0
  50. package/dist/sources/base.source.d.ts +22 -0
  51. package/dist/sources/base.source.js +130 -0
  52. package/dist/sources/base.source.js.map +1 -0
  53. package/dist/sources/base.source.mjs +93 -0
  54. package/dist/sources/base.source.mjs.map +1 -0
  55. package/dist/sources/playwright.source.d.mts +34 -0
  56. package/dist/sources/playwright.source.d.ts +34 -0
  57. package/dist/sources/playwright.source.js +209 -0
  58. package/dist/sources/playwright.source.js.map +1 -0
  59. package/dist/sources/playwright.source.mjs +172 -0
  60. package/dist/sources/playwright.source.mjs.map +1 -0
  61. package/dist/sources/routes.source.d.mts +58 -0
  62. package/dist/sources/routes.source.d.ts +58 -0
  63. package/dist/sources/routes.source.js +288 -0
  64. package/dist/sources/routes.source.js.map +1 -0
  65. package/dist/sources/routes.source.mjs +251 -0
  66. package/dist/sources/routes.source.mjs.map +1 -0
  67. package/dist/sources/yaml.source.d.mts +16 -0
  68. package/dist/sources/yaml.source.d.ts +16 -0
  69. package/dist/sources/yaml.source.js +160 -0
  70. package/dist/sources/yaml.source.js.map +1 -0
  71. package/dist/sources/yaml.source.mjs +123 -0
  72. package/dist/sources/yaml.source.mjs.map +1 -0
  73. package/dist/utils/http.d.mts +7 -0
  74. package/dist/utils/http.d.ts +7 -0
  75. package/dist/utils/http.js +37 -0
  76. package/dist/utils/http.js.map +1 -0
  77. package/dist/utils/http.mjs +12 -0
  78. package/dist/utils/http.mjs.map +1 -0
  79. package/dist/utils/logger.d.mts +35 -0
  80. package/dist/utils/logger.d.ts +35 -0
  81. package/dist/utils/logger.js +114 -0
  82. package/dist/utils/logger.js.map +1 -0
  83. package/dist/utils/logger.mjs +79 -0
  84. package/dist/utils/logger.mjs.map +1 -0
  85. package/package.json +115 -0
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/reporters/base.reporter.ts
21
+ var base_reporter_exports = {};
22
+ __export(base_reporter_exports, {
23
+ BaseReporter: () => BaseReporter
24
+ });
25
+ module.exports = __toCommonJS(base_reporter_exports);
26
+ var BaseReporter = class {
27
+ };
28
+ // Annotate the CommonJS export names for ESM import in node:
29
+ 0 && (module.exports = {
30
+ BaseReporter
31
+ });
32
+ //# sourceMappingURL=base.reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/reporters/base.reporter.ts"],"sourcesContent":["/**\n * src/reporters/base.reporter.ts\n * \n * Abstract base class for all reporters.\n * To add a new output format: extend this class, implement write().\n */\n\nimport { CoverageReport } from '../core/types'\n\nexport abstract class BaseReporter {\n abstract readonly name: string\n\n /**\n * Write the report to the given output path.\n * For console reporter, outputPath may be ignored.\n */\n abstract write(report: CoverageReport, outputPath: string): Promise<void>\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AASO,IAAe,eAAf,MAA4B;AAQnC;","names":[]}
@@ -0,0 +1,7 @@
1
+ // src/reporters/base.reporter.ts
2
+ var BaseReporter = class {
3
+ };
4
+ export {
5
+ BaseReporter
6
+ };
7
+ //# sourceMappingURL=base.reporter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/reporters/base.reporter.ts"],"sourcesContent":["/**\n * src/reporters/base.reporter.ts\n * \n * Abstract base class for all reporters.\n * To add a new output format: extend this class, implement write().\n */\n\nimport { CoverageReport } from '../core/types'\n\nexport abstract class BaseReporter {\n abstract readonly name: string\n\n /**\n * Write the report to the given output path.\n * For console reporter, outputPath may be ignored.\n */\n abstract write(report: CoverageReport, outputPath: string): Promise<void>\n}\n"],"mappings":";AASO,IAAe,eAAf,MAA4B;AAQnC;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { BaseReporter } from './base.reporter.mjs';
2
+ import { CoverageReport } from '../core/types.mjs';
3
+
4
+ /**
5
+ * src/reporters/console.reporter.ts
6
+ * Pretty terminal output using chalk.
7
+ * Kept concise — this is a summary, not the full report.
8
+ */
9
+
10
+ declare class ConsoleReporter extends BaseReporter {
11
+ readonly name = "console";
12
+ write(report: CoverageReport, _outputDir: string): Promise<void>;
13
+ private colourise;
14
+ }
15
+
16
+ export { ConsoleReporter };
@@ -0,0 +1,16 @@
1
+ import { BaseReporter } from './base.reporter.js';
2
+ import { CoverageReport } from '../core/types.js';
3
+
4
+ /**
5
+ * src/reporters/console.reporter.ts
6
+ * Pretty terminal output using chalk.
7
+ * Kept concise — this is a summary, not the full report.
8
+ */
9
+
10
+ declare class ConsoleReporter extends BaseReporter {
11
+ readonly name = "console";
12
+ write(report: CoverageReport, _outputDir: string): Promise<void>;
13
+ private colourise;
14
+ }
15
+
16
+ export { ConsoleReporter };
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/reporters/console.reporter.ts
31
+ var console_reporter_exports = {};
32
+ __export(console_reporter_exports, {
33
+ ConsoleReporter: () => ConsoleReporter
34
+ });
35
+ module.exports = __toCommonJS(console_reporter_exports);
36
+ var path = __toESM(require("path"));
37
+ var import_chalk = __toESM(require("chalk"));
38
+
39
+ // src/reporters/base.reporter.ts
40
+ var BaseReporter = class {
41
+ };
42
+
43
+ // src/reporters/console.reporter.ts
44
+ var ConsoleReporter = class extends BaseReporter {
45
+ name = "console";
46
+ async write(report, _outputDir) {
47
+ const { summary, areas, uncategorised } = report;
48
+ const allRoutes = [...areas.flatMap((a) => a.routes), ...uncategorised];
49
+ const noCoverage = allRoutes.filter((r) => r.status === "none");
50
+ const stale = allRoutes.filter((r) => r.staleness === "stale");
51
+ const totalColoured = this.colourise(summary.totalCoverage, `${summary.totalCoverage}%`);
52
+ console.log(
53
+ `
54
+ qualitylens: ${totalColoured} total (${import_chalk.default.cyan(summary.automatedCoverage + "%")} auto \xB7 ${import_chalk.default.magenta(summary.manualOnlyCoverage + "%")} manual \xB7 ${import_chalk.default.red(summary.noCoverage + "%")} none)
55
+ `
56
+ );
57
+ for (const area of areas) {
58
+ const filled = Math.round(area.coveragePercent / 5);
59
+ const bar = this.colourise(
60
+ area.coveragePercent,
61
+ "\u2588".repeat(filled) + "\u2591".repeat(20 - filled)
62
+ );
63
+ const pct = this.colourise(area.coveragePercent, `${area.coveragePercent}%`);
64
+ console.log(`
65
+ ${area.name.padEnd(24)} ${bar} ${pct}`);
66
+ for (const r of area.routes) {
67
+ const method = r.route.method ? import_chalk.default.dim(`${r.route.method} `) : "";
68
+ const routeLine = r.status === "none" ? import_chalk.default.red(` \u274C ${method}${r.route.path}`) : r.status === "automated" ? import_chalk.default.green(` \u2705 ${method}${r.route.path}`) : r.status === "both" ? import_chalk.default.cyan(` \u2B50 ${method}${r.route.path}`) : import_chalk.default.blue(` \u{1F535} ${method}${r.route.path}`);
69
+ console.log(routeLine);
70
+ for (const t of r.automatedTests) {
71
+ const file = t.filePath ? import_chalk.default.dim(` (${path.basename(t.filePath)})`) : "";
72
+ console.log(import_chalk.default.green(` \u2192 "${t.title}"`) + file);
73
+ }
74
+ for (const t of r.manualTests) {
75
+ const src = import_chalk.default.dim(` (${t.source})`);
76
+ console.log(import_chalk.default.blue(` \u2192 "${t.title}"`) + src);
77
+ }
78
+ }
79
+ }
80
+ if (uncategorised.length > 0) {
81
+ console.log(import_chalk.default.dim(`
82
+ Uncategorised`));
83
+ for (const r of uncategorised) {
84
+ const method = r.route.method ? import_chalk.default.dim(`${r.route.method} `) : "";
85
+ console.log(
86
+ r.status === "none" ? import_chalk.default.red(` \u274C ${method}${r.route.path}`) : import_chalk.default.dim(` \xB7 ${method}${r.route.path}`)
87
+ );
88
+ for (const t of r.automatedTests) {
89
+ const file = t.filePath ? import_chalk.default.dim(` (${path.basename(t.filePath)})`) : "";
90
+ console.log(import_chalk.default.green(` \u2192 "${t.title}"`) + file);
91
+ }
92
+ for (const t of r.manualTests) {
93
+ console.log(import_chalk.default.blue(` \u2192 "${t.title}"`) + import_chalk.default.dim(` (${t.source})`));
94
+ }
95
+ }
96
+ }
97
+ if (noCoverage.length > 0) {
98
+ console.log(import_chalk.default.red(`
99
+ No coverage (${noCoverage.length} routes): `) + noCoverage.map((r) => import_chalk.default.red(r.route.path)).join(", "));
100
+ }
101
+ if (stale.length > 0) {
102
+ console.log(import_chalk.default.yellow(`
103
+ Stale manual coverage (${stale.length} routes):`));
104
+ stale.forEach((r) => {
105
+ const days = r.lastTestedAt ? Math.round((Date.now() - r.lastTestedAt.getTime()) / 864e5) : "?";
106
+ console.log(import_chalk.default.yellow(` \u26A0\uFE0F ${r.route.path.padEnd(40)} \u2014 last tested ${days}d ago`));
107
+ });
108
+ }
109
+ if (report.unmatchedTests.length > 0) {
110
+ console.log(import_chalk.default.magenta(`
111
+ Orphaned tests \u2014 matched no route (${report.unmatchedTests.length}):`));
112
+ for (const t of report.unmatchedTests) {
113
+ const file = t.filePath ? import_chalk.default.dim(` (${path.basename(t.filePath)})`) : "";
114
+ console.log(import_chalk.default.magenta(` \u26A0 "${t.title}"`) + file);
115
+ }
116
+ }
117
+ console.log();
118
+ }
119
+ // Colours a string based on a numeric coverage percentage threshold
120
+ colourise(percent, str) {
121
+ if (percent >= 80) return import_chalk.default.green(str);
122
+ if (percent >= 50) return import_chalk.default.yellow(str);
123
+ return import_chalk.default.red(str);
124
+ }
125
+ };
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ ConsoleReporter
129
+ });
130
+ //# sourceMappingURL=console.reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/reporters/console.reporter.ts","../../src/reporters/base.reporter.ts"],"sourcesContent":["/**\n * src/reporters/console.reporter.ts\n * Pretty terminal output using chalk.\n * Kept concise — this is a summary, not the full report.\n */\n\nimport * as path from 'path'\nimport chalk from 'chalk'\nimport { BaseReporter } from './base.reporter'\nimport { CoverageReport } from '../core/types'\n\nexport class ConsoleReporter extends BaseReporter {\n readonly name = 'console'\n\n async write(report: CoverageReport, _outputDir: string): Promise<void> {\n const { summary, areas, uncategorised } = report\n const allRoutes = [...areas.flatMap(a => a.routes), ...uncategorised]\n const noCoverage = allRoutes.filter(r => r.status === 'none')\n const stale = allRoutes.filter(r => r.staleness === 'stale')\n\n // ── Summary line ─────────────────────────────────────────────────────────\n const totalColoured = this.colourise(summary.totalCoverage, `${summary.totalCoverage}%`)\n console.log(\n `\\nqualitylens: ${totalColoured} total` +\n ` (${chalk.cyan(summary.automatedCoverage + '%')} auto` +\n ` · ${chalk.magenta(summary.manualOnlyCoverage + '%')} manual` +\n ` · ${chalk.red(summary.noCoverage + '%')} none)\\n`\n )\n\n // ── Per-area breakdown with route + test traceability ────────────────────\n for (const area of areas) {\n const filled = Math.round(area.coveragePercent / 5)\n const bar = this.colourise(\n area.coveragePercent,\n '█'.repeat(filled) + '░'.repeat(20 - filled)\n )\n const pct = this.colourise(area.coveragePercent, `${area.coveragePercent}%`)\n console.log(`\\n ${area.name.padEnd(24)} ${bar} ${pct}`)\n\n for (const r of area.routes) {\n const method = r.route.method ? chalk.dim(`${r.route.method} `) : ''\n const routeLine = r.status === 'none'\n ? chalk.red(` ❌ ${method}${r.route.path}`)\n : r.status === 'automated'\n ? chalk.green(` ✅ ${method}${r.route.path}`)\n : r.status === 'both'\n ? chalk.cyan(` ⭐ ${method}${r.route.path}`)\n : chalk.blue(` 🔵 ${method}${r.route.path}`)\n console.log(routeLine)\n\n for (const t of r.automatedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.green(` → \"${t.title}\"`) + file)\n }\n for (const t of r.manualTests) {\n const src = chalk.dim(` (${t.source})`)\n console.log(chalk.blue(` → \"${t.title}\"`) + src)\n }\n }\n }\n\n // ── Uncategorised routes ──────────────────────────────────────────────────\n if (uncategorised.length > 0) {\n console.log(chalk.dim(`\\n Uncategorised`))\n for (const r of uncategorised) {\n const method = r.route.method ? chalk.dim(`${r.route.method} `) : ''\n console.log(r.status === 'none'\n ? chalk.red(` ❌ ${method}${r.route.path}`)\n : chalk.dim(` · ${method}${r.route.path}`)\n )\n for (const t of r.automatedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.green(` → \"${t.title}\"`) + file)\n }\n for (const t of r.manualTests) {\n console.log(chalk.blue(` → \"${t.title}\"`) + chalk.dim(` (${t.source})`))\n }\n }\n }\n\n // ── No coverage summary ───────────────────────────────────────────────────\n if (noCoverage.length > 0) {\n console.log(chalk.red(`\\nNo coverage (${noCoverage.length} routes): `) +\n noCoverage.map(r => chalk.red(r.route.path)).join(', '))\n }\n\n // ── Stale manual coverage list ───────────────────────────────────────────\n if (stale.length > 0) {\n console.log(chalk.yellow(`\\nStale manual coverage (${stale.length} routes):`))\n stale.forEach(r => {\n const days = r.lastTestedAt\n ? Math.round((Date.now() - r.lastTestedAt.getTime()) / 86400000)\n : '?'\n console.log(chalk.yellow(` ⚠️ ${r.route.path.padEnd(40)} — last tested ${days}d ago`))\n })\n }\n\n // ── Orphaned tests (matched no route) ────────────────────────────────────\n if (report.unmatchedTests.length > 0) {\n console.log(chalk.magenta(`\\nOrphaned tests — matched no route (${report.unmatchedTests.length}):`))\n for (const t of report.unmatchedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.magenta(` ⚠ \"${t.title}\"`) + file)\n }\n }\n\n console.log()\n }\n\n // Colours a string based on a numeric coverage percentage threshold\n private colourise(percent: number, str: string): string {\n if (percent >= 80) return chalk.green(str)\n if (percent >= 50) return chalk.yellow(str)\n return chalk.red(str)\n }\n}\n","/**\n * src/reporters/base.reporter.ts\n * \n * Abstract base class for all reporters.\n * To add a new output format: extend this class, implement write().\n */\n\nimport { CoverageReport } from '../core/types'\n\nexport abstract class BaseReporter {\n abstract readonly name: string\n\n /**\n * Write the report to the given output path.\n * For console reporter, outputPath may be ignored.\n */\n abstract write(report: CoverageReport, outputPath: string): Promise<void>\n}\n"],"mappings":"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","names":["chalk"]}
@@ -0,0 +1,95 @@
1
+ // src/reporters/console.reporter.ts
2
+ import * as path from "path";
3
+ import chalk from "chalk";
4
+
5
+ // src/reporters/base.reporter.ts
6
+ var BaseReporter = class {
7
+ };
8
+
9
+ // src/reporters/console.reporter.ts
10
+ var ConsoleReporter = class extends BaseReporter {
11
+ name = "console";
12
+ async write(report, _outputDir) {
13
+ const { summary, areas, uncategorised } = report;
14
+ const allRoutes = [...areas.flatMap((a) => a.routes), ...uncategorised];
15
+ const noCoverage = allRoutes.filter((r) => r.status === "none");
16
+ const stale = allRoutes.filter((r) => r.staleness === "stale");
17
+ const totalColoured = this.colourise(summary.totalCoverage, `${summary.totalCoverage}%`);
18
+ console.log(
19
+ `
20
+ qualitylens: ${totalColoured} total (${chalk.cyan(summary.automatedCoverage + "%")} auto \xB7 ${chalk.magenta(summary.manualOnlyCoverage + "%")} manual \xB7 ${chalk.red(summary.noCoverage + "%")} none)
21
+ `
22
+ );
23
+ for (const area of areas) {
24
+ const filled = Math.round(area.coveragePercent / 5);
25
+ const bar = this.colourise(
26
+ area.coveragePercent,
27
+ "\u2588".repeat(filled) + "\u2591".repeat(20 - filled)
28
+ );
29
+ const pct = this.colourise(area.coveragePercent, `${area.coveragePercent}%`);
30
+ console.log(`
31
+ ${area.name.padEnd(24)} ${bar} ${pct}`);
32
+ for (const r of area.routes) {
33
+ const method = r.route.method ? chalk.dim(`${r.route.method} `) : "";
34
+ const routeLine = r.status === "none" ? chalk.red(` \u274C ${method}${r.route.path}`) : r.status === "automated" ? chalk.green(` \u2705 ${method}${r.route.path}`) : r.status === "both" ? chalk.cyan(` \u2B50 ${method}${r.route.path}`) : chalk.blue(` \u{1F535} ${method}${r.route.path}`);
35
+ console.log(routeLine);
36
+ for (const t of r.automatedTests) {
37
+ const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : "";
38
+ console.log(chalk.green(` \u2192 "${t.title}"`) + file);
39
+ }
40
+ for (const t of r.manualTests) {
41
+ const src = chalk.dim(` (${t.source})`);
42
+ console.log(chalk.blue(` \u2192 "${t.title}"`) + src);
43
+ }
44
+ }
45
+ }
46
+ if (uncategorised.length > 0) {
47
+ console.log(chalk.dim(`
48
+ Uncategorised`));
49
+ for (const r of uncategorised) {
50
+ const method = r.route.method ? chalk.dim(`${r.route.method} `) : "";
51
+ console.log(
52
+ r.status === "none" ? chalk.red(` \u274C ${method}${r.route.path}`) : chalk.dim(` \xB7 ${method}${r.route.path}`)
53
+ );
54
+ for (const t of r.automatedTests) {
55
+ const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : "";
56
+ console.log(chalk.green(` \u2192 "${t.title}"`) + file);
57
+ }
58
+ for (const t of r.manualTests) {
59
+ console.log(chalk.blue(` \u2192 "${t.title}"`) + chalk.dim(` (${t.source})`));
60
+ }
61
+ }
62
+ }
63
+ if (noCoverage.length > 0) {
64
+ console.log(chalk.red(`
65
+ No coverage (${noCoverage.length} routes): `) + noCoverage.map((r) => chalk.red(r.route.path)).join(", "));
66
+ }
67
+ if (stale.length > 0) {
68
+ console.log(chalk.yellow(`
69
+ Stale manual coverage (${stale.length} routes):`));
70
+ stale.forEach((r) => {
71
+ const days = r.lastTestedAt ? Math.round((Date.now() - r.lastTestedAt.getTime()) / 864e5) : "?";
72
+ console.log(chalk.yellow(` \u26A0\uFE0F ${r.route.path.padEnd(40)} \u2014 last tested ${days}d ago`));
73
+ });
74
+ }
75
+ if (report.unmatchedTests.length > 0) {
76
+ console.log(chalk.magenta(`
77
+ Orphaned tests \u2014 matched no route (${report.unmatchedTests.length}):`));
78
+ for (const t of report.unmatchedTests) {
79
+ const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : "";
80
+ console.log(chalk.magenta(` \u26A0 "${t.title}"`) + file);
81
+ }
82
+ }
83
+ console.log();
84
+ }
85
+ // Colours a string based on a numeric coverage percentage threshold
86
+ colourise(percent, str) {
87
+ if (percent >= 80) return chalk.green(str);
88
+ if (percent >= 50) return chalk.yellow(str);
89
+ return chalk.red(str);
90
+ }
91
+ };
92
+ export {
93
+ ConsoleReporter
94
+ };
95
+ //# sourceMappingURL=console.reporter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/reporters/console.reporter.ts","../../src/reporters/base.reporter.ts"],"sourcesContent":["/**\n * src/reporters/console.reporter.ts\n * Pretty terminal output using chalk.\n * Kept concise — this is a summary, not the full report.\n */\n\nimport * as path from 'path'\nimport chalk from 'chalk'\nimport { BaseReporter } from './base.reporter'\nimport { CoverageReport } from '../core/types'\n\nexport class ConsoleReporter extends BaseReporter {\n readonly name = 'console'\n\n async write(report: CoverageReport, _outputDir: string): Promise<void> {\n const { summary, areas, uncategorised } = report\n const allRoutes = [...areas.flatMap(a => a.routes), ...uncategorised]\n const noCoverage = allRoutes.filter(r => r.status === 'none')\n const stale = allRoutes.filter(r => r.staleness === 'stale')\n\n // ── Summary line ─────────────────────────────────────────────────────────\n const totalColoured = this.colourise(summary.totalCoverage, `${summary.totalCoverage}%`)\n console.log(\n `\\nqualitylens: ${totalColoured} total` +\n ` (${chalk.cyan(summary.automatedCoverage + '%')} auto` +\n ` · ${chalk.magenta(summary.manualOnlyCoverage + '%')} manual` +\n ` · ${chalk.red(summary.noCoverage + '%')} none)\\n`\n )\n\n // ── Per-area breakdown with route + test traceability ────────────────────\n for (const area of areas) {\n const filled = Math.round(area.coveragePercent / 5)\n const bar = this.colourise(\n area.coveragePercent,\n '█'.repeat(filled) + '░'.repeat(20 - filled)\n )\n const pct = this.colourise(area.coveragePercent, `${area.coveragePercent}%`)\n console.log(`\\n ${area.name.padEnd(24)} ${bar} ${pct}`)\n\n for (const r of area.routes) {\n const method = r.route.method ? chalk.dim(`${r.route.method} `) : ''\n const routeLine = r.status === 'none'\n ? chalk.red(` ❌ ${method}${r.route.path}`)\n : r.status === 'automated'\n ? chalk.green(` ✅ ${method}${r.route.path}`)\n : r.status === 'both'\n ? chalk.cyan(` ⭐ ${method}${r.route.path}`)\n : chalk.blue(` 🔵 ${method}${r.route.path}`)\n console.log(routeLine)\n\n for (const t of r.automatedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.green(` → \"${t.title}\"`) + file)\n }\n for (const t of r.manualTests) {\n const src = chalk.dim(` (${t.source})`)\n console.log(chalk.blue(` → \"${t.title}\"`) + src)\n }\n }\n }\n\n // ── Uncategorised routes ──────────────────────────────────────────────────\n if (uncategorised.length > 0) {\n console.log(chalk.dim(`\\n Uncategorised`))\n for (const r of uncategorised) {\n const method = r.route.method ? chalk.dim(`${r.route.method} `) : ''\n console.log(r.status === 'none'\n ? chalk.red(` ❌ ${method}${r.route.path}`)\n : chalk.dim(` · ${method}${r.route.path}`)\n )\n for (const t of r.automatedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.green(` → \"${t.title}\"`) + file)\n }\n for (const t of r.manualTests) {\n console.log(chalk.blue(` → \"${t.title}\"`) + chalk.dim(` (${t.source})`))\n }\n }\n }\n\n // ── No coverage summary ───────────────────────────────────────────────────\n if (noCoverage.length > 0) {\n console.log(chalk.red(`\\nNo coverage (${noCoverage.length} routes): `) +\n noCoverage.map(r => chalk.red(r.route.path)).join(', '))\n }\n\n // ── Stale manual coverage list ───────────────────────────────────────────\n if (stale.length > 0) {\n console.log(chalk.yellow(`\\nStale manual coverage (${stale.length} routes):`))\n stale.forEach(r => {\n const days = r.lastTestedAt\n ? Math.round((Date.now() - r.lastTestedAt.getTime()) / 86400000)\n : '?'\n console.log(chalk.yellow(` ⚠️ ${r.route.path.padEnd(40)} — last tested ${days}d ago`))\n })\n }\n\n // ── Orphaned tests (matched no route) ────────────────────────────────────\n if (report.unmatchedTests.length > 0) {\n console.log(chalk.magenta(`\\nOrphaned tests — matched no route (${report.unmatchedTests.length}):`))\n for (const t of report.unmatchedTests) {\n const file = t.filePath ? chalk.dim(` (${path.basename(t.filePath)})`) : ''\n console.log(chalk.magenta(` ⚠ \"${t.title}\"`) + file)\n }\n }\n\n console.log()\n }\n\n // Colours a string based on a numeric coverage percentage threshold\n private colourise(percent: number, str: string): string {\n if (percent >= 80) return chalk.green(str)\n if (percent >= 50) return chalk.yellow(str)\n return chalk.red(str)\n }\n}\n","/**\n * src/reporters/base.reporter.ts\n * \n * Abstract base class for all reporters.\n * To add a new output format: extend this class, implement write().\n */\n\nimport { CoverageReport } from '../core/types'\n\nexport abstract class BaseReporter {\n abstract readonly name: string\n\n /**\n * Write the report to the given output path.\n * For console reporter, outputPath may be ignored.\n */\n abstract write(report: CoverageReport, outputPath: string): Promise<void>\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,22 @@
1
+ import { TestEntry } from '../core/types.mjs';
2
+
3
+ /**
4
+ * src/sources/base.source.ts
5
+ *
6
+ * Abstract base class for all test sources.
7
+ * To add a new source: extend this class, implement collect().
8
+ * Sources are stateless — no caching, no shared state.
9
+ */
10
+
11
+ declare abstract class BaseSource {
12
+ abstract readonly name: string;
13
+ /**
14
+ * Collect all test entries from this source.
15
+ * Must never throw — return [] and log a warning on failure.
16
+ */
17
+ abstract collect(): Promise<TestEntry[]>;
18
+ protected log(msg: string): void;
19
+ protected warn(msg: string, detail?: unknown): void;
20
+ }
21
+
22
+ export { BaseSource };
@@ -0,0 +1,22 @@
1
+ import { TestEntry } from '../core/types.js';
2
+
3
+ /**
4
+ * src/sources/base.source.ts
5
+ *
6
+ * Abstract base class for all test sources.
7
+ * To add a new source: extend this class, implement collect().
8
+ * Sources are stateless — no caching, no shared state.
9
+ */
10
+
11
+ declare abstract class BaseSource {
12
+ abstract readonly name: string;
13
+ /**
14
+ * Collect all test entries from this source.
15
+ * Must never throw — return [] and log a warning on failure.
16
+ */
17
+ abstract collect(): Promise<TestEntry[]>;
18
+ protected log(msg: string): void;
19
+ protected warn(msg: string, detail?: unknown): void;
20
+ }
21
+
22
+ export { BaseSource };
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/sources/base.source.ts
31
+ var base_source_exports = {};
32
+ __export(base_source_exports, {
33
+ BaseSource: () => BaseSource
34
+ });
35
+ module.exports = __toCommonJS(base_source_exports);
36
+
37
+ // src/utils/logger.ts
38
+ var fs = __toESM(require("fs"));
39
+ var path = __toESM(require("path"));
40
+ var Logger = class {
41
+ logPath = null;
42
+ fd = null;
43
+ /**
44
+ * Open (or create) qualitylens.log in the given directory.
45
+ * Appends to an existing log so multiple runs accumulate history.
46
+ * Call once at the start of each command.
47
+ */
48
+ configure(outputDir) {
49
+ try {
50
+ fs.mkdirSync(outputDir, { recursive: true });
51
+ this.logPath = path.join(outputDir, "qualitylens.log");
52
+ this.fd = fs.openSync(this.logPath, "a");
53
+ this.write("INFO", `--- qualitylens session started (pid ${process.pid}) ---`);
54
+ } catch {
55
+ this.logPath = null;
56
+ this.fd = null;
57
+ }
58
+ }
59
+ /** Path to the current log file, or null if not configured. */
60
+ get filePath() {
61
+ return this.logPath;
62
+ }
63
+ info(msg, detail) {
64
+ this.write("INFO", msg, detail);
65
+ }
66
+ warn(msg, detail) {
67
+ this.write("WARN", msg, detail);
68
+ }
69
+ /** Logs full detail (stack trace, raw error) to file only. */
70
+ error(msg, detail) {
71
+ this.write("ERROR", msg, detail);
72
+ }
73
+ debug(msg, detail) {
74
+ this.write("DEBUG", msg, detail);
75
+ }
76
+ close() {
77
+ if (this.fd !== null) {
78
+ try {
79
+ fs.closeSync(this.fd);
80
+ } catch {
81
+ }
82
+ this.fd = null;
83
+ }
84
+ }
85
+ write(level, msg, detail) {
86
+ if (this.fd === null) return;
87
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
88
+ let line = `[${ts}] ${level.padEnd(5)} ${msg}`;
89
+ if (detail !== void 0) {
90
+ if (detail instanceof Error) {
91
+ line += `
92
+ ${detail.message}`;
93
+ if (detail.stack) {
94
+ line += "\n" + detail.stack.split("\n").map((l) => " " + l).join("\n");
95
+ }
96
+ } else if (typeof detail === "string" && detail.trim()) {
97
+ line += "\n" + detail.split("\n").map((l) => " " + l).join("\n");
98
+ } else if (typeof detail === "object") {
99
+ try {
100
+ line += "\n " + JSON.stringify(detail, null, 2).replace(/\n/g, "\n ");
101
+ } catch {
102
+ }
103
+ }
104
+ }
105
+ try {
106
+ fs.writeSync(this.fd, line + "\n");
107
+ } catch {
108
+ }
109
+ }
110
+ };
111
+ var logger = new Logger();
112
+
113
+ // src/sources/base.source.ts
114
+ var BaseSource = class {
115
+ log(msg) {
116
+ process.stdout.write(` [${this.name}] ${msg}
117
+ `);
118
+ logger.info(`[${this.name}] ${msg}`);
119
+ }
120
+ warn(msg, detail) {
121
+ process.stderr.write(` [${this.name}] WARNING: ${msg}
122
+ `);
123
+ logger.warn(`[${this.name}] ${msg}`, detail);
124
+ }
125
+ };
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ BaseSource
129
+ });
130
+ //# sourceMappingURL=base.source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sources/base.source.ts","../../src/utils/logger.ts"],"sourcesContent":["/**\n * src/sources/base.source.ts\n * \n * Abstract base class for all test sources.\n * To add a new source: extend this class, implement collect().\n * Sources are stateless — no caching, no shared state.\n */\n\nimport { TestEntry } from '../core/types'\nimport { logger } from '../utils/logger'\n\nexport abstract class BaseSource {\n abstract readonly name: string\n\n /**\n * Collect all test entries from this source.\n * Must never throw — return [] and log a warning on failure.\n */\n abstract collect(): Promise<TestEntry[]>\n\n protected log(msg: string): void {\n process.stdout.write(` [${this.name}] ${msg}\\n`)\n logger.info(`[${this.name}] ${msg}`)\n }\n\n protected warn(msg: string, detail?: unknown): void {\n process.stderr.write(` [${this.name}] WARNING: ${msg}\\n`)\n logger.warn(`[${this.name}] ${msg}`, detail)\n }\n}\n","/**\n * src/utils/logger.ts\n *\n * Module-level singleton logger.\n * - Always writes to a log file (qualitylens.log) with timestamps and full detail.\n * - Console output is handled separately by each command — the logger only writes the file.\n *\n * Usage:\n * import { logger } from './utils/logger'\n * logger.configure('./reports') // call once at startup\n * logger.info('scan started', { config: opts.config })\n * logger.error('route discovery failed', err)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'\n\nclass Logger {\n private logPath: string | null = null\n private fd: number | null = null\n\n /**\n * Open (or create) qualitylens.log in the given directory.\n * Appends to an existing log so multiple runs accumulate history.\n * Call once at the start of each command.\n */\n configure(outputDir: string): void {\n try {\n fs.mkdirSync(outputDir, { recursive: true })\n this.logPath = path.join(outputDir, 'qualitylens.log')\n this.fd = fs.openSync(this.logPath, 'a')\n this.write('INFO', `--- qualitylens session started (pid ${process.pid}) ---`)\n } catch {\n // If we can't open a log file, continue silently — logging is non-blocking\n this.logPath = null\n this.fd = null\n }\n }\n\n /** Path to the current log file, or null if not configured. */\n get filePath(): string | null {\n return this.logPath\n }\n\n info(msg: string, detail?: unknown): void {\n this.write('INFO', msg, detail)\n }\n\n warn(msg: string, detail?: unknown): void {\n this.write('WARN', msg, detail)\n }\n\n /** Logs full detail (stack trace, raw error) to file only. */\n error(msg: string, detail?: unknown): void {\n this.write('ERROR', msg, detail)\n }\n\n debug(msg: string, detail?: unknown): void {\n this.write('DEBUG', msg, detail)\n }\n\n close(): void {\n if (this.fd !== null) {\n try { fs.closeSync(this.fd) } catch { /* ignore */ }\n this.fd = null\n }\n }\n\n private write(level: LogLevel, msg: string, detail?: unknown): void {\n if (this.fd === null) return\n\n const ts = new Date().toISOString()\n let line = `[${ts}] ${level.padEnd(5)} ${msg}`\n\n if (detail !== undefined) {\n if (detail instanceof Error) {\n line += `\\n ${detail.message}`\n if (detail.stack) {\n line += '\\n' + detail.stack.split('\\n').map(l => ' ' + l).join('\\n')\n }\n } else if (typeof detail === 'string' && detail.trim()) {\n line += '\\n' + detail.split('\\n').map(l => ' ' + l).join('\\n')\n } else if (typeof detail === 'object') {\n try {\n line += '\\n ' + JSON.stringify(detail, null, 2).replace(/\\n/g, '\\n ')\n } catch { /* circular ref or similar — skip */ }\n }\n }\n\n try {\n fs.writeSync(this.fd, line + '\\n')\n } catch { /* disk full or fd closed — ignore */ }\n }\n}\n\nexport const logger = new Logger()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcA,SAAoB;AACpB,WAAsB;AAItB,IAAM,SAAN,MAAa;AAAA,EACH,UAAyB;AAAA,EACzB,KAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,UAAU,WAAyB;AACjC,QAAI;AACF,MAAG,aAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,WAAK,UAAe,UAAK,WAAW,iBAAiB;AACrD,WAAK,KAAQ,YAAS,KAAK,SAAS,GAAG;AACvC,WAAK,MAAM,QAAQ,wCAAwC,QAAQ,GAAG,OAAO;AAAA,IAC/E,QAAQ;AAEN,WAAK,UAAU;AACf,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,WAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,KAAa,QAAwB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM;AAAA,EAChC;AAAA,EAEA,KAAK,KAAa,QAAwB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,KAAa,QAAwB;AACzC,SAAK,MAAM,SAAS,KAAK,MAAM;AAAA,EACjC;AAAA,EAEA,MAAM,KAAa,QAAwB;AACzC,SAAK,MAAM,SAAS,KAAK,MAAM;AAAA,EACjC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAO,MAAM;AACpB,UAAI;AAAE,QAAG,aAAU,KAAK,EAAE;AAAA,MAAE,QAAQ;AAAA,MAAe;AACnD,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEQ,MAAM,OAAiB,KAAa,QAAwB;AAClE,QAAI,KAAK,OAAO,KAAM;AAEtB,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,QAAI,OAAO,IAAI,EAAE,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,GAAG;AAE5C,QAAI,WAAW,QAAW;AACxB,UAAI,kBAAkB,OAAO;AAC3B,gBAAQ;AAAA,IAAO,OAAO,OAAO;AAC7B,YAAI,OAAO,OAAO;AAChB,kBAAQ,OAAO,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,OAAK,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,QACtE;AAAA,MACF,WAAW,OAAO,WAAW,YAAY,OAAO,KAAK,GAAG;AACtD,gBAAQ,OAAO,OAAO,MAAM,IAAI,EAAE,IAAI,OAAK,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,MAChE,WAAW,OAAO,WAAW,UAAU;AACrC,YAAI;AACF,kBAAQ,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,QAAQ,OAAO,MAAM;AAAA,QACxE,QAAQ;AAAA,QAAuC;AAAA,MACjD;AAAA,IACF;AAEA,QAAI;AACF,MAAG,aAAU,KAAK,IAAI,OAAO,IAAI;AAAA,IACnC,QAAQ;AAAA,IAAwC;AAAA,EAClD;AACF;AAEO,IAAM,SAAS,IAAI,OAAO;;;ADtF1B,IAAe,aAAf,MAA0B;AAAA,EASrB,IAAI,KAAmB;AAC/B,YAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,KAAK,GAAG;AAAA,CAAI;AAChD,WAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,EAAE;AAAA,EACrC;AAAA,EAEU,KAAK,KAAa,QAAwB;AAClD,YAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,cAAc,GAAG;AAAA,CAAI;AACzD,WAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,MAAM;AAAA,EAC7C;AACF;","names":[]}
@@ -0,0 +1,93 @@
1
+ // src/utils/logger.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var Logger = class {
5
+ logPath = null;
6
+ fd = null;
7
+ /**
8
+ * Open (or create) qualitylens.log in the given directory.
9
+ * Appends to an existing log so multiple runs accumulate history.
10
+ * Call once at the start of each command.
11
+ */
12
+ configure(outputDir) {
13
+ try {
14
+ fs.mkdirSync(outputDir, { recursive: true });
15
+ this.logPath = path.join(outputDir, "qualitylens.log");
16
+ this.fd = fs.openSync(this.logPath, "a");
17
+ this.write("INFO", `--- qualitylens session started (pid ${process.pid}) ---`);
18
+ } catch {
19
+ this.logPath = null;
20
+ this.fd = null;
21
+ }
22
+ }
23
+ /** Path to the current log file, or null if not configured. */
24
+ get filePath() {
25
+ return this.logPath;
26
+ }
27
+ info(msg, detail) {
28
+ this.write("INFO", msg, detail);
29
+ }
30
+ warn(msg, detail) {
31
+ this.write("WARN", msg, detail);
32
+ }
33
+ /** Logs full detail (stack trace, raw error) to file only. */
34
+ error(msg, detail) {
35
+ this.write("ERROR", msg, detail);
36
+ }
37
+ debug(msg, detail) {
38
+ this.write("DEBUG", msg, detail);
39
+ }
40
+ close() {
41
+ if (this.fd !== null) {
42
+ try {
43
+ fs.closeSync(this.fd);
44
+ } catch {
45
+ }
46
+ this.fd = null;
47
+ }
48
+ }
49
+ write(level, msg, detail) {
50
+ if (this.fd === null) return;
51
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
52
+ let line = `[${ts}] ${level.padEnd(5)} ${msg}`;
53
+ if (detail !== void 0) {
54
+ if (detail instanceof Error) {
55
+ line += `
56
+ ${detail.message}`;
57
+ if (detail.stack) {
58
+ line += "\n" + detail.stack.split("\n").map((l) => " " + l).join("\n");
59
+ }
60
+ } else if (typeof detail === "string" && detail.trim()) {
61
+ line += "\n" + detail.split("\n").map((l) => " " + l).join("\n");
62
+ } else if (typeof detail === "object") {
63
+ try {
64
+ line += "\n " + JSON.stringify(detail, null, 2).replace(/\n/g, "\n ");
65
+ } catch {
66
+ }
67
+ }
68
+ }
69
+ try {
70
+ fs.writeSync(this.fd, line + "\n");
71
+ } catch {
72
+ }
73
+ }
74
+ };
75
+ var logger = new Logger();
76
+
77
+ // src/sources/base.source.ts
78
+ var BaseSource = class {
79
+ log(msg) {
80
+ process.stdout.write(` [${this.name}] ${msg}
81
+ `);
82
+ logger.info(`[${this.name}] ${msg}`);
83
+ }
84
+ warn(msg, detail) {
85
+ process.stderr.write(` [${this.name}] WARNING: ${msg}
86
+ `);
87
+ logger.warn(`[${this.name}] ${msg}`, detail);
88
+ }
89
+ };
90
+ export {
91
+ BaseSource
92
+ };
93
+ //# sourceMappingURL=base.source.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/logger.ts","../../src/sources/base.source.ts"],"sourcesContent":["/**\n * src/utils/logger.ts\n *\n * Module-level singleton logger.\n * - Always writes to a log file (qualitylens.log) with timestamps and full detail.\n * - Console output is handled separately by each command — the logger only writes the file.\n *\n * Usage:\n * import { logger } from './utils/logger'\n * logger.configure('./reports') // call once at startup\n * logger.info('scan started', { config: opts.config })\n * logger.error('route discovery failed', err)\n */\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'\n\nclass Logger {\n private logPath: string | null = null\n private fd: number | null = null\n\n /**\n * Open (or create) qualitylens.log in the given directory.\n * Appends to an existing log so multiple runs accumulate history.\n * Call once at the start of each command.\n */\n configure(outputDir: string): void {\n try {\n fs.mkdirSync(outputDir, { recursive: true })\n this.logPath = path.join(outputDir, 'qualitylens.log')\n this.fd = fs.openSync(this.logPath, 'a')\n this.write('INFO', `--- qualitylens session started (pid ${process.pid}) ---`)\n } catch {\n // If we can't open a log file, continue silently — logging is non-blocking\n this.logPath = null\n this.fd = null\n }\n }\n\n /** Path to the current log file, or null if not configured. */\n get filePath(): string | null {\n return this.logPath\n }\n\n info(msg: string, detail?: unknown): void {\n this.write('INFO', msg, detail)\n }\n\n warn(msg: string, detail?: unknown): void {\n this.write('WARN', msg, detail)\n }\n\n /** Logs full detail (stack trace, raw error) to file only. */\n error(msg: string, detail?: unknown): void {\n this.write('ERROR', msg, detail)\n }\n\n debug(msg: string, detail?: unknown): void {\n this.write('DEBUG', msg, detail)\n }\n\n close(): void {\n if (this.fd !== null) {\n try { fs.closeSync(this.fd) } catch { /* ignore */ }\n this.fd = null\n }\n }\n\n private write(level: LogLevel, msg: string, detail?: unknown): void {\n if (this.fd === null) return\n\n const ts = new Date().toISOString()\n let line = `[${ts}] ${level.padEnd(5)} ${msg}`\n\n if (detail !== undefined) {\n if (detail instanceof Error) {\n line += `\\n ${detail.message}`\n if (detail.stack) {\n line += '\\n' + detail.stack.split('\\n').map(l => ' ' + l).join('\\n')\n }\n } else if (typeof detail === 'string' && detail.trim()) {\n line += '\\n' + detail.split('\\n').map(l => ' ' + l).join('\\n')\n } else if (typeof detail === 'object') {\n try {\n line += '\\n ' + JSON.stringify(detail, null, 2).replace(/\\n/g, '\\n ')\n } catch { /* circular ref or similar — skip */ }\n }\n }\n\n try {\n fs.writeSync(this.fd, line + '\\n')\n } catch { /* disk full or fd closed — ignore */ }\n }\n}\n\nexport const logger = new Logger()\n","/**\n * src/sources/base.source.ts\n * \n * Abstract base class for all test sources.\n * To add a new source: extend this class, implement collect().\n * Sources are stateless — no caching, no shared state.\n */\n\nimport { TestEntry } from '../core/types'\nimport { logger } from '../utils/logger'\n\nexport abstract class BaseSource {\n abstract readonly name: string\n\n /**\n * Collect all test entries from this source.\n * Must never throw — return [] and log a warning on failure.\n */\n abstract collect(): Promise<TestEntry[]>\n\n protected log(msg: string): void {\n process.stdout.write(` [${this.name}] ${msg}\\n`)\n logger.info(`[${this.name}] ${msg}`)\n }\n\n protected warn(msg: string, detail?: unknown): void {\n process.stderr.write(` [${this.name}] WARNING: ${msg}\\n`)\n logger.warn(`[${this.name}] ${msg}`, detail)\n }\n}\n"],"mappings":";AAcA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAItB,IAAM,SAAN,MAAa;AAAA,EACH,UAAyB;AAAA,EACzB,KAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,UAAU,WAAyB;AACjC,QAAI;AACF,MAAG,aAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,WAAK,UAAe,UAAK,WAAW,iBAAiB;AACrD,WAAK,KAAQ,YAAS,KAAK,SAAS,GAAG;AACvC,WAAK,MAAM,QAAQ,wCAAwC,QAAQ,GAAG,OAAO;AAAA,IAC/E,QAAQ;AAEN,WAAK,UAAU;AACf,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,WAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,KAAa,QAAwB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM;AAAA,EAChC;AAAA,EAEA,KAAK,KAAa,QAAwB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM;AAAA,EAChC;AAAA;AAAA,EAGA,MAAM,KAAa,QAAwB;AACzC,SAAK,MAAM,SAAS,KAAK,MAAM;AAAA,EACjC;AAAA,EAEA,MAAM,KAAa,QAAwB;AACzC,SAAK,MAAM,SAAS,KAAK,MAAM;AAAA,EACjC;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAO,MAAM;AACpB,UAAI;AAAE,QAAG,aAAU,KAAK,EAAE;AAAA,MAAE,QAAQ;AAAA,MAAe;AACnD,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEQ,MAAM,OAAiB,KAAa,QAAwB;AAClE,QAAI,KAAK,OAAO,KAAM;AAEtB,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,QAAI,OAAO,IAAI,EAAE,KAAK,MAAM,OAAO,CAAC,CAAC,IAAI,GAAG;AAE5C,QAAI,WAAW,QAAW;AACxB,UAAI,kBAAkB,OAAO;AAC3B,gBAAQ;AAAA,IAAO,OAAO,OAAO;AAC7B,YAAI,OAAO,OAAO;AAChB,kBAAQ,OAAO,OAAO,MAAM,MAAM,IAAI,EAAE,IAAI,OAAK,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,QACtE;AAAA,MACF,WAAW,OAAO,WAAW,YAAY,OAAO,KAAK,GAAG;AACtD,gBAAQ,OAAO,OAAO,MAAM,IAAI,EAAE,IAAI,OAAK,OAAO,CAAC,EAAE,KAAK,IAAI;AAAA,MAChE,WAAW,OAAO,WAAW,UAAU;AACrC,YAAI;AACF,kBAAQ,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,QAAQ,OAAO,MAAM;AAAA,QACxE,QAAQ;AAAA,QAAuC;AAAA,MACjD;AAAA,IACF;AAEA,QAAI;AACF,MAAG,aAAU,KAAK,IAAI,OAAO,IAAI;AAAA,IACnC,QAAQ;AAAA,IAAwC;AAAA,EAClD;AACF;AAEO,IAAM,SAAS,IAAI,OAAO;;;ACtF1B,IAAe,aAAf,MAA0B;AAAA,EASrB,IAAI,KAAmB;AAC/B,YAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,KAAK,GAAG;AAAA,CAAI;AAChD,WAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,EAAE;AAAA,EACrC;AAAA,EAEU,KAAK,KAAa,QAAwB;AAClD,YAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,cAAc,GAAG;AAAA,CAAI;AACzD,WAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,MAAM;AAAA,EAC7C;AACF;","names":[]}