@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,160 @@
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/yaml.source.ts
31
+ var yaml_source_exports = {};
32
+ __export(yaml_source_exports, {
33
+ YamlSource: () => YamlSource
34
+ });
35
+ module.exports = __toCommonJS(yaml_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
+
127
+ // src/sources/yaml.source.ts
128
+ var fs2 = __toESM(require("fs"));
129
+ var yaml = __toESM(require("js-yaml"));
130
+ var YamlSource = class extends BaseSource {
131
+ constructor(configPath) {
132
+ super();
133
+ this.configPath = configPath;
134
+ }
135
+ name = "yaml";
136
+ async collect() {
137
+ if (!fs2.existsSync(this.configPath)) {
138
+ this.warn(`config file not found at ${this.configPath}`);
139
+ return [];
140
+ }
141
+ const raw = yaml.load(fs2.readFileSync(this.configPath, "utf-8"));
142
+ const entries = raw?.manualCoverage ?? [];
143
+ if (entries.length === 0) {
144
+ this.log("no manual coverage entries found");
145
+ return [];
146
+ }
147
+ this.log(`found ${entries.length} manual coverage entries`);
148
+ return entries.map((entry) => ({
149
+ title: entry.route,
150
+ source: "yaml",
151
+ lastRun: entry.lastTested ? new Date(entry.lastTested) : void 0,
152
+ tags: entry.tester ? [entry.tester] : []
153
+ }));
154
+ }
155
+ };
156
+ // Annotate the CommonJS export names for ESM import in node:
157
+ 0 && (module.exports = {
158
+ YamlSource
159
+ });
160
+ //# sourceMappingURL=yaml.source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sources/yaml.source.ts","../../src/utils/logger.ts","../../src/sources/base.source.ts"],"sourcesContent":["/**\n * src/sources/yaml.source.ts\n * Reads manual coverage entries from qualitylens.yaml\n */\n\nimport { BaseSource } from './base.source'\nimport { TestEntry } from '../core/types'\nimport * as fs from 'fs'\nimport * as yaml from 'js-yaml'\n\nexport class YamlSource extends BaseSource {\n readonly name = 'yaml'\n\n constructor(private configPath: string) {\n super()\n }\n\n async collect(): Promise<TestEntry[]> {\n if (!fs.existsSync(this.configPath)) {\n this.warn(`config file not found at ${this.configPath}`)\n return []\n }\n\n const raw = yaml.load(fs.readFileSync(this.configPath, 'utf-8')) as any\n const entries: Array<{ route: string; lastTested?: string; tester?: string }> =\n raw?.manualCoverage ?? []\n\n if (entries.length === 0) {\n this.log('no manual coverage entries found')\n return []\n }\n\n this.log(`found ${entries.length} manual coverage entries`)\n\n // The title is set to the route path itself so FuzzyMatcher's exactMatch\n // will always link this entry to the correct route without any ambiguity.\n return entries.map(entry => ({\n title: entry.route,\n source: 'yaml' as const,\n lastRun: entry.lastTested ? new Date(entry.lastTested) : undefined,\n tags: entry.tester ? [entry.tester] : [],\n }))\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","/**\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":"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","names":["fs"]}
@@ -0,0 +1,123 @@
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
+
91
+ // src/sources/yaml.source.ts
92
+ import * as fs2 from "fs";
93
+ import * as yaml from "js-yaml";
94
+ var YamlSource = class extends BaseSource {
95
+ constructor(configPath) {
96
+ super();
97
+ this.configPath = configPath;
98
+ }
99
+ name = "yaml";
100
+ async collect() {
101
+ if (!fs2.existsSync(this.configPath)) {
102
+ this.warn(`config file not found at ${this.configPath}`);
103
+ return [];
104
+ }
105
+ const raw = yaml.load(fs2.readFileSync(this.configPath, "utf-8"));
106
+ const entries = raw?.manualCoverage ?? [];
107
+ if (entries.length === 0) {
108
+ this.log("no manual coverage entries found");
109
+ return [];
110
+ }
111
+ this.log(`found ${entries.length} manual coverage entries`);
112
+ return entries.map((entry) => ({
113
+ title: entry.route,
114
+ source: "yaml",
115
+ lastRun: entry.lastTested ? new Date(entry.lastTested) : void 0,
116
+ tags: entry.tester ? [entry.tester] : []
117
+ }));
118
+ }
119
+ };
120
+ export {
121
+ YamlSource
122
+ };
123
+ //# sourceMappingURL=yaml.source.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/logger.ts","../../src/sources/base.source.ts","../../src/sources/yaml.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","/**\n * src/sources/yaml.source.ts\n * Reads manual coverage entries from qualitylens.yaml\n */\n\nimport { BaseSource } from './base.source'\nimport { TestEntry } from '../core/types'\nimport * as fs from 'fs'\nimport * as yaml from 'js-yaml'\n\nexport class YamlSource extends BaseSource {\n readonly name = 'yaml'\n\n constructor(private configPath: string) {\n super()\n }\n\n async collect(): Promise<TestEntry[]> {\n if (!fs.existsSync(this.configPath)) {\n this.warn(`config file not found at ${this.configPath}`)\n return []\n }\n\n const raw = yaml.load(fs.readFileSync(this.configPath, 'utf-8')) as any\n const entries: Array<{ route: string; lastTested?: string; tester?: string }> =\n raw?.manualCoverage ?? []\n\n if (entries.length === 0) {\n this.log('no manual coverage entries found')\n return []\n }\n\n this.log(`found ${entries.length} manual coverage entries`)\n\n // The title is set to the route path itself so FuzzyMatcher's exactMatch\n // will always link this entry to the correct route without any ambiguity.\n return entries.map(entry => ({\n title: entry.route,\n source: 'yaml' as const,\n lastRun: entry.lastTested ? new Date(entry.lastTested) : undefined,\n tags: entry.tester ? [entry.tester] : [],\n }))\n }\n}\n"],"mappings":"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","names":["fs"]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * src/utils/http.ts
3
+ * Thin fetch wrapper for API calls (ADO, future sources).
4
+ */
5
+ declare function httpGet<T>(url: string, headers?: Record<string, string>): Promise<T>;
6
+
7
+ export { httpGet };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * src/utils/http.ts
3
+ * Thin fetch wrapper for API calls (ADO, future sources).
4
+ */
5
+ declare function httpGet<T>(url: string, headers?: Record<string, string>): Promise<T>;
6
+
7
+ export { httpGet };
@@ -0,0 +1,37 @@
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/utils/http.ts
21
+ var http_exports = {};
22
+ __export(http_exports, {
23
+ httpGet: () => httpGet
24
+ });
25
+ module.exports = __toCommonJS(http_exports);
26
+ async function httpGet(url, headers = {}) {
27
+ const response = await fetch(url, { headers });
28
+ if (!response.ok) {
29
+ throw new Error(`HTTP ${response.status} ${response.statusText} \u2014 ${url}`);
30
+ }
31
+ return response.json();
32
+ }
33
+ // Annotate the CommonJS export names for ESM import in node:
34
+ 0 && (module.exports = {
35
+ httpGet
36
+ });
37
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/http.ts"],"sourcesContent":["/**\n * src/utils/http.ts\n * Thin fetch wrapper for API calls (ADO, future sources).\n */\n\nexport async function httpGet<T>(url: string, headers: Record<string, string> = {}): Promise<T> {\n const response = await fetch(url, { headers })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} ${response.statusText} — ${url}`)\n }\n\n return response.json() as Promise<T>\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,eAAsB,QAAW,KAAa,UAAkC,CAAC,GAAe;AAC9F,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;AAE7C,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,IAAI,SAAS,UAAU,WAAM,GAAG,EAAE;AAAA,EAC3E;AAEA,SAAO,SAAS,KAAK;AACvB;","names":[]}
@@ -0,0 +1,12 @@
1
+ // src/utils/http.ts
2
+ async function httpGet(url, headers = {}) {
3
+ const response = await fetch(url, { headers });
4
+ if (!response.ok) {
5
+ throw new Error(`HTTP ${response.status} ${response.statusText} \u2014 ${url}`);
6
+ }
7
+ return response.json();
8
+ }
9
+ export {
10
+ httpGet
11
+ };
12
+ //# sourceMappingURL=http.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/http.ts"],"sourcesContent":["/**\n * src/utils/http.ts\n * Thin fetch wrapper for API calls (ADO, future sources).\n */\n\nexport async function httpGet<T>(url: string, headers: Record<string, string> = {}): Promise<T> {\n const response = await fetch(url, { headers })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} ${response.statusText} — ${url}`)\n }\n\n return response.json() as Promise<T>\n}\n"],"mappings":";AAKA,eAAsB,QAAW,KAAa,UAAkC,CAAC,GAAe;AAC9F,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;AAE7C,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,QAAQ,SAAS,MAAM,IAAI,SAAS,UAAU,WAAM,GAAG,EAAE;AAAA,EAC3E;AAEA,SAAO,SAAS,KAAK;AACvB;","names":[]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * src/utils/logger.ts
3
+ *
4
+ * Module-level singleton logger.
5
+ * - Always writes to a log file (qualitylens.log) with timestamps and full detail.
6
+ * - Console output is handled separately by each command — the logger only writes the file.
7
+ *
8
+ * Usage:
9
+ * import { logger } from './utils/logger'
10
+ * logger.configure('./reports') // call once at startup
11
+ * logger.info('scan started', { config: opts.config })
12
+ * logger.error('route discovery failed', err)
13
+ */
14
+ declare class Logger {
15
+ private logPath;
16
+ private fd;
17
+ /**
18
+ * Open (or create) qualitylens.log in the given directory.
19
+ * Appends to an existing log so multiple runs accumulate history.
20
+ * Call once at the start of each command.
21
+ */
22
+ configure(outputDir: string): void;
23
+ /** Path to the current log file, or null if not configured. */
24
+ get filePath(): string | null;
25
+ info(msg: string, detail?: unknown): void;
26
+ warn(msg: string, detail?: unknown): void;
27
+ /** Logs full detail (stack trace, raw error) to file only. */
28
+ error(msg: string, detail?: unknown): void;
29
+ debug(msg: string, detail?: unknown): void;
30
+ close(): void;
31
+ private write;
32
+ }
33
+ declare const logger: Logger;
34
+
35
+ export { logger };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * src/utils/logger.ts
3
+ *
4
+ * Module-level singleton logger.
5
+ * - Always writes to a log file (qualitylens.log) with timestamps and full detail.
6
+ * - Console output is handled separately by each command — the logger only writes the file.
7
+ *
8
+ * Usage:
9
+ * import { logger } from './utils/logger'
10
+ * logger.configure('./reports') // call once at startup
11
+ * logger.info('scan started', { config: opts.config })
12
+ * logger.error('route discovery failed', err)
13
+ */
14
+ declare class Logger {
15
+ private logPath;
16
+ private fd;
17
+ /**
18
+ * Open (or create) qualitylens.log in the given directory.
19
+ * Appends to an existing log so multiple runs accumulate history.
20
+ * Call once at the start of each command.
21
+ */
22
+ configure(outputDir: string): void;
23
+ /** Path to the current log file, or null if not configured. */
24
+ get filePath(): string | null;
25
+ info(msg: string, detail?: unknown): void;
26
+ warn(msg: string, detail?: unknown): void;
27
+ /** Logs full detail (stack trace, raw error) to file only. */
28
+ error(msg: string, detail?: unknown): void;
29
+ debug(msg: string, detail?: unknown): void;
30
+ close(): void;
31
+ private write;
32
+ }
33
+ declare const logger: Logger;
34
+
35
+ export { logger };
@@ -0,0 +1,114 @@
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/utils/logger.ts
31
+ var logger_exports = {};
32
+ __export(logger_exports, {
33
+ logger: () => logger
34
+ });
35
+ module.exports = __toCommonJS(logger_exports);
36
+ var fs = __toESM(require("fs"));
37
+ var path = __toESM(require("path"));
38
+ var Logger = class {
39
+ logPath = null;
40
+ fd = null;
41
+ /**
42
+ * Open (or create) qualitylens.log in the given directory.
43
+ * Appends to an existing log so multiple runs accumulate history.
44
+ * Call once at the start of each command.
45
+ */
46
+ configure(outputDir) {
47
+ try {
48
+ fs.mkdirSync(outputDir, { recursive: true });
49
+ this.logPath = path.join(outputDir, "qualitylens.log");
50
+ this.fd = fs.openSync(this.logPath, "a");
51
+ this.write("INFO", `--- qualitylens session started (pid ${process.pid}) ---`);
52
+ } catch {
53
+ this.logPath = null;
54
+ this.fd = null;
55
+ }
56
+ }
57
+ /** Path to the current log file, or null if not configured. */
58
+ get filePath() {
59
+ return this.logPath;
60
+ }
61
+ info(msg, detail) {
62
+ this.write("INFO", msg, detail);
63
+ }
64
+ warn(msg, detail) {
65
+ this.write("WARN", msg, detail);
66
+ }
67
+ /** Logs full detail (stack trace, raw error) to file only. */
68
+ error(msg, detail) {
69
+ this.write("ERROR", msg, detail);
70
+ }
71
+ debug(msg, detail) {
72
+ this.write("DEBUG", msg, detail);
73
+ }
74
+ close() {
75
+ if (this.fd !== null) {
76
+ try {
77
+ fs.closeSync(this.fd);
78
+ } catch {
79
+ }
80
+ this.fd = null;
81
+ }
82
+ }
83
+ write(level, msg, detail) {
84
+ if (this.fd === null) return;
85
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
86
+ let line = `[${ts}] ${level.padEnd(5)} ${msg}`;
87
+ if (detail !== void 0) {
88
+ if (detail instanceof Error) {
89
+ line += `
90
+ ${detail.message}`;
91
+ if (detail.stack) {
92
+ line += "\n" + detail.stack.split("\n").map((l) => " " + l).join("\n");
93
+ }
94
+ } else if (typeof detail === "string" && detail.trim()) {
95
+ line += "\n" + detail.split("\n").map((l) => " " + l).join("\n");
96
+ } else if (typeof detail === "object") {
97
+ try {
98
+ line += "\n " + JSON.stringify(detail, null, 2).replace(/\n/g, "\n ");
99
+ } catch {
100
+ }
101
+ }
102
+ }
103
+ try {
104
+ fs.writeSync(this.fd, line + "\n");
105
+ } catch {
106
+ }
107
+ }
108
+ };
109
+ var logger = new Logger();
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ logger
113
+ });
114
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/logger.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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,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;","names":[]}