@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.
- package/dist/core/config.d.mts +26 -0
- package/dist/core/config.d.ts +26 -0
- package/dist/core/config.js +139 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.mjs +104 -0
- package/dist/core/config.mjs.map +1 -0
- package/dist/core/detector.d.mts +43 -0
- package/dist/core/detector.d.ts +43 -0
- package/dist/core/detector.js +431 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/detector.mjs +395 -0
- package/dist/core/detector.mjs.map +1 -0
- package/dist/core/engine.d.mts +29 -0
- package/dist/core/engine.d.ts +29 -0
- package/dist/core/engine.js +151 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/engine.mjs +126 -0
- package/dist/core/engine.mjs.map +1 -0
- package/dist/core/types.d.mts +109 -0
- package/dist/core/types.d.ts +109 -0
- package/dist/core/types.js +19 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/types.mjs +1 -0
- package/dist/core/types.mjs.map +1 -0
- package/dist/matchers/area.matcher.d.mts +38 -0
- package/dist/matchers/area.matcher.d.ts +38 -0
- package/dist/matchers/area.matcher.js +102 -0
- package/dist/matchers/area.matcher.js.map +1 -0
- package/dist/matchers/area.matcher.mjs +77 -0
- package/dist/matchers/area.matcher.mjs.map +1 -0
- package/dist/matchers/fuzzy.matcher.d.mts +83 -0
- package/dist/matchers/fuzzy.matcher.d.ts +83 -0
- package/dist/matchers/fuzzy.matcher.js +161 -0
- package/dist/matchers/fuzzy.matcher.js.map +1 -0
- package/dist/matchers/fuzzy.matcher.mjs +136 -0
- package/dist/matchers/fuzzy.matcher.mjs.map +1 -0
- package/dist/reporters/base.reporter.d.mts +19 -0
- package/dist/reporters/base.reporter.d.ts +19 -0
- package/dist/reporters/base.reporter.js +32 -0
- package/dist/reporters/base.reporter.js.map +1 -0
- package/dist/reporters/base.reporter.mjs +7 -0
- package/dist/reporters/base.reporter.mjs.map +1 -0
- package/dist/reporters/console.reporter.d.mts +16 -0
- package/dist/reporters/console.reporter.d.ts +16 -0
- package/dist/reporters/console.reporter.js +130 -0
- package/dist/reporters/console.reporter.js.map +1 -0
- package/dist/reporters/console.reporter.mjs +95 -0
- package/dist/reporters/console.reporter.mjs.map +1 -0
- package/dist/sources/base.source.d.mts +22 -0
- package/dist/sources/base.source.d.ts +22 -0
- package/dist/sources/base.source.js +130 -0
- package/dist/sources/base.source.js.map +1 -0
- package/dist/sources/base.source.mjs +93 -0
- package/dist/sources/base.source.mjs.map +1 -0
- package/dist/sources/playwright.source.d.mts +34 -0
- package/dist/sources/playwright.source.d.ts +34 -0
- package/dist/sources/playwright.source.js +209 -0
- package/dist/sources/playwright.source.js.map +1 -0
- package/dist/sources/playwright.source.mjs +172 -0
- package/dist/sources/playwright.source.mjs.map +1 -0
- package/dist/sources/routes.source.d.mts +58 -0
- package/dist/sources/routes.source.d.ts +58 -0
- package/dist/sources/routes.source.js +288 -0
- package/dist/sources/routes.source.js.map +1 -0
- package/dist/sources/routes.source.mjs +251 -0
- package/dist/sources/routes.source.mjs.map +1 -0
- package/dist/sources/yaml.source.d.mts +16 -0
- package/dist/sources/yaml.source.d.ts +16 -0
- package/dist/sources/yaml.source.js +160 -0
- package/dist/sources/yaml.source.js.map +1 -0
- package/dist/sources/yaml.source.mjs +123 -0
- package/dist/sources/yaml.source.mjs.map +1 -0
- package/dist/utils/http.d.mts +7 -0
- package/dist/utils/http.d.ts +7 -0
- package/dist/utils/http.js +37 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/http.mjs +12 -0
- package/dist/utils/http.mjs.map +1 -0
- package/dist/utils/logger.d.mts +35 -0
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.js +114 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.mjs +79 -0
- package/dist/utils/logger.mjs.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TestGapConfig } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/config.ts
|
|
5
|
+
* Loads and validates qualitylens.yaml. Generates starter config for `qualitylens init`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface StarterOptions {
|
|
9
|
+
projectName: string;
|
|
10
|
+
routeType: 'nextjs' | 'openapi' | 'express' | 'manual';
|
|
11
|
+
routesPath: string;
|
|
12
|
+
generate?: string | null;
|
|
13
|
+
omitGenerate?: boolean;
|
|
14
|
+
basePath?: string | null;
|
|
15
|
+
testsPath?: string | null;
|
|
16
|
+
areas: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
patterns: string[];
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
declare class Config {
|
|
22
|
+
static load(filePath: string): TestGapConfig;
|
|
23
|
+
static generateStarter(opts: StarterOptions): string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Config, type StarterOptions };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TestGapConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/config.ts
|
|
5
|
+
* Loads and validates qualitylens.yaml. Generates starter config for `qualitylens init`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface StarterOptions {
|
|
9
|
+
projectName: string;
|
|
10
|
+
routeType: 'nextjs' | 'openapi' | 'express' | 'manual';
|
|
11
|
+
routesPath: string;
|
|
12
|
+
generate?: string | null;
|
|
13
|
+
omitGenerate?: boolean;
|
|
14
|
+
basePath?: string | null;
|
|
15
|
+
testsPath?: string | null;
|
|
16
|
+
areas: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
patterns: string[];
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
declare class Config {
|
|
22
|
+
static load(filePath: string): TestGapConfig;
|
|
23
|
+
static generateStarter(opts: StarterOptions): string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Config, type StarterOptions };
|
|
@@ -0,0 +1,139 @@
|
|
|
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/core/config.ts
|
|
31
|
+
var config_exports = {};
|
|
32
|
+
__export(config_exports, {
|
|
33
|
+
Config: () => Config
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(config_exports);
|
|
36
|
+
var fs = __toESM(require("fs"));
|
|
37
|
+
var yaml = __toESM(require("js-yaml"));
|
|
38
|
+
var Config = class {
|
|
39
|
+
static load(filePath) {
|
|
40
|
+
if (!fs.existsSync(filePath)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`qualitylens.yaml not found at ${filePath}
|
|
43
|
+
Run 'qualitylens init' to create one.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const raw = yaml.load(fs.readFileSync(filePath, "utf-8"));
|
|
47
|
+
if (!raw.projectName) throw new Error("qualitylens.yaml: projectName is required");
|
|
48
|
+
if (!raw.routes) throw new Error("qualitylens.yaml: routes is required");
|
|
49
|
+
if (!raw.routes.type) throw new Error("qualitylens.yaml: routes.type is required");
|
|
50
|
+
if (!raw.routes.path) throw new Error("qualitylens.yaml: routes.path is required");
|
|
51
|
+
if (!raw.areas || raw.areas.length === 0) {
|
|
52
|
+
throw new Error("qualitylens.yaml: at least one area is required");
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...raw,
|
|
56
|
+
projectName: raw.projectName,
|
|
57
|
+
routes: raw.routes,
|
|
58
|
+
areas: raw.areas,
|
|
59
|
+
staleThresholdDays: raw.staleThresholdDays ?? 30,
|
|
60
|
+
manualCoverage: raw.manualCoverage ?? [],
|
|
61
|
+
thresholds: raw.thresholds ?? [],
|
|
62
|
+
testHints: raw.testHints ?? []
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
static generateStarter(opts) {
|
|
66
|
+
const areasYaml = opts.areas.length > 0 ? opts.areas.map((a) => [
|
|
67
|
+
` - name: ${a.name}`,
|
|
68
|
+
` patterns:`,
|
|
69
|
+
...a.patterns.map((p) => ` - ${p}`)
|
|
70
|
+
].join("\n")).join("\n\n") : [
|
|
71
|
+
` - name: API`,
|
|
72
|
+
` patterns:`,
|
|
73
|
+
` - /api`
|
|
74
|
+
].join("\n");
|
|
75
|
+
const generateLine = opts.omitGenerate ? null : opts.generate ? ` generate: ${opts.generate} # runs before route discovery to produce openapi.json` : ` # generate: npm run generate:openapi # uncomment if your spec is generated at build time`;
|
|
76
|
+
const basePathLine = opts.basePath ? ` basePath: ${opts.basePath} # prefix stripped when grouping routes into areas` : ` # basePath: /api # uncomment and set if your routes share a common prefix (e.g. /api, /api/v1)`;
|
|
77
|
+
const testsSection = opts.testsPath ? `
|
|
78
|
+
tests:
|
|
79
|
+
path: ${opts.testsPath} # directory scanned for *.spec.ts / *.test.ts
|
|
80
|
+
` : `
|
|
81
|
+
# tests:
|
|
82
|
+
# path: ./e2e # uncomment to specify a test directory (default: same folder as this file)
|
|
83
|
+
`;
|
|
84
|
+
return `# qualitylens.yaml \u2014 generated by 'qualitylens init'
|
|
85
|
+
# Run 'qualitylens scan' to generate your first coverage report.
|
|
86
|
+
|
|
87
|
+
projectName: ${opts.projectName}
|
|
88
|
+
|
|
89
|
+
# Days before a manual test entry is flagged as stale
|
|
90
|
+
staleThresholdDays: 30
|
|
91
|
+
|
|
92
|
+
routes:
|
|
93
|
+
type: ${opts.routeType}
|
|
94
|
+
path: ${opts.routesPath}
|
|
95
|
+
${generateLine != null ? generateLine + "\n" : ""}${basePathLine}
|
|
96
|
+
${testsSection}
|
|
97
|
+
areas:
|
|
98
|
+
${areasYaml}
|
|
99
|
+
|
|
100
|
+
# Optional: Azure DevOps Test Plans integration
|
|
101
|
+
# ado:
|
|
102
|
+
# orgUrl: https://dev.azure.com/your-org
|
|
103
|
+
# project: YourProject
|
|
104
|
+
# planId: 42
|
|
105
|
+
# patEnvVar: ADO_PAT
|
|
106
|
+
|
|
107
|
+
# Optional: manual coverage entries
|
|
108
|
+
# manualCoverage:
|
|
109
|
+
# - route: /your-route
|
|
110
|
+
# lastTested: 2026-01-01
|
|
111
|
+
# tester: your-name
|
|
112
|
+
|
|
113
|
+
# Optional: CI gates
|
|
114
|
+
# thresholds:
|
|
115
|
+
# - area: "*"
|
|
116
|
+
# minCoverage: 50
|
|
117
|
+
|
|
118
|
+
# Optional: test hints (Pro) \u2014 resolve orphaned tests, flag route gaps, acknowledge stale tests
|
|
119
|
+
# Run 'qualitylens hint add' or 'qualitylens orphans' to manage this section
|
|
120
|
+
# testHints:
|
|
121
|
+
# - test: "Sign out after registration"
|
|
122
|
+
# status: resolved # resolved | gap | acknowledged | ignore
|
|
123
|
+
# route: /login
|
|
124
|
+
# reason: Sign out is part of the authentication flow
|
|
125
|
+
# confidence: high
|
|
126
|
+
# resolvedBy: agent
|
|
127
|
+
# - test: "DELETE /users/:id returns 404"
|
|
128
|
+
# status: gap
|
|
129
|
+
# route: /users/:id
|
|
130
|
+
# method: DELETE
|
|
131
|
+
# reason: Route not in OpenAPI spec \u2014 missing implementation or undocumented
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
136
|
+
0 && (module.exports = {
|
|
137
|
+
Config
|
|
138
|
+
});
|
|
139
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["/**\n * src/core/config.ts\n * Loads and validates qualitylens.yaml. Generates starter config for `qualitylens init`.\n */\n\nimport * as fs from 'fs'\nimport * as yaml from 'js-yaml'\nimport { TestGapConfig } from './types'\n\nexport interface StarterOptions {\n projectName: string\n routeType: 'nextjs' | 'openapi' | 'express' | 'manual'\n routesPath: string\n generate?: string | null\n omitGenerate?: boolean // true = spec file already exists, skip generate line entirely\n basePath?: string | null\n testsPath?: string | null\n areas: Array<{ name: string; patterns: string[] }>\n}\n\nexport class Config {\n static load(filePath: string): TestGapConfig {\n if (!fs.existsSync(filePath)) {\n throw new Error(\n `qualitylens.yaml not found at ${filePath}\\n` +\n `Run 'qualitylens init' to create one.`\n )\n }\n\n const raw = yaml.load(fs.readFileSync(filePath, 'utf-8')) as Partial<TestGapConfig>\n\n // Validate required fields\n if (!raw.projectName) throw new Error('qualitylens.yaml: projectName is required')\n if (!raw.routes) throw new Error('qualitylens.yaml: routes is required')\n if (!raw.routes.type) throw new Error('qualitylens.yaml: routes.type is required')\n if (!raw.routes.path) throw new Error('qualitylens.yaml: routes.path is required')\n if (!raw.areas || raw.areas.length === 0) {\n throw new Error('qualitylens.yaml: at least one area is required')\n }\n\n // Apply defaults\n return {\n ...raw,\n projectName: raw.projectName,\n routes: raw.routes,\n areas: raw.areas,\n staleThresholdDays: raw.staleThresholdDays ?? 30,\n manualCoverage: raw.manualCoverage ?? [],\n thresholds: raw.thresholds ?? [],\n testHints: raw.testHints ?? [],\n }\n }\n\n static generateStarter(opts: StarterOptions): string {\n const areasYaml = opts.areas.length > 0\n ? opts.areas.map(a => [\n ` - name: ${a.name}`,\n ` patterns:`,\n ...a.patterns.map(p => ` - ${p}`),\n ].join('\\n')).join('\\n\\n')\n : [\n ` - name: API`,\n ` patterns:`,\n ` - /api`,\n ].join('\\n')\n\n const generateLine = opts.omitGenerate\n ? null\n : opts.generate\n ? ` generate: ${opts.generate} # runs before route discovery to produce openapi.json`\n : ` # generate: npm run generate:openapi # uncomment if your spec is generated at build time`\n\n const basePathLine = opts.basePath\n ? ` basePath: ${opts.basePath} # prefix stripped when grouping routes into areas`\n : ` # basePath: /api # uncomment and set if your routes share a common prefix (e.g. /api, /api/v1)`\n\n const testsSection = opts.testsPath\n ? `\\ntests:\\n path: ${opts.testsPath} # directory scanned for *.spec.ts / *.test.ts\\n`\n : `\\n# tests:\\n# path: ./e2e # uncomment to specify a test directory (default: same folder as this file)\\n`\n\n return `# qualitylens.yaml — generated by 'qualitylens init'\n# Run 'qualitylens scan' to generate your first coverage report.\n\nprojectName: ${opts.projectName}\n\n# Days before a manual test entry is flagged as stale\nstaleThresholdDays: 30\n\nroutes:\n type: ${opts.routeType}\n path: ${opts.routesPath}\n${generateLine != null ? generateLine + '\\n' : ''}${basePathLine}\n${testsSection}\nareas:\n${areasYaml}\n\n# Optional: Azure DevOps Test Plans integration\n# ado:\n# orgUrl: https://dev.azure.com/your-org\n# project: YourProject\n# planId: 42\n# patEnvVar: ADO_PAT\n\n# Optional: manual coverage entries\n# manualCoverage:\n# - route: /your-route\n# lastTested: 2026-01-01\n# tester: your-name\n\n# Optional: CI gates\n# thresholds:\n# - area: \"*\"\n# minCoverage: 50\n\n# Optional: test hints (Pro) — resolve orphaned tests, flag route gaps, acknowledge stale tests\n# Run 'qualitylens hint add' or 'qualitylens orphans' to manage this section\n# testHints:\n# - test: \"Sign out after registration\"\n# status: resolved # resolved | gap | acknowledged | ignore\n# route: /login\n# reason: Sign out is part of the authentication flow\n# confidence: high\n# resolvedBy: agent\n# - test: \"DELETE /users/:id returns 404\"\n# status: gap\n# route: /users/:id\n# method: DELETE\n# reason: Route not in OpenAPI spec — missing implementation or undocumented\n`\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAoB;AACpB,WAAsB;AAcf,IAAM,SAAN,MAAa;AAAA,EAClB,OAAO,KAAK,UAAiC;AAC3C,QAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,iCAAiC,QAAQ;AAAA;AAAA,MAE3C;AAAA,IACF;AAEA,UAAM,MAAW,UAAQ,gBAAa,UAAU,OAAO,CAAC;AAGxD,QAAI,CAAC,IAAI,YAAa,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,OAAQ,OAAM,IAAI,MAAM,sCAAsC;AACvE,QAAI,CAAC,IAAI,OAAO,KAAM,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,OAAO,KAAM,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,SAAS,IAAI,MAAM,WAAW,GAAG;AACxC,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAGA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,aAAa,IAAI;AAAA,MACjB,QAAQ,IAAI;AAAA,MACZ,OAAO,IAAI;AAAA,MACX,oBAAoB,IAAI,sBAAsB;AAAA,MAC9C,gBAAgB,IAAI,kBAAkB,CAAC;AAAA,MACvC,YAAY,IAAI,cAAc,CAAC;AAAA,MAC/B,WAAW,IAAI,aAAa,CAAC;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,OAAO,gBAAgB,MAA8B;AACnD,UAAM,YAAY,KAAK,MAAM,SAAS,IAClC,KAAK,MAAM,IAAI,OAAK;AAAA,MAClB,aAAa,EAAE,IAAI;AAAA,MACnB;AAAA,MACA,GAAG,EAAE,SAAS,IAAI,OAAK,WAAW,CAAC,EAAE;AAAA,IACvC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,MAAM,IACzB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAEf,UAAM,eAAe,KAAK,eACtB,OACA,KAAK,WACH,eAAe,KAAK,QAAQ,6DAC5B;AAEN,UAAM,eAAe,KAAK,WACtB,eAAe,KAAK,QAAQ,yDAC5B;AAEJ,UAAM,eAAe,KAAK,YACtB;AAAA;AAAA,UAAqB,KAAK,SAAS;AAAA,IACnC;AAAA;AAAA;AAAA;AAEJ,WAAO;AAAA;AAAA;AAAA,eAGI,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMrB,KAAK,SAAS;AAAA,UACd,KAAK,UAAU;AAAA,EACvB,gBAAgB,OAAO,eAAe,OAAO,EAAE,GAAG,YAAY;AAAA,EAC9D,YAAY;AAAA;AAAA,EAEZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCT;AACF;","names":[]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as yaml from "js-yaml";
|
|
4
|
+
var Config = class {
|
|
5
|
+
static load(filePath) {
|
|
6
|
+
if (!fs.existsSync(filePath)) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
`qualitylens.yaml not found at ${filePath}
|
|
9
|
+
Run 'qualitylens init' to create one.`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
const raw = yaml.load(fs.readFileSync(filePath, "utf-8"));
|
|
13
|
+
if (!raw.projectName) throw new Error("qualitylens.yaml: projectName is required");
|
|
14
|
+
if (!raw.routes) throw new Error("qualitylens.yaml: routes is required");
|
|
15
|
+
if (!raw.routes.type) throw new Error("qualitylens.yaml: routes.type is required");
|
|
16
|
+
if (!raw.routes.path) throw new Error("qualitylens.yaml: routes.path is required");
|
|
17
|
+
if (!raw.areas || raw.areas.length === 0) {
|
|
18
|
+
throw new Error("qualitylens.yaml: at least one area is required");
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...raw,
|
|
22
|
+
projectName: raw.projectName,
|
|
23
|
+
routes: raw.routes,
|
|
24
|
+
areas: raw.areas,
|
|
25
|
+
staleThresholdDays: raw.staleThresholdDays ?? 30,
|
|
26
|
+
manualCoverage: raw.manualCoverage ?? [],
|
|
27
|
+
thresholds: raw.thresholds ?? [],
|
|
28
|
+
testHints: raw.testHints ?? []
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
static generateStarter(opts) {
|
|
32
|
+
const areasYaml = opts.areas.length > 0 ? opts.areas.map((a) => [
|
|
33
|
+
` - name: ${a.name}`,
|
|
34
|
+
` patterns:`,
|
|
35
|
+
...a.patterns.map((p) => ` - ${p}`)
|
|
36
|
+
].join("\n")).join("\n\n") : [
|
|
37
|
+
` - name: API`,
|
|
38
|
+
` patterns:`,
|
|
39
|
+
` - /api`
|
|
40
|
+
].join("\n");
|
|
41
|
+
const generateLine = opts.omitGenerate ? null : opts.generate ? ` generate: ${opts.generate} # runs before route discovery to produce openapi.json` : ` # generate: npm run generate:openapi # uncomment if your spec is generated at build time`;
|
|
42
|
+
const basePathLine = opts.basePath ? ` basePath: ${opts.basePath} # prefix stripped when grouping routes into areas` : ` # basePath: /api # uncomment and set if your routes share a common prefix (e.g. /api, /api/v1)`;
|
|
43
|
+
const testsSection = opts.testsPath ? `
|
|
44
|
+
tests:
|
|
45
|
+
path: ${opts.testsPath} # directory scanned for *.spec.ts / *.test.ts
|
|
46
|
+
` : `
|
|
47
|
+
# tests:
|
|
48
|
+
# path: ./e2e # uncomment to specify a test directory (default: same folder as this file)
|
|
49
|
+
`;
|
|
50
|
+
return `# qualitylens.yaml \u2014 generated by 'qualitylens init'
|
|
51
|
+
# Run 'qualitylens scan' to generate your first coverage report.
|
|
52
|
+
|
|
53
|
+
projectName: ${opts.projectName}
|
|
54
|
+
|
|
55
|
+
# Days before a manual test entry is flagged as stale
|
|
56
|
+
staleThresholdDays: 30
|
|
57
|
+
|
|
58
|
+
routes:
|
|
59
|
+
type: ${opts.routeType}
|
|
60
|
+
path: ${opts.routesPath}
|
|
61
|
+
${generateLine != null ? generateLine + "\n" : ""}${basePathLine}
|
|
62
|
+
${testsSection}
|
|
63
|
+
areas:
|
|
64
|
+
${areasYaml}
|
|
65
|
+
|
|
66
|
+
# Optional: Azure DevOps Test Plans integration
|
|
67
|
+
# ado:
|
|
68
|
+
# orgUrl: https://dev.azure.com/your-org
|
|
69
|
+
# project: YourProject
|
|
70
|
+
# planId: 42
|
|
71
|
+
# patEnvVar: ADO_PAT
|
|
72
|
+
|
|
73
|
+
# Optional: manual coverage entries
|
|
74
|
+
# manualCoverage:
|
|
75
|
+
# - route: /your-route
|
|
76
|
+
# lastTested: 2026-01-01
|
|
77
|
+
# tester: your-name
|
|
78
|
+
|
|
79
|
+
# Optional: CI gates
|
|
80
|
+
# thresholds:
|
|
81
|
+
# - area: "*"
|
|
82
|
+
# minCoverage: 50
|
|
83
|
+
|
|
84
|
+
# Optional: test hints (Pro) \u2014 resolve orphaned tests, flag route gaps, acknowledge stale tests
|
|
85
|
+
# Run 'qualitylens hint add' or 'qualitylens orphans' to manage this section
|
|
86
|
+
# testHints:
|
|
87
|
+
# - test: "Sign out after registration"
|
|
88
|
+
# status: resolved # resolved | gap | acknowledged | ignore
|
|
89
|
+
# route: /login
|
|
90
|
+
# reason: Sign out is part of the authentication flow
|
|
91
|
+
# confidence: high
|
|
92
|
+
# resolvedBy: agent
|
|
93
|
+
# - test: "DELETE /users/:id returns 404"
|
|
94
|
+
# status: gap
|
|
95
|
+
# route: /users/:id
|
|
96
|
+
# method: DELETE
|
|
97
|
+
# reason: Route not in OpenAPI spec \u2014 missing implementation or undocumented
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
export {
|
|
102
|
+
Config
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["/**\n * src/core/config.ts\n * Loads and validates qualitylens.yaml. Generates starter config for `qualitylens init`.\n */\n\nimport * as fs from 'fs'\nimport * as yaml from 'js-yaml'\nimport { TestGapConfig } from './types'\n\nexport interface StarterOptions {\n projectName: string\n routeType: 'nextjs' | 'openapi' | 'express' | 'manual'\n routesPath: string\n generate?: string | null\n omitGenerate?: boolean // true = spec file already exists, skip generate line entirely\n basePath?: string | null\n testsPath?: string | null\n areas: Array<{ name: string; patterns: string[] }>\n}\n\nexport class Config {\n static load(filePath: string): TestGapConfig {\n if (!fs.existsSync(filePath)) {\n throw new Error(\n `qualitylens.yaml not found at ${filePath}\\n` +\n `Run 'qualitylens init' to create one.`\n )\n }\n\n const raw = yaml.load(fs.readFileSync(filePath, 'utf-8')) as Partial<TestGapConfig>\n\n // Validate required fields\n if (!raw.projectName) throw new Error('qualitylens.yaml: projectName is required')\n if (!raw.routes) throw new Error('qualitylens.yaml: routes is required')\n if (!raw.routes.type) throw new Error('qualitylens.yaml: routes.type is required')\n if (!raw.routes.path) throw new Error('qualitylens.yaml: routes.path is required')\n if (!raw.areas || raw.areas.length === 0) {\n throw new Error('qualitylens.yaml: at least one area is required')\n }\n\n // Apply defaults\n return {\n ...raw,\n projectName: raw.projectName,\n routes: raw.routes,\n areas: raw.areas,\n staleThresholdDays: raw.staleThresholdDays ?? 30,\n manualCoverage: raw.manualCoverage ?? [],\n thresholds: raw.thresholds ?? [],\n testHints: raw.testHints ?? [],\n }\n }\n\n static generateStarter(opts: StarterOptions): string {\n const areasYaml = opts.areas.length > 0\n ? opts.areas.map(a => [\n ` - name: ${a.name}`,\n ` patterns:`,\n ...a.patterns.map(p => ` - ${p}`),\n ].join('\\n')).join('\\n\\n')\n : [\n ` - name: API`,\n ` patterns:`,\n ` - /api`,\n ].join('\\n')\n\n const generateLine = opts.omitGenerate\n ? null\n : opts.generate\n ? ` generate: ${opts.generate} # runs before route discovery to produce openapi.json`\n : ` # generate: npm run generate:openapi # uncomment if your spec is generated at build time`\n\n const basePathLine = opts.basePath\n ? ` basePath: ${opts.basePath} # prefix stripped when grouping routes into areas`\n : ` # basePath: /api # uncomment and set if your routes share a common prefix (e.g. /api, /api/v1)`\n\n const testsSection = opts.testsPath\n ? `\\ntests:\\n path: ${opts.testsPath} # directory scanned for *.spec.ts / *.test.ts\\n`\n : `\\n# tests:\\n# path: ./e2e # uncomment to specify a test directory (default: same folder as this file)\\n`\n\n return `# qualitylens.yaml — generated by 'qualitylens init'\n# Run 'qualitylens scan' to generate your first coverage report.\n\nprojectName: ${opts.projectName}\n\n# Days before a manual test entry is flagged as stale\nstaleThresholdDays: 30\n\nroutes:\n type: ${opts.routeType}\n path: ${opts.routesPath}\n${generateLine != null ? generateLine + '\\n' : ''}${basePathLine}\n${testsSection}\nareas:\n${areasYaml}\n\n# Optional: Azure DevOps Test Plans integration\n# ado:\n# orgUrl: https://dev.azure.com/your-org\n# project: YourProject\n# planId: 42\n# patEnvVar: ADO_PAT\n\n# Optional: manual coverage entries\n# manualCoverage:\n# - route: /your-route\n# lastTested: 2026-01-01\n# tester: your-name\n\n# Optional: CI gates\n# thresholds:\n# - area: \"*\"\n# minCoverage: 50\n\n# Optional: test hints (Pro) — resolve orphaned tests, flag route gaps, acknowledge stale tests\n# Run 'qualitylens hint add' or 'qualitylens orphans' to manage this section\n# testHints:\n# - test: \"Sign out after registration\"\n# status: resolved # resolved | gap | acknowledged | ignore\n# route: /login\n# reason: Sign out is part of the authentication flow\n# confidence: high\n# resolvedBy: agent\n# - test: \"DELETE /users/:id returns 404\"\n# status: gap\n# route: /users/:id\n# method: DELETE\n# reason: Route not in OpenAPI spec — missing implementation or undocumented\n`\n }\n}\n"],"mappings":";AAKA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAcf,IAAM,SAAN,MAAa;AAAA,EAClB,OAAO,KAAK,UAAiC;AAC3C,QAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,iCAAiC,QAAQ;AAAA;AAAA,MAE3C;AAAA,IACF;AAEA,UAAM,MAAW,UAAQ,gBAAa,UAAU,OAAO,CAAC;AAGxD,QAAI,CAAC,IAAI,YAAa,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,OAAQ,OAAM,IAAI,MAAM,sCAAsC;AACvE,QAAI,CAAC,IAAI,OAAO,KAAM,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,OAAO,KAAM,OAAM,IAAI,MAAM,2CAA2C;AACjF,QAAI,CAAC,IAAI,SAAS,IAAI,MAAM,WAAW,GAAG;AACxC,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAGA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,aAAa,IAAI;AAAA,MACjB,QAAQ,IAAI;AAAA,MACZ,OAAO,IAAI;AAAA,MACX,oBAAoB,IAAI,sBAAsB;AAAA,MAC9C,gBAAgB,IAAI,kBAAkB,CAAC;AAAA,MACvC,YAAY,IAAI,cAAc,CAAC;AAAA,MAC/B,WAAW,IAAI,aAAa,CAAC;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,OAAO,gBAAgB,MAA8B;AACnD,UAAM,YAAY,KAAK,MAAM,SAAS,IAClC,KAAK,MAAM,IAAI,OAAK;AAAA,MAClB,aAAa,EAAE,IAAI;AAAA,MACnB;AAAA,MACA,GAAG,EAAE,SAAS,IAAI,OAAK,WAAW,CAAC,EAAE;AAAA,IACvC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,MAAM,IACzB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAEf,UAAM,eAAe,KAAK,eACtB,OACA,KAAK,WACH,eAAe,KAAK,QAAQ,6DAC5B;AAEN,UAAM,eAAe,KAAK,WACtB,eAAe,KAAK,QAAQ,yDAC5B;AAEJ,UAAM,eAAe,KAAK,YACtB;AAAA;AAAA,UAAqB,KAAK,SAAS;AAAA,IACnC;AAAA;AAAA;AAAA;AAEJ,WAAO;AAAA;AAAA;AAAA,eAGI,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMrB,KAAK,SAAS;AAAA,UACd,KAAK,UAAU;AAAA,EACvB,gBAAgB,OAAO,eAAe,OAAO,EAAE,GAAG,YAAY;AAAA,EAC9D,YAAY;AAAA;AAAA,EAEZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCT;AACF;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AppRoute } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/detector.ts
|
|
5
|
+
*
|
|
6
|
+
* Auto-detection logic for `qualitylens init`.
|
|
7
|
+
* Inspects a target directory and returns everything it can infer:
|
|
8
|
+
* framework, route type, routes path, discovered routes, and suggested areas.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here writes files or prompts the user — it only observes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type Framework = 'nextjs' | 'express-openapi' | 'express' | 'unknown';
|
|
14
|
+
type RouteType = 'nextjs' | 'openapi' | 'express' | 'manual';
|
|
15
|
+
interface SuggestedArea {
|
|
16
|
+
suggestedName: string;
|
|
17
|
+
routes: AppRoute[];
|
|
18
|
+
patterns: string[];
|
|
19
|
+
}
|
|
20
|
+
interface DetectionResult {
|
|
21
|
+
projectName: string;
|
|
22
|
+
framework: Framework;
|
|
23
|
+
routeType: RouteType;
|
|
24
|
+
routesPath: string | null;
|
|
25
|
+
testsPath: string | null;
|
|
26
|
+
generate: string | null;
|
|
27
|
+
basePath: string | null;
|
|
28
|
+
hasSwaggerJsdoc: boolean;
|
|
29
|
+
hasPlaywright: boolean;
|
|
30
|
+
discoveredRoutes: AppRoute[];
|
|
31
|
+
suggestedAreas: SuggestedArea[];
|
|
32
|
+
}
|
|
33
|
+
declare function detect(targetDir: string): Promise<DetectionResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Groups routes by their first meaningful path segment and maps each group
|
|
36
|
+
* to a human-readable area name.
|
|
37
|
+
* basePath (e.g. /api, /api/v1) is stripped per-route before grouping so routes
|
|
38
|
+
* are organised by service name rather than the shared prefix.
|
|
39
|
+
* Exported so the init command can re-run it after the user confirms/changes basePath.
|
|
40
|
+
*/
|
|
41
|
+
declare function suggestAreas(routes: AppRoute[], basePath: string | null): SuggestedArea[];
|
|
42
|
+
|
|
43
|
+
export { type DetectionResult, type Framework, type RouteType, type SuggestedArea, detect, suggestAreas };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AppRoute } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/core/detector.ts
|
|
5
|
+
*
|
|
6
|
+
* Auto-detection logic for `qualitylens init`.
|
|
7
|
+
* Inspects a target directory and returns everything it can infer:
|
|
8
|
+
* framework, route type, routes path, discovered routes, and suggested areas.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here writes files or prompts the user — it only observes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
type Framework = 'nextjs' | 'express-openapi' | 'express' | 'unknown';
|
|
14
|
+
type RouteType = 'nextjs' | 'openapi' | 'express' | 'manual';
|
|
15
|
+
interface SuggestedArea {
|
|
16
|
+
suggestedName: string;
|
|
17
|
+
routes: AppRoute[];
|
|
18
|
+
patterns: string[];
|
|
19
|
+
}
|
|
20
|
+
interface DetectionResult {
|
|
21
|
+
projectName: string;
|
|
22
|
+
framework: Framework;
|
|
23
|
+
routeType: RouteType;
|
|
24
|
+
routesPath: string | null;
|
|
25
|
+
testsPath: string | null;
|
|
26
|
+
generate: string | null;
|
|
27
|
+
basePath: string | null;
|
|
28
|
+
hasSwaggerJsdoc: boolean;
|
|
29
|
+
hasPlaywright: boolean;
|
|
30
|
+
discoveredRoutes: AppRoute[];
|
|
31
|
+
suggestedAreas: SuggestedArea[];
|
|
32
|
+
}
|
|
33
|
+
declare function detect(targetDir: string): Promise<DetectionResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Groups routes by their first meaningful path segment and maps each group
|
|
36
|
+
* to a human-readable area name.
|
|
37
|
+
* basePath (e.g. /api, /api/v1) is stripped per-route before grouping so routes
|
|
38
|
+
* are organised by service name rather than the shared prefix.
|
|
39
|
+
* Exported so the init command can re-run it after the user confirms/changes basePath.
|
|
40
|
+
*/
|
|
41
|
+
declare function suggestAreas(routes: AppRoute[], basePath: string | null): SuggestedArea[];
|
|
42
|
+
|
|
43
|
+
export { type DetectionResult, type Framework, type RouteType, type SuggestedArea, detect, suggestAreas };
|