mejora 2.3.2 → 2.3.3
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/check-worker.d.mts +13 -0
- package/dist/check-worker.mjs +1 -0
- package/dist/index.d.mts +1 -314
- package/dist/index.mjs +1 -1
- package/dist/registry-DhjvvQpC.mjs +1 -0
- package/dist/run.mjs +20 -20
- package/dist/types-B1hGuB2T.d.mts +317 -0
- package/package.json +4 -1
- /package/dist/{typescript-C0a92mCs.mjs → typescript-C4WyXHBh.mjs} +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { a as RawSnapshot } from "./types-B1hGuB2T.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/check-worker.d.ts
|
|
4
|
+
declare function run({
|
|
5
|
+
checkId
|
|
6
|
+
}: {
|
|
7
|
+
checkId: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
duration: number;
|
|
10
|
+
snapshot: RawSnapshot;
|
|
11
|
+
}>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { run as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{g as e}from"./typescript-C4WyXHBh.mjs";import{n as t,t as n}from"./registry-DhjvvQpC.mjs";let r=null,i=null;const a=new Map;async function o({checkId:o}){r||(i??=(async()=>{r=await e()})(),await i);let s=r?.checks[o];if(!s)throw Error(`Check not found in config: ${o}`);let c=a.get(s.type);if(!c){c=new t,n(c,r);let e=new Set([s.type]);await Promise.all([c.setup(e),c.validate(e)]),a.set(s.type,c)}let l=performance.now(),u=await c.get(s.type).run(s);return{duration:performance.now()-l,snapshot:u}}export{o as default};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,320 +1,7 @@
|
|
|
1
|
+
import { a as RawSnapshot, c as Snapshot, i as IssueInput, l as TypeScriptCheckConfig, n as ESLintCheckConfig, o as RegexCheckConfig, r as Issue, s as RegexPattern, t as Config, u as CheckRunner } from "./types-B1hGuB2T.mjs";
|
|
1
2
|
import * as eslint0 from "eslint";
|
|
2
|
-
import { Linter } from "eslint";
|
|
3
3
|
import * as typescript0 from "typescript";
|
|
4
|
-
import { CompilerOptions } from "typescript";
|
|
5
4
|
|
|
6
|
-
//#region src/types.d.ts
|
|
7
|
-
interface Issue {
|
|
8
|
-
/**
|
|
9
|
-
* 1-indexed column number for display.
|
|
10
|
-
*/
|
|
11
|
-
column: number;
|
|
12
|
-
/**
|
|
13
|
-
* Relative path from cwd.
|
|
14
|
-
*/
|
|
15
|
-
file: string;
|
|
16
|
-
/**
|
|
17
|
-
* Hash of canonical representation.
|
|
18
|
-
*
|
|
19
|
-
* @example "a1b2c3d4e5f6g7h8i9j0"
|
|
20
|
-
*/
|
|
21
|
-
id: string;
|
|
22
|
-
/**
|
|
23
|
-
* 1-indexed line number for display.
|
|
24
|
-
*/
|
|
25
|
-
line: number;
|
|
26
|
-
/**
|
|
27
|
-
* The message.
|
|
28
|
-
*/
|
|
29
|
-
message: string;
|
|
30
|
-
/**
|
|
31
|
-
* Identifier for the issue (rule name, diagnostic code, etc).
|
|
32
|
-
*
|
|
33
|
-
* @example "no-nested-ternary" (ESLint)
|
|
34
|
-
*
|
|
35
|
-
* @example "TS2345" (TypeScript)
|
|
36
|
-
*/
|
|
37
|
-
rule: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Issue produced by a check runner.
|
|
41
|
-
*/
|
|
42
|
-
type IssueInput = Omit<Issue, "id">;
|
|
43
|
-
type SnapshotType = "items";
|
|
44
|
-
interface RawSnapshot {
|
|
45
|
-
/**
|
|
46
|
-
* Snapshot items (each item represents an issue produced by the check).
|
|
47
|
-
*/
|
|
48
|
-
items: IssueInput[];
|
|
49
|
-
type: SnapshotType;
|
|
50
|
-
}
|
|
51
|
-
interface Snapshot {
|
|
52
|
-
items: Issue[];
|
|
53
|
-
type: SnapshotType;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Configuration for an ESLint check.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* eslint({
|
|
61
|
-
* files: ["src/**\/*.{ts,tsx}"],
|
|
62
|
-
* overrides: {
|
|
63
|
-
* rules: {
|
|
64
|
-
* "no-nested-ternary": "error",
|
|
65
|
-
* },
|
|
66
|
-
* },
|
|
67
|
-
* })
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
interface ESLintCheckConfig {
|
|
71
|
-
/**
|
|
72
|
-
* Concurrency setting for ESLint.
|
|
73
|
-
*
|
|
74
|
-
* @see https://eslint.org/blog/2025/08/multithread-linting/#cli-multithreading-support
|
|
75
|
-
*
|
|
76
|
-
* @default "auto"
|
|
77
|
-
*/
|
|
78
|
-
concurrency?: "auto" | "off" | number;
|
|
79
|
-
/**
|
|
80
|
-
* Glob patterns for files to lint.
|
|
81
|
-
*
|
|
82
|
-
* Passed directly to ESLint's `lintFiles()` method.
|
|
83
|
-
*
|
|
84
|
-
* @example ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
85
|
-
*/
|
|
86
|
-
files: string[];
|
|
87
|
-
/**
|
|
88
|
-
* ESLint configuration to merge with the base config.
|
|
89
|
-
*
|
|
90
|
-
* This is passed to ESLint's `overrideConfig` option and merged with
|
|
91
|
-
* your existing ESLint configuration.
|
|
92
|
-
*
|
|
93
|
-
* Can be a single config object or an array of config objects.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```ts
|
|
97
|
-
* {
|
|
98
|
-
* rules: {
|
|
99
|
-
* "no-console": "error",
|
|
100
|
-
* },
|
|
101
|
-
* }
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
|
-
overrides?: Linter.Config | Linter.Config[];
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Configuration for a TypeScript diagnostics check.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* typescript({
|
|
112
|
-
* overrides: {
|
|
113
|
-
* compilerOptions: {
|
|
114
|
-
* noImplicitAny: true,
|
|
115
|
-
* },
|
|
116
|
-
* },
|
|
117
|
-
* })
|
|
118
|
-
* ```
|
|
119
|
-
*/
|
|
120
|
-
interface TypeScriptCheckConfig {
|
|
121
|
-
/**
|
|
122
|
-
* Compiler options to merge with the base tsconfig.
|
|
123
|
-
*
|
|
124
|
-
* These options are merged with (not replacing) the compiler options
|
|
125
|
-
* from your tsconfig file.
|
|
126
|
-
*/
|
|
127
|
-
overrides?: {
|
|
128
|
-
compilerOptions?: CompilerOptions;
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* Path to a TypeScript config file.
|
|
132
|
-
*
|
|
133
|
-
* If not provided, mejora will search for the nearest `tsconfig.json`
|
|
134
|
-
* starting from the current working directory.
|
|
135
|
-
*
|
|
136
|
-
* @example "tsconfig.strict.json"
|
|
137
|
-
*/
|
|
138
|
-
tsconfig?: string;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* A regex pattern configuration.
|
|
142
|
-
*/
|
|
143
|
-
interface RegexPattern {
|
|
144
|
-
/**
|
|
145
|
-
* Human-readable message for matches.
|
|
146
|
-
*
|
|
147
|
-
* @example "TODO comment found"
|
|
148
|
-
*
|
|
149
|
-
* @example "console.log statement"
|
|
150
|
-
*
|
|
151
|
-
* @example (match) => `Found TODO at line ${match.index + 1}`
|
|
152
|
-
*/
|
|
153
|
-
message?: ((match: RegExpExecArray) => string) | string;
|
|
154
|
-
/**
|
|
155
|
-
* The regex pattern to match.
|
|
156
|
-
*
|
|
157
|
-
* @example /\/\/\s*TODO:/gi
|
|
158
|
-
*
|
|
159
|
-
* @example /console\.log/g
|
|
160
|
-
*/
|
|
161
|
-
pattern: RegExp;
|
|
162
|
-
/**
|
|
163
|
-
* Rule identifier for this pattern.
|
|
164
|
-
* If not provided, uses the pattern source as the rule ID.
|
|
165
|
-
*
|
|
166
|
-
* @example "no-todos"
|
|
167
|
-
*
|
|
168
|
-
* @example "no-console-log"
|
|
169
|
-
*/
|
|
170
|
-
rule?: string;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Configuration for regex pattern matching check.
|
|
174
|
-
*/
|
|
175
|
-
interface RegexCheckConfig {
|
|
176
|
-
/**
|
|
177
|
-
* Concurrency for processing files.
|
|
178
|
-
*
|
|
179
|
-
* @default 10
|
|
180
|
-
*/
|
|
181
|
-
concurrency?: number;
|
|
182
|
-
/**
|
|
183
|
-
* Array of glob patterns for files to check.
|
|
184
|
-
*
|
|
185
|
-
* @example ["src/**\/*.ts", "lib/**\/*.js"]
|
|
186
|
-
*/
|
|
187
|
-
files: string[];
|
|
188
|
-
/**
|
|
189
|
-
* Array of glob patterns to ignore.
|
|
190
|
-
*
|
|
191
|
-
* @default ["**\/node_modules/**", "**\/dist/**", "**\/.git/**"]
|
|
192
|
-
*/
|
|
193
|
-
ignore?: string[];
|
|
194
|
-
/**
|
|
195
|
-
* Array of regex patterns to match.
|
|
196
|
-
*/
|
|
197
|
-
patterns: RegexPattern[];
|
|
198
|
-
}
|
|
199
|
-
type CustomCheckConfig = Record<string, unknown> & {
|
|
200
|
-
type: string;
|
|
201
|
-
};
|
|
202
|
-
type CheckConfig$1 = (ESLintCheckConfig & {
|
|
203
|
-
type: "eslint";
|
|
204
|
-
}) | (TypeScriptCheckConfig & {
|
|
205
|
-
type: "typescript";
|
|
206
|
-
}) | CustomCheckConfig;
|
|
207
|
-
/**
|
|
208
|
-
* mejora configuration.
|
|
209
|
-
*
|
|
210
|
-
* Define checks to run and track for regressions.
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```ts
|
|
214
|
-
* import { defineConfig, eslint, typescript } from "mejora";
|
|
215
|
-
*
|
|
216
|
-
* export default defineConfig({
|
|
217
|
-
* checks: {
|
|
218
|
-
* "eslint > no-nested-ternary": eslint({
|
|
219
|
-
* files: ["src/**\/*.{ts,tsx}"],
|
|
220
|
-
* overrides: {
|
|
221
|
-
* rules: {
|
|
222
|
-
* "no-nested-ternary": "error",
|
|
223
|
-
* },
|
|
224
|
-
* },
|
|
225
|
-
* }),
|
|
226
|
-
* "typescript": typescript({
|
|
227
|
-
* overrides: {
|
|
228
|
-
* compilerOptions: {
|
|
229
|
-
* noImplicitAny: true,
|
|
230
|
-
* },
|
|
231
|
-
* },
|
|
232
|
-
* }),
|
|
233
|
-
* },
|
|
234
|
-
* });
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
interface Config {
|
|
238
|
-
/**
|
|
239
|
-
* Check definitions.
|
|
240
|
-
*
|
|
241
|
-
* Each key is a check identifier used in the baseline and output.
|
|
242
|
-
* The identifier can contain any characters.
|
|
243
|
-
*
|
|
244
|
-
* Use `eslint()` and `typescript()` helpers to create check configs.
|
|
245
|
-
*
|
|
246
|
-
* @example
|
|
247
|
-
* ```ts
|
|
248
|
-
* {
|
|
249
|
-
* "eslint > no-console": eslint({ ... }),
|
|
250
|
-
* "typescript": typescriptCheck({ ... }),
|
|
251
|
-
* }
|
|
252
|
-
* ```
|
|
253
|
-
*/
|
|
254
|
-
checks: Record<string, CheckConfig$1>;
|
|
255
|
-
/**
|
|
256
|
-
* Runners to register custom check types.
|
|
257
|
-
*
|
|
258
|
-
* Built-in checks (eslint, typescript) are always available.
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```ts
|
|
262
|
-
* {
|
|
263
|
-
* runners: [myCustomRunner()],
|
|
264
|
-
* checks: {
|
|
265
|
-
* "custom": myCheck({ ... })
|
|
266
|
-
* }
|
|
267
|
-
* }
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
runners?: CheckRunner[];
|
|
271
|
-
}
|
|
272
|
-
//#endregion
|
|
273
|
-
//#region src/check-runner.d.ts
|
|
274
|
-
/**
|
|
275
|
-
* Interface that all check runners must implement.
|
|
276
|
-
*
|
|
277
|
-
* Each check type (eslint, typescript, custom) implements this interface
|
|
278
|
-
* to provide consistent lifecycle hooks for execution.
|
|
279
|
-
*/
|
|
280
|
-
interface CheckRunner<TConfig = unknown> {
|
|
281
|
-
/**
|
|
282
|
-
* Execute the check and return a snapshot of issues.
|
|
283
|
-
*
|
|
284
|
-
* @param config - Check-specific configuration
|
|
285
|
-
*
|
|
286
|
-
* @returns Snapshot containing all issues found
|
|
287
|
-
*/
|
|
288
|
-
run(config: TConfig): Promise<RawSnapshot>;
|
|
289
|
-
/**
|
|
290
|
-
* Setup any infrastructure needed for this check.
|
|
291
|
-
* Called once during runner setup, in parallel with other checks.
|
|
292
|
-
*
|
|
293
|
-
* Examples: creating cache directories, initializing compilation state.
|
|
294
|
-
*
|
|
295
|
-
* Optional - not all checks need infrastructure setup.
|
|
296
|
-
*/
|
|
297
|
-
setup?(): Promise<void>;
|
|
298
|
-
/**
|
|
299
|
-
* Unique identifier for this check type.
|
|
300
|
-
* Must match the `type` field in CheckConfig.
|
|
301
|
-
*
|
|
302
|
-
* @example "eslint"
|
|
303
|
-
*
|
|
304
|
-
* @example "typescript"
|
|
305
|
-
*/
|
|
306
|
-
readonly type: string;
|
|
307
|
-
/**
|
|
308
|
-
* Validate that all dependencies for this check are available.
|
|
309
|
-
* Called once during runner setup before any checks execute.
|
|
310
|
-
*
|
|
311
|
-
* Should throw a descriptive error if dependencies are missing.
|
|
312
|
-
*
|
|
313
|
-
* @throws {Error} If required dependencies are not installed
|
|
314
|
-
*/
|
|
315
|
-
validate?(): Promise<void>;
|
|
316
|
-
}
|
|
317
|
-
//#endregion
|
|
318
5
|
//#region src/runners/eslint.d.ts
|
|
319
6
|
/**
|
|
320
7
|
* Check runner for ESLint.
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,h as n,i as r,l as i,n as a,o,p as s,s as c}from"./typescript-
|
|
1
|
+
import{a as e,c as t,h as n,i as r,l as i,n as a,o,p as s,s as c}from"./typescript-C4WyXHBh.mjs";import{mkdir as l}from"node:fs/promises";import{createReadStream as u}from"node:fs";import{createInterface as d}from"node:readline/promises";const f=[`**/node_modules/**`,`**/dist/**`,`**/.git/**`];function p(e,t){if(t?.length)return t;let n=e.map(e=>/^([^*]+\/)/.exec(e)?.[1]).filter(e=>e!==void 0);return[...f,...n.flatMap(e=>f.map(t=>t.replace(/^\*\*\//,e)))]}async function m(e,t,n){let r=Array.from({length:e.length}),i=0;return await Promise.all(Array.from({length:Math.min(t,e.length)},async()=>{for(;i<e.length;){let t=i++;r[t]=await n(e[t])}})),r}var h=class{type=`regex`;async run(n){let r=process.cwd(),{glob:a}=await import(`tinyglobby`),l=p(n.files,n.ignore),f=await a(n.files,{absolute:!1,cwd:r,ignore:l}),h=n.patterns.map(({message:e,pattern:t,rule:n})=>{let r=t.flags.includes(`g`)?t.flags:`${t.flags}g`;return{message:e,regex:new RegExp(t.source,r),ruleText:n??t.source}}),g=s(o(this.type,r),`${e(n)}.json`),_=await t(g),v={},y=await m(f,n.concurrency??10,async e=>{let t=s(r,e),n=await c(t);if(!n)return[];let i=_[e];if(i?.hash===n)return v[e]=i,i.items;try{let r=[],i=d({crlfDelay:1/0,input:u(t,{encoding:`utf8`})}),a=0;try{for await(let t of i){a++;for(let n of h){n.regex.lastIndex=0;let i;for(;(i=n.regex.exec(t))!==null;){let t=i.index+1,o=typeof n.message==`function`?n.message(i):n.message??`Pattern matched: ${i[0]}`;r.push({column:t,file:e,line:a,message:o,rule:n.ruleText})}}}}finally{i.close()}return v[e]={hash:n,items:r},r}catch{return[]}}),b=[];for(let e of y)b.push(...e);return await i(g,v),{items:b,type:`items`}}async setup(){let e=process.cwd();await l(o(this.type,e),{recursive:!0})}async validate(){try{await import(`tinyglobby`)}catch{throw Error(`${this.type} check requires "tinyglobby" package to be installed. Run: npm install tinyglobby`)}}};const g=()=>new h;function _(e){return{type:`regex`,...e}}export{n as defineConfig,r as eslint,r as eslintCheck,_ as regex,_ as regexCheck,g as regexRunner,a as typescript,a as typescriptCheck};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e,t}from"./typescript-C4WyXHBh.mjs";var n=class{runners=new Map;static getRequiredTypes(e){return new Set(Object.values(e).map(e=>e.type))}get(e){let t=this.runners.get(e);if(!t)throw Error(`Unknown check type: ${e}`);return t}getTypes(){return new Set(this.runners.keys())}has(e){return this.runners.has(e)}register(e){if(this.runners.has(e.type))throw Error(`Check runner already registered: ${e.type}`);this.runners.set(e.type,e)}async setup(e){await this.runLifecycle(e,`setup`)}async validate(e){await this.runLifecycle(e,`validate`)}async runLifecycle(e,t){let n=[];for(let r of e){let e=this.get(r)[t]?.();e&&n.push(e)}await Promise.all(n)}};function r(n,r){if(n.register(new e),n.register(new t),r.runners)for(let e of r.runners)n.register(e)}export{n,r as t};
|
package/dist/run.mjs
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{d as e,f as t,g as n,m as r,
|
|
3
|
-
`,r=e.snapshot.items.length,i=[`${n}${v(`ℹ`)} ${e.checkId}:`,` Initial baseline created with ${v(r)} ${
|
|
4
|
-
`}${e.hasRegression?
|
|
5
|
-
`,r=e.snapshot.items.length;return e.duration===void 0?[`${n}${
|
|
6
|
-
`)}function
|
|
7
|
-
`)}const
|
|
8
|
-
}`.repeat(t)}`}function
|
|
2
|
+
import{d as e,f as t,g as n,m as r,u as i}from"./typescript-C4WyXHBh.mjs";import{n as a,t as o}from"./registry-DhjvvQpC.mjs";import{fileURLToPath as s}from"node:url";import{mkdir as c,readFile as l,writeFile as u}from"node:fs/promises";import{inspect as d,parseArgs as f,styleText as p}from"node:util";import{Tinypool as m}from"tinypool";import{env as h}from"node:process";function g(e,t){if(!(e===void 0||t===0))return e/t}function ee(e){let{results:t,totalDuration:n}=e,r=t.length,i=[],a=[],o=[],s=[],c=0,l=0,u=0,d=0,f=0,p=[];for(let e of t){let t=e.snapshot.items.length;f+=t,e.isInitial?(u+=t,o.push(e.checkId)):(e.hasImprovement&&(c+=e.removedIssues.length,i.push(e.checkId)),e.hasRegression&&(l+=e.newIssues.length,a.push(e.checkId)),!e.hasImprovement&&!e.hasRegression&&(d+=t,s.push(e.checkId))),p.push({checkId:e.checkId,duration:e.duration,hasImprovement:e.hasImprovement,hasRegression:e.hasRegression,isInitial:e.isInitial,newIssues:e.newIssues,removedIssues:e.removedIssues,totalIssues:t})}let m={checks:p,exitCode:e.exitCode,hasImprovement:e.hasImprovement,hasRegression:e.hasRegression,summary:{avgDuration:g(n,r),checksRun:r,improvementChecks:i,improvements:c,initial:u,initialChecks:o,regressionChecks:a,regressions:l,totalIssues:f,unchanged:d,unchangedChecks:s},totalDuration:n};return JSON.stringify(m,null,2)}const _=e=>t=>p(e,typeof t==`number`?t.toString():t),v=_(`blue`),y=_(`bold`),te=_(`cyan`),b=_(`dim`),x=_(`green`),S=_(`red`),C=_(`gray`),ne=_(`underline`),re=_(`yellow`);function ie(e){if(e<1e3)return`${e}ms`;let t=e/1e3;if(t<60)return t%1==0?`${t}s`:`${t.toFixed(1)}s`;let n=e/6e4;if(n<60)return n%1==0?`${n}m`:`${n.toFixed(1)}m`;let r=e/36e5;return r%1==0?`${r}h`:`${r.toFixed(1)}h`}function w(e){let t=Math.round(e);return t<1?`<1ms`:ie(t)}function T(e,t){return e===1?t:`${t}s`}const E=` `,ae=`${E} `;function oe(e){return e===`initial`?b(`→`):e===`improvement`?x(`↑`):S(`↓`)}function se(n,r,i){let a=t(n),o=e(n),s=r>0?`:${r}:${i>0?i:1}`:``;return`${a===`.`?``:b(`${a}/`)}${ne(o)}${s?b(s):``}`}function ce(e,t){return[`${oe(t)} ${se(e.file,e.line,e.column)} ${b(e.rule)}`,e.message]}function D(e,t){let n=[],r=e.slice(0,10);for(let e of r){let[r,i]=ce(e,t);n.push(`${E}${r}`,`${ae}${i}`)}let i=e.length-r.length;return i>0&&n.push(`${E}${b(`... and ${i} more`)}`),n}function le(e){return e===void 0?[]:[` ${b(`Duration`)} ${w(e)}`]}function ue(e){return[` ${b(`Issues`)} ${y(e)}`]}function O(e){return[...le(e.duration),...ue(e.snapshot.items.length)]}function de(e){if(!e.hasRegression)return[];let t=e.newIssues.length;return[` ${S(t)} new ${T(t,`issue`)} (${T(t,`regression`)}):`,...D(e.newIssues,`regression`)]}function fe(e){if(!e.hasImprovement)return[];let t=e.removedIssues.length;return[` ${x(t)} ${T(t,`issue`)} fixed (${T(t,`improvement`)}):`,...D(e.removedIssues,`improvement`)]}function pe(e,t){let n=t?``:`
|
|
3
|
+
`,r=e.snapshot.items.length,i=[`${n}${v(`ℹ`)} ${e.checkId}:`,` Initial baseline created with ${v(r)} ${T(r,`issue`)}`];return e.snapshot.items.length>0&&i.push(...D(e.snapshot.items,`initial`)),i.push(``,...O(e)),i}function me(e,t){return[`${t?``:`
|
|
4
|
+
`}${e.hasRegression?S(`✖`):x(`✔`)} ${e.checkId}:`,...de(e),...fe(e),``,...O(e)]}function he(e,t){let n=t?``:`
|
|
5
|
+
`,r=e.snapshot.items.length;return e.duration===void 0?[`${n}${C(`ℹ`)} ${e.checkId} (${y(r)})`]:[`${n}${C(`ℹ`)} ${e.checkId} (${y(r)}) ${b(w(e.duration))}`]}function ge(e,t){return e.isInitial?pe(e,t):e.hasRegression||e.hasImprovement?me(e,t):he(e,t)}function _e(e,t,n){return t?v(`✔ Initial baseline created successfully`):e.hasRegression?n?re(`⚠ Regressions detected (forced)`):`${S(`✗ Regressions detected`)} - Run failed`:e.hasImprovement?`${x(`✔ Improvements detected`)} - Baseline updated`:x(`✔ All checks passed`)}function ve(e,t){let n={hasAnyInitial:!1,totalImprovements:0,totalInitial:0,totalIssues:0,totalRegressions:0};for(let t of e.results){let e=t.snapshot.items.length;if(n.totalIssues+=e,t.isInitial){n.hasAnyInitial=!0,n.totalInitial+=e;continue}let{hasImprovement:r,hasRegression:i}=t;r&&(n.totalImprovements+=t.removedIssues.length),i&&(n.totalRegressions+=t.newIssues.length)}let r=[` ${b(`Improvements`)} ${x(n.totalImprovements)}`,` ${b(`Regressions`)} ${S(n.totalRegressions)}`,` ${b(`Initial`)} ${v(n.totalInitial)}`,` ${b(`Checks`)} ${e.results.length}`,` ${b(`Issues`)} ${y(n.totalIssues)}`],i=g(e.totalDuration,e.results.length);return e.totalDuration!==void 0&&i!==void 0&&r.push(` ${b(`Duration`)} ${w(e.totalDuration)} ${C(`(avg ${w(i)})`)}`),r.push(``,_e(e,n.hasAnyInitial,t)),r.join(`
|
|
6
|
+
`)}function ye(e,t){let n=[];for(let[t,r]of e.results.entries())n.push(...ge(r,t===0));return n.length>0&&n.push(``),n.push(ve(e,t)),n.join(`
|
|
7
|
+
`)}const k=e=>e in h&&h[e]!==`0`&&h[e]!==`false`;var A=k(`CI`)||k(`CONTINUOUS_INTEGRATION`);function j(e,t){let n=e;for(let e=0;e<t;e++){let e=n.trimEnd();if(!e.endsWith(`}`))break;n=e.slice(0,-1)}return n}function M(e,t){return`${e}${`
|
|
8
|
+
}`.repeat(t)}`}function N(e){let t=(e.match(/\{/g)?.length??0)-(e.match(/\}/g)?.length??0);return t===0?e:t<0?j(e,-t):M(e,t)}function P(e){try{return JSON.parse(e)}catch{return}}function F(e){let t=e.trim();return t.endsWith(`,`)?t.slice(0,-1):t}function be(e){return`{
|
|
9
9
|
"version": 2,
|
|
10
|
-
${
|
|
11
|
-
}`}function
|
|
12
|
-
`)}function
|
|
13
|
-
`)}function
|
|
14
|
-
`);let o=
|
|
15
|
-
`)}function
|
|
10
|
+
${F(e)}
|
|
11
|
+
}`}function I(e){if(typeof e!=`object`||!e)throw TypeError(`Baseline must be an object`);if(`checks`in e&&e.checks&&typeof e.checks==`object`)return{checks:e.checks,version:2};let t={};for(let[n,r]of Object.entries(e))n!==`version`&&(t[n]=r);return{checks:t,version:2}}function L(e){try{let t=P(e.trim());if(t)return I(t);let n=be(N(F(e)));return I(JSON.parse(n))}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to parse baseline during conflict resolution: ${t}`,{cause:e})}}function xe(e){let t=[...e.matchAll(/<<<<<<< .*\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>> .*$/gm)];if(t.length===0)throw Error(`Could not parse conflict markers in baseline`);return t.map(([,e=``,t=``])=>({ours:e,theirs:t}))}function R(e){let t=new Map;for(let n of e)for(let[e,{items:r=[]}]of Object.entries(n.checks)){if(r.length===0)continue;let n=t.get(e);n||(n=new Map,t.set(e,n));for(let e of r)n.set(e.id,e)}let n={};for(let[e,r]of t)n[e]={items:[...r.values()].toSorted((e,t)=>e.id.localeCompare(t.id)),type:`items`};return{checks:n,version:2}}function Se(e){let t=xe(e),n=[];for(let{ours:e,theirs:r}of t)n.push(L(e),L(r));return R(n)}const z=`__unparsable__`;function B(e){return e.replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`[`,`[`).replaceAll(`]`,`]`)}function V(e,t,n){let i=r(t,e);return n?`${i}#L${n}`:i}function H(e,t){return`[${e}](${t})`}function Ce(e,t){let n=V(e.file,t,e.line);return`- ${H(e.line?`Line ${e.line}`:e.file,n)} - ${`${e.rule}: ${B(e.message)}`}`}function we(e){let t=Object.groupBy(e,e=>e.file||z);return Object.entries(t).map(([e,t=[]])=>({filePath:e,items:t})).toSorted((e,t)=>e.filePath===z?1:t.filePath===z?-1:e.filePath.localeCompare(t.filePath))}function Te(e,t){let n=e.length,r=T(n,`issue`),i=[`\n### Other Issues · ${t}\n`];for(let t of e)i.push(`- ${t.rule}: ${B(t.message)}`);return i.push(`\n${n} ${r} in Other Issues`,``),i.join(`
|
|
12
|
+
`)}function Ee(e,t,n){if(e.filePath===z)return Te(e.items,n);let r=V(e.filePath,t),i=H(e.filePath,r),a=e.items.length,o=T(a,`issue`),s=[`\n### ${i} · ${n}\n`];for(let n of e.items)s.push(Ce(n,t));return s.push(`\n${a} ${o} in ${e.filePath}`,``),s.join(`
|
|
13
|
+
`)}function De(e,t,n){let r=t.length,i=T(r,`issue`),a=[`\n## ${e}\n`];if(t.length===0)return a.push(`No issues`),a.join(`
|
|
14
|
+
`);let o=we(t);for(let t of o)a.push(Ee(t,n,e));return a.push(`---\n${r} total ${i} for ${e}`),a.join(`
|
|
15
|
+
`)}function Oe(e){return`${e.replaceAll(/\n{3,}/g,`
|
|
16
16
|
|
|
17
|
-
`).trimEnd()}\n`}function
|
|
17
|
+
`).trimEnd()}\n`}function U(e,t){let n=[`<!-- prettier-ignore-start -->
|
|
18
18
|
`,`# Mejora Baseline
|
|
19
|
-
`,`This file represents the current accepted state of the codebase.`];for(let[r,{items:i=[]}]of Object.entries(e.checks))n.push(
|
|
20
|
-
<!-- prettier-ignore-end -->`),
|
|
21
|
-
`))}const
|
|
22
|
-
`).slice(1).map(e=>
|
|
19
|
+
`,`This file represents the current accepted state of the codebase.`];for(let[r,{items:i=[]}]of Object.entries(e.checks))n.push(De(r,i,t));return n.push(`
|
|
20
|
+
<!-- prettier-ignore-end -->`),Oe(n.join(`
|
|
21
|
+
`))}const ke=(e,t)=>{if(!t)return!1;let n=e.items,r=t.items;if(n.length!==r.length)return!1;let i=n.toSorted((e,t)=>e.id.localeCompare(t.id)),a=r.toSorted((e,t)=>e.id.localeCompare(t.id));return i.every((e,t)=>{let n=a[t];return e.id===n.id&&e.file===n.file&&e.line===n.line&&e.column===n.column&&e.rule===n.rule&&e.message===n.message})};function Ae(e){let t=[e.message];if(e.stack){let n=e.stack.split(`
|
|
22
|
+
`).slice(1).map(e=>b(e.trim())).join(`
|
|
23
23
|
`);t.push(n)}return t.join(`
|
|
24
|
-
`)}function
|
|
24
|
+
`)}function W(...e){return e.map(e=>typeof e==`string`?e:e instanceof Error?Ae(e):d(e,{colors:!1,depth:10})).join(` `)}const G={error:(...e)=>{console.error(S(`✖`),W(...e))},log:(...e)=>{console.log(W(...e))},start:(...e)=>{console.log(te(`◐`),W(...e))},success:(...e)=>{console.log(x(`✔`),W(...e))}};var K=class e{baselinePath;constructor(e=`.mejora/baseline.json`){this.baselinePath=e}static create(e){return{checks:e,version:2}}static getEntry(e,t){return e?.checks[t]}static update(t,n,r){let i=t??e.create({}),a=i.checks[n];return ke(r,a)?i:{...i,checks:{...i.checks,[n]:r}}}async load(){try{let e=await l(this.baselinePath,`utf8`);if(e.includes(`<<<<<<<`)){G.start(`Merge conflict detected in baseline, auto-resolving...`);let t=Se(e);return await this.save(t,!0),G.success(`Baseline conflict resolved`),t}return await this.resolveMarkdownConflictIfNeeded(e),JSON.parse(e)}catch(e){if(e.code===`ENOENT`)return null;throw e}}async save(e,n=!1){if(A&&!n)return;let r=`${JSON.stringify(e,null,2)}\n`,i=this.baselinePath.replace(`.json`,`.md`),a=U(e,t(this.baselinePath));await c(t(this.baselinePath),{recursive:!0}),await Promise.all([u(this.baselinePath,r,`utf8`),u(i,a,`utf8`)])}async resolveMarkdownConflictIfNeeded(e){let n=this.baselinePath.replace(`.json`,`.md`);try{(await l(n,`utf8`)).includes(`<<<<<<<`)&&(G.start(`Merge conflict detected in markdown report, regenerating...`),await u(n,U(JSON.parse(e),t(this.baselinePath)),`utf8`),G.success(`Markdown report regenerated`))}catch{}}};function je(){return{hasImprovement:!1,hasRegression:!1,hasRelocation:!1,isInitial:!0,newIssues:[],removedIssues:[]}}function Me(e,t,n){return{hasImprovement:t.length>0,hasRegression:e.length>0,hasRelocation:n,isInitial:!1,newIssues:e.toSorted((e,t)=>e.id.localeCompare(t.id)),removedIssues:t.toSorted((e,t)=>e.id.localeCompare(t.id))}}function q(e=[]){return new Map(e.map(e=>[e.id,e]))}function J(e){return new Set(e.keys())}function Y(e,t){let n=[];for(let r of t){let t=e.get(r);n.push(t)}return n}function Ne(e,t){for(let[n,r]of e){let e=t.get(n);if(e&&(r.line!==e.line||r.column!==e.column))return!0}return!1}function Pe(e,t){let n=q(e.items),r=q(t.items),i=J(n),a=J(r),o=i.difference(a),s=a.difference(i);return Me(Y(n,o),Y(r,s),i.size>o.size?Ne(n,r):!1)}function X(e,t){return t?Pe(e,t):je()}function Z(e,t){return e.file===t.file?e.line===t.line?e.column-t.column:e.line-t.line:e.file.localeCompare(t.file)}function Fe(e){let t=Map.groupBy(e,e=>e.signature),n=[];for(let[e,r]of t){r.sort(Z);for(let[t,{signature:a,...o}]of r.entries())n.push({...o,id:i(`${e}:${t}`)})}return n}function Q(e){return{items:Fe(e.items.map(e=>({...e,signature:`${e.file} - ${e.rule}: ${e.message}`}))).toSorted(Z),type:`items`}}const Ie=s(new URL(`check-worker.mjs`,import.meta.url));var Le=class e{baselineManager;registry;constructor(e,t){this.registry=e,this.baselineManager=new K(t)}static async executeChecksParallel(e,t){let n=Object.keys(e),r=new m({filename:Ie});try{let e=n.map(async e=>{let n=await r.run({checkId:e}),i=Q(n.snapshot),a=K.getEntry(t,e),o=X(i,a);return{baseline:a,checkId:e,duration:n.duration,hasImprovement:o.hasImprovement,hasRegression:o.hasRegression,hasRelocation:o.hasRelocation,isInitial:o.isInitial,newIssues:o.newIssues,removedIssues:o.removedIssues,snapshot:i}});return await Promise.all(e)}catch(e){return G.error(`Parallel execution failed:`,e),null}finally{await r.destroy()}}static filterChecks=(t,n)=>{let r=n.only?e.resolveRegex(n.only,`--only`):null,i=n.skip?e.resolveRegex(n.skip,`--skip`):null;return!r&&!i?t:Object.fromEntries(Object.entries(t).filter(([e])=>!(r&&!r.test(e)||i?.test(e))))};static resolveRegex(e,t){try{return new RegExp(e)}catch{throw Error(`Invalid regex pattern for ${t}: "${e}"`)}}async run(t,n={}){let r=performance.now(),i=await this.baselineManager.load(),a=e.filterChecks(t.checks,n),o=Object.keys(a).length;G.start(`Running ${o} check${o===1?``:`s`}...`);let s=o>1?await e.executeChecksParallel(a,i):await this.executeChecksSequential(a,i);if(!s)return{exitCode:2,hasImprovement:!1,hasRegression:!0,results:[],totalDuration:performance.now()-r};let c=i,l=!1,u=!1,d=!1;for(let e of s)e.hasRegression&&(l=!0),e.hasImprovement&&(u=!0),e.isInitial&&(d=!0),(e.hasImprovement||e.hasRelocation||n.force||e.isInitial)&&(c=K.update(c,e.checkId,{items:e.snapshot.items,type:e.snapshot.type}));return c&&c!==i&&(!l||n.force||d)&&await this.baselineManager.save(c,n.force),{exitCode:l&&!n.force?1:0,hasImprovement:u,hasRegression:l,results:s,totalDuration:performance.now()-r}}async executeChecksSequential(e,t){try{let t=a.getRequiredTypes(e);await Promise.all([this.registry.setup(t),this.registry.validate(t)])}catch(e){return G.error(`Setup failed:`,e),null}let n=[];for(let[r,i]of Object.entries(e))try{let e=performance.now(),a=await this.registry.get(i.type).run(i),o=performance.now()-e,s=Q(a),c=K.getEntry(t,r),l=X(s,c);n.push({baseline:c,checkId:r,duration:o,hasImprovement:l.hasImprovement,hasRegression:l.hasRegression,hasRelocation:l.hasRelocation,isInitial:l.isInitial,newIssues:l.newIssues,removedIssues:l.removedIssues,snapshot:s})}catch(e){return G.error(`Error running check "${r}":`,e),null}return n}};const{values:$}=f({allowPositionals:!1,options:{force:{default:!1,short:`f`,type:`boolean`},help:{short:`h`,type:`boolean`},json:{default:!1,type:`boolean`},only:{type:`string`},skip:{type:`string`}},strict:!0});$.help&&(G.log(`
|
|
25
25
|
mejora - Prevent regressions by allowing only improvement
|
|
26
26
|
|
|
27
27
|
Usage:
|
|
@@ -40,4 +40,4 @@ Examples:
|
|
|
40
40
|
mejora --json
|
|
41
41
|
mejora --only "eslint > *"
|
|
42
42
|
mejora --skip typescript
|
|
43
|
-
`),process.exit(0));try{let e=new
|
|
43
|
+
`),process.exit(0));try{let e=new a,t=await n();o(e,t);let r=await new Le(e).run(t,{force:$.force,json:$.json,only:$.only,skip:$.skip});$.json?G.log(ee(r)):(G.log(``),G.log(ye(r,$.force))),process.exit(r.exitCode)}catch(e){e instanceof Error?G.error(e.message):G.error(e),process.exit(2)}export{};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { Linter } from "eslint";
|
|
2
|
+
import { CompilerOptions } from "typescript";
|
|
3
|
+
|
|
4
|
+
//#region src/check-runner.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interface that all check runners must implement.
|
|
8
|
+
*
|
|
9
|
+
* Each check type (eslint, typescript, custom) implements this interface
|
|
10
|
+
* to provide consistent lifecycle hooks for execution.
|
|
11
|
+
*/
|
|
12
|
+
interface CheckRunner<TConfig = unknown> {
|
|
13
|
+
/**
|
|
14
|
+
* Execute the check and return a snapshot of issues.
|
|
15
|
+
*
|
|
16
|
+
* @param config - Check-specific configuration
|
|
17
|
+
*
|
|
18
|
+
* @returns Snapshot containing all issues found
|
|
19
|
+
*/
|
|
20
|
+
run(config: TConfig): Promise<RawSnapshot>;
|
|
21
|
+
/**
|
|
22
|
+
* Setup any infrastructure needed for this check.
|
|
23
|
+
* Called once during runner setup, in parallel with other checks.
|
|
24
|
+
*
|
|
25
|
+
* Examples: creating cache directories, initializing compilation state.
|
|
26
|
+
*
|
|
27
|
+
* Optional - not all checks need infrastructure setup.
|
|
28
|
+
*/
|
|
29
|
+
setup?(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Unique identifier for this check type.
|
|
32
|
+
* Must match the `type` field in CheckConfig.
|
|
33
|
+
*
|
|
34
|
+
* @example "eslint"
|
|
35
|
+
*
|
|
36
|
+
* @example "typescript"
|
|
37
|
+
*/
|
|
38
|
+
readonly type: string;
|
|
39
|
+
/**
|
|
40
|
+
* Validate that all dependencies for this check are available.
|
|
41
|
+
* Called once during runner setup before any checks execute.
|
|
42
|
+
*
|
|
43
|
+
* Should throw a descriptive error if dependencies are missing.
|
|
44
|
+
*
|
|
45
|
+
* @throws {Error} If required dependencies are not installed
|
|
46
|
+
*/
|
|
47
|
+
validate?(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/types.d.ts
|
|
51
|
+
interface Issue {
|
|
52
|
+
/**
|
|
53
|
+
* 1-indexed column number for display.
|
|
54
|
+
*/
|
|
55
|
+
column: number;
|
|
56
|
+
/**
|
|
57
|
+
* Relative path from cwd.
|
|
58
|
+
*/
|
|
59
|
+
file: string;
|
|
60
|
+
/**
|
|
61
|
+
* Hash of canonical representation.
|
|
62
|
+
*
|
|
63
|
+
* @example "a1b2c3d4e5f6g7h8i9j0"
|
|
64
|
+
*/
|
|
65
|
+
id: string;
|
|
66
|
+
/**
|
|
67
|
+
* 1-indexed line number for display.
|
|
68
|
+
*/
|
|
69
|
+
line: number;
|
|
70
|
+
/**
|
|
71
|
+
* The message.
|
|
72
|
+
*/
|
|
73
|
+
message: string;
|
|
74
|
+
/**
|
|
75
|
+
* Identifier for the issue (rule name, diagnostic code, etc).
|
|
76
|
+
*
|
|
77
|
+
* @example "no-nested-ternary" (ESLint)
|
|
78
|
+
*
|
|
79
|
+
* @example "TS2345" (TypeScript)
|
|
80
|
+
*/
|
|
81
|
+
rule: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Issue produced by a check runner.
|
|
85
|
+
*/
|
|
86
|
+
type IssueInput = Omit<Issue, "id">;
|
|
87
|
+
type SnapshotType = "items";
|
|
88
|
+
interface RawSnapshot {
|
|
89
|
+
/**
|
|
90
|
+
* Snapshot items (each item represents an issue produced by the check).
|
|
91
|
+
*/
|
|
92
|
+
items: IssueInput[];
|
|
93
|
+
type: SnapshotType;
|
|
94
|
+
}
|
|
95
|
+
interface Snapshot {
|
|
96
|
+
items: Issue[];
|
|
97
|
+
type: SnapshotType;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Configuration for an ESLint check.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* eslint({
|
|
105
|
+
* files: ["src/**\/*.{ts,tsx}"],
|
|
106
|
+
* overrides: {
|
|
107
|
+
* rules: {
|
|
108
|
+
* "no-nested-ternary": "error",
|
|
109
|
+
* },
|
|
110
|
+
* },
|
|
111
|
+
* })
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
interface ESLintCheckConfig {
|
|
115
|
+
/**
|
|
116
|
+
* Concurrency setting for ESLint.
|
|
117
|
+
*
|
|
118
|
+
* @see https://eslint.org/blog/2025/08/multithread-linting/#cli-multithreading-support
|
|
119
|
+
*
|
|
120
|
+
* @default "auto"
|
|
121
|
+
*/
|
|
122
|
+
concurrency?: "auto" | "off" | number;
|
|
123
|
+
/**
|
|
124
|
+
* Glob patterns for files to lint.
|
|
125
|
+
*
|
|
126
|
+
* Passed directly to ESLint's `lintFiles()` method.
|
|
127
|
+
*
|
|
128
|
+
* @example ["src/**\/*.ts", "src/**\/*.tsx"]
|
|
129
|
+
*/
|
|
130
|
+
files: string[];
|
|
131
|
+
/**
|
|
132
|
+
* ESLint configuration to merge with the base config.
|
|
133
|
+
*
|
|
134
|
+
* This is passed to ESLint's `overrideConfig` option and merged with
|
|
135
|
+
* your existing ESLint configuration.
|
|
136
|
+
*
|
|
137
|
+
* Can be a single config object or an array of config objects.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* {
|
|
142
|
+
* rules: {
|
|
143
|
+
* "no-console": "error",
|
|
144
|
+
* },
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
overrides?: Linter.Config | Linter.Config[];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Configuration for a TypeScript diagnostics check.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* typescript({
|
|
156
|
+
* overrides: {
|
|
157
|
+
* compilerOptions: {
|
|
158
|
+
* noImplicitAny: true,
|
|
159
|
+
* },
|
|
160
|
+
* },
|
|
161
|
+
* })
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
interface TypeScriptCheckConfig {
|
|
165
|
+
/**
|
|
166
|
+
* Compiler options to merge with the base tsconfig.
|
|
167
|
+
*
|
|
168
|
+
* These options are merged with (not replacing) the compiler options
|
|
169
|
+
* from your tsconfig file.
|
|
170
|
+
*/
|
|
171
|
+
overrides?: {
|
|
172
|
+
compilerOptions?: CompilerOptions;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Path to a TypeScript config file.
|
|
176
|
+
*
|
|
177
|
+
* If not provided, mejora will search for the nearest `tsconfig.json`
|
|
178
|
+
* starting from the current working directory.
|
|
179
|
+
*
|
|
180
|
+
* @example "tsconfig.strict.json"
|
|
181
|
+
*/
|
|
182
|
+
tsconfig?: string;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A regex pattern configuration.
|
|
186
|
+
*/
|
|
187
|
+
interface RegexPattern {
|
|
188
|
+
/**
|
|
189
|
+
* Human-readable message for matches.
|
|
190
|
+
*
|
|
191
|
+
* @example "TODO comment found"
|
|
192
|
+
*
|
|
193
|
+
* @example "console.log statement"
|
|
194
|
+
*
|
|
195
|
+
* @example (match) => `Found TODO at line ${match.index + 1}`
|
|
196
|
+
*/
|
|
197
|
+
message?: ((match: RegExpExecArray) => string) | string;
|
|
198
|
+
/**
|
|
199
|
+
* The regex pattern to match.
|
|
200
|
+
*
|
|
201
|
+
* @example /\/\/\s*TODO:/gi
|
|
202
|
+
*
|
|
203
|
+
* @example /console\.log/g
|
|
204
|
+
*/
|
|
205
|
+
pattern: RegExp;
|
|
206
|
+
/**
|
|
207
|
+
* Rule identifier for this pattern.
|
|
208
|
+
* If not provided, uses the pattern source as the rule ID.
|
|
209
|
+
*
|
|
210
|
+
* @example "no-todos"
|
|
211
|
+
*
|
|
212
|
+
* @example "no-console-log"
|
|
213
|
+
*/
|
|
214
|
+
rule?: string;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Configuration for regex pattern matching check.
|
|
218
|
+
*/
|
|
219
|
+
interface RegexCheckConfig {
|
|
220
|
+
/**
|
|
221
|
+
* Concurrency for processing files.
|
|
222
|
+
*
|
|
223
|
+
* @default 10
|
|
224
|
+
*/
|
|
225
|
+
concurrency?: number;
|
|
226
|
+
/**
|
|
227
|
+
* Array of glob patterns for files to check.
|
|
228
|
+
*
|
|
229
|
+
* @example ["src/**\/*.ts", "lib/**\/*.js"]
|
|
230
|
+
*/
|
|
231
|
+
files: string[];
|
|
232
|
+
/**
|
|
233
|
+
* Array of glob patterns to ignore.
|
|
234
|
+
*
|
|
235
|
+
* @default ["**\/node_modules/**", "**\/dist/**", "**\/.git/**"]
|
|
236
|
+
*/
|
|
237
|
+
ignore?: string[];
|
|
238
|
+
/**
|
|
239
|
+
* Array of regex patterns to match.
|
|
240
|
+
*/
|
|
241
|
+
patterns: RegexPattern[];
|
|
242
|
+
}
|
|
243
|
+
type CustomCheckConfig = Record<string, unknown> & {
|
|
244
|
+
type: string;
|
|
245
|
+
};
|
|
246
|
+
type CheckConfig = (ESLintCheckConfig & {
|
|
247
|
+
type: "eslint";
|
|
248
|
+
}) | (TypeScriptCheckConfig & {
|
|
249
|
+
type: "typescript";
|
|
250
|
+
}) | CustomCheckConfig;
|
|
251
|
+
/**
|
|
252
|
+
* mejora configuration.
|
|
253
|
+
*
|
|
254
|
+
* Define checks to run and track for regressions.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* import { defineConfig, eslint, typescript } from "mejora";
|
|
259
|
+
*
|
|
260
|
+
* export default defineConfig({
|
|
261
|
+
* checks: {
|
|
262
|
+
* "eslint > no-nested-ternary": eslint({
|
|
263
|
+
* files: ["src/**\/*.{ts,tsx}"],
|
|
264
|
+
* overrides: {
|
|
265
|
+
* rules: {
|
|
266
|
+
* "no-nested-ternary": "error",
|
|
267
|
+
* },
|
|
268
|
+
* },
|
|
269
|
+
* }),
|
|
270
|
+
* "typescript": typescript({
|
|
271
|
+
* overrides: {
|
|
272
|
+
* compilerOptions: {
|
|
273
|
+
* noImplicitAny: true,
|
|
274
|
+
* },
|
|
275
|
+
* },
|
|
276
|
+
* }),
|
|
277
|
+
* },
|
|
278
|
+
* });
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
interface Config {
|
|
282
|
+
/**
|
|
283
|
+
* Check definitions.
|
|
284
|
+
*
|
|
285
|
+
* Each key is a check identifier used in the baseline and output.
|
|
286
|
+
* The identifier can contain any characters.
|
|
287
|
+
*
|
|
288
|
+
* Use `eslint()` and `typescript()` helpers to create check configs.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* {
|
|
293
|
+
* "eslint > no-console": eslint({ ... }),
|
|
294
|
+
* "typescript": typescriptCheck({ ... }),
|
|
295
|
+
* }
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
checks: Record<string, CheckConfig>;
|
|
299
|
+
/**
|
|
300
|
+
* Runners to register custom check types.
|
|
301
|
+
*
|
|
302
|
+
* Built-in checks (eslint, typescript) are always available.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```ts
|
|
306
|
+
* {
|
|
307
|
+
* runners: [myCustomRunner()],
|
|
308
|
+
* checks: {
|
|
309
|
+
* "custom": myCheck({ ... })
|
|
310
|
+
* }
|
|
311
|
+
* }
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
runners?: CheckRunner[];
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
export { RawSnapshot as a, Snapshot as c, IssueInput as i, TypeScriptCheckConfig as l, ESLintCheckConfig as n, RegexCheckConfig as o, Issue as r, RegexPattern as s, Config as t, CheckRunner as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mejora",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "Prevent regressions. Allow improvement.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"regression",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"files": [
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"tinypool": "^2.1.0"
|
|
41
|
+
},
|
|
39
42
|
"peerDependencies": {
|
|
40
43
|
"eslint": "^9.34.0",
|
|
41
44
|
"tinyglobby": "^0.2.0",
|
|
File without changes
|