mejora 3.1.2 → 3.2.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/README.md +10 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +26 -0
- package/dist/config-VQG8vlqE.mjs +1 -0
- package/dist/index-BqVC-9Iv.d.mts +172 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -3
- package/dist/run-B9dk_cJ1.mjs +18 -0
- package/dist/{index-BNOgTD7N.d.mts → run-BHo9Tcdv.d.mts} +42 -169
- package/dist/run.d.mts +2 -1
- package/dist/run.mjs +1 -43
- package/dist/workers/check.d.mts +5 -2
- package/dist/workers/check.mjs +1 -1
- package/package.json +2 -2
- package/dist/check-registry-mjn8FfPF.mjs +0 -1
- package/dist/config-C0qrgNuO.mjs +0 -1
- package/dist/hash-BaLFZO2A.mjs +0 -1
package/README.md
CHANGED
|
@@ -113,6 +113,16 @@ Skip checks:
|
|
|
113
113
|
pnpm mejora --skip typescript
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
## Programmatic API
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { run } from "mejora";
|
|
120
|
+
|
|
121
|
+
const result = await run();
|
|
122
|
+
// with options (config is loaded from disk by default)
|
|
123
|
+
const resultWithOptions = await run({ force: true });
|
|
124
|
+
```
|
|
125
|
+
|
|
116
126
|
## Configuration
|
|
117
127
|
|
|
118
128
|
Create one of:
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import"./config-VQG8vlqE.mjs";import{a as e,c as t,d as n,f as r,i,l as a,m as o,o as s,p as c,r as l,s as u,t as d,u as f}from"./run-B9dk_cJ1.mjs";import{parseArgs as p}from"node:util";function m(e,t){if(!(e===void 0||t===0))return e/t}function h(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})}return{checks:p,exitCode:e.exitCode,hasImprovement:e.hasImprovement,hasRegression:e.hasRegression,summary:{avgDuration:m(n,r),checksRun:r,improvementChecks:i,improvements:c,initial:u,initialChecks:o,regressionChecks:a,regressions:l,totalIssues:f,unchanged:d,unchangedChecks:s},totalDuration:n}}function g(e){return JSON.stringify(h(e),null,2)}function _(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 v(e){let t=Math.round(e);return t<1?`<1ms`:_(t)}const y=` `,b=`${y} `;function x(e){return e===`initial`?u(`→`):e===`improvement`?a(`↑`):f(`↓`)}function S(e,t,r){let i=o(e),a=c(e),s=t>0?`:${t}:${r>0?r:1}`:``;return`${i===`.`?``:u(`${i}/`)}${n(a)}${s?u(s):``}`}function C(e,t){return[`${x(t)} ${S(e.file,e.line,e.column)} ${u(e.rule)}`,e.message]}function w(e,t){let n=[],r=e.slice(0,10);for(let e of r){let[r,i]=C(e,t);n.push(`${y}${r}`,`${b}${i}`)}let i=e.length-r.length;return i>0&&n.push(`${y}${u(`... and ${i} more`)}`),n}function T(e){return e===void 0?[]:[` ${u(`Duration`)} ${v(e)}`]}function E(e){return[` ${u(`Issues`)} ${s(e)}`]}function D(e){return[...T(e.duration),...E(e.snapshot.items.length)]}function O(e){if(!e.hasRegression)return[];let t=e.newIssues.length;return[` ${f(t)} new ${i(t,`issue`)} (${i(t,`regression`)}):`,...w(e.newIssues,`regression`)]}function k(e){if(!e.hasImprovement)return[];let t=e.removedIssues.length;return[` ${a(t)} ${i(t,`issue`)} fixed (${i(t,`improvement`)}):`,...w(e.removedIssues,`improvement`)]}function A(t,n){let r=n?``:`
|
|
3
|
+
`,a=t.snapshot.items.length,o=[`${r}${e(`ℹ`)} ${t.checkId}:`,` Initial baseline created with ${e(a)} ${i(a,`issue`)}`];return t.snapshot.items.length>0&&o.push(...w(t.snapshot.items,`initial`)),o.push(``,...D(t)),o}function j(e,t){return[`${t?``:`
|
|
4
|
+
`}${e.hasRegression?f(`✖`):a(`✔`)} ${e.checkId}:`,...O(e),...k(e),``,...D(e)]}function M(e,n){let r=n?``:`
|
|
5
|
+
`,i=e.snapshot.items.length,o=i===0?a(`✔`):t(`ℹ`),c=i===0?a(i):s(i);return e.duration===void 0?[`${r}${o} ${e.checkId} (${c})`]:[`${r}${o} ${e.checkId} (${c}) ${u(v(e.duration))}`]}function N(e,t){return e.isInitial?A(e,t):e.hasRegression||e.hasImprovement?j(e,t):M(e,t)}function P(t,n,i){return n?e(`✔ Initial baseline created successfully`):t.hasRegression?i?r(`⚠ Regressions detected (forced)`):`${f(`✗ Regressions detected`)} - Run failed`:t.hasImprovement?`${a(`✔ Improvements detected`)} - Baseline updated`:a(`✔ All checks passed`)}function F(n,r){let i={hasAnyInitial:!1,totalImprovements:0,totalInitial:0,totalIssues:0,totalRegressions:0};for(let e of n.results){let t=e.snapshot.items.length;if(i.totalIssues+=t,e.isInitial){i.hasAnyInitial=!0,i.totalInitial+=t;continue}let{hasImprovement:n,hasRegression:r}=e;n&&(i.totalImprovements+=e.removedIssues.length),r&&(i.totalRegressions+=e.newIssues.length)}let o=[` ${u(`Improvements`)} ${a(i.totalImprovements)}`,` ${u(`Regressions`)} ${f(i.totalRegressions)}`,` ${u(`Initial`)} ${e(i.totalInitial)}`,` ${u(`Checks`)} ${n.results.length}`,` ${u(`Issues`)} ${s(i.totalIssues)}`],c=m(n.totalDuration,n.results.length);return n.totalDuration!==void 0&&c!==void 0&&o.push(` ${u(`Duration`)} ${v(n.totalDuration)} ${t(`(avg ${v(c)})`)}`),o.push(``,P(n,i.hasAnyInitial,r)),o.join(`
|
|
6
|
+
`)}function I(e,t){let n=[];for(let[t,r]of e.results.entries())n.push(...N(r,t===0));return n.length>0&&n.push(``),n.push(F(e,t)),n.join(`
|
|
7
|
+
`)}try{let{values:e}=p({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});e.help&&(l.log(`
|
|
8
|
+
mejora - Prevent regressions by allowing only improvement
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
mejora [options]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
-f, --force Update baseline even with regressions
|
|
15
|
+
--json Output results as JSON
|
|
16
|
+
--only <pattern> Run only checks matching pattern (e.g., "eslint > *")
|
|
17
|
+
--skip <pattern> Skip checks matching pattern
|
|
18
|
+
-h, --help Show this help message
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
mejora
|
|
22
|
+
mejora --force
|
|
23
|
+
mejora --json
|
|
24
|
+
mejora --only "eslint > *"
|
|
25
|
+
mejora --skip typescript
|
|
26
|
+
`),process.exit(0));let t=await d({force:e.force,only:e.only,skip:e.skip});e.json?l.log(g(t)):(l.log(``),l.log(I(t,e.force))),process.exit(t.exitCode)}catch(e){e instanceof Error?l.error(e.message):l.error(e),process.exit(2)}export{};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as e}from"node:module";import{pathToFileURL as t}from"node:url";var n=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),r=e(import.meta.url),i=class{runners=new Map;static getRequiredTypes(e){return new Set(e.map(e=>e.config.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)}init(e={}){if(e.runners)for(let t of e.runners)this.register(t)}register(e){this.runners.has(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)}},a=n(((e,t)=>{let n=r(`path`),i=r(`fs`),a=r(`os`),o=r(`url`),s=i.promises.readFile;function c(e,t){return[`package.json`,`.${e}rc.json`,`.${e}rc.js`,`.${e}rc.cjs`,...t?[]:[`.${e}rc.mjs`],`.config/${e}rc`,`.config/${e}rc.json`,`.config/${e}rc.js`,`.config/${e}rc.cjs`,...t?[]:[`.config/${e}rc.mjs`],`${e}.config.js`,`${e}.config.cjs`,...t?[]:[`${e}.config.mjs`]]}function l(e){return n.dirname(e)||n.sep}let u=(e,t)=>JSON.parse(t),d=typeof __webpack_require__==`function`?__non_webpack_require__:r,f=Object.freeze({".js":d,".json":d,".cjs":d,noExt:u});t.exports.defaultLoadersSync=f;let p=async e=>{try{return(await import(o.pathToFileURL(e).href)).default}catch(t){try{return d(e)}catch(e){throw e.code===`ERR_REQUIRE_ESM`||e instanceof SyntaxError&&e.toString().includes(`Cannot use import statement outside a module`)?t:e}}},m=Object.freeze({".js":p,".mjs":p,".cjs":p,".json":u,noExt:u});t.exports.defaultLoaders=m;function h(e,t,r){let i={stopDir:a.homedir(),searchPlaces:c(e,r),ignoreEmptySearchPlaces:!0,cache:!0,transform:e=>e,packageProp:[e],...t,loaders:{...r?f:m,...t.loaders}};return i.searchPlaces.forEach(e=>{let t=n.extname(e)||`noExt`,r=i.loaders[t];if(!r)throw Error(`Missing loader for extension "${e}"`);if(typeof r!=`function`)throw Error(`Loader for extension "${e}" is not a function: Received ${typeof r}.`)}),i}function g(e,t){return typeof e==`string`&&e in t?t[e]:(Array.isArray(e)?e:e.split(`.`)).reduce((e,t)=>e===void 0?e:e[t],t)||null}function _(e){if(!e)throw Error(`load must pass a non-empty string`)}function v(e,t){if(!e)throw Error(`No loader specified for extension "${t}"`);if(typeof e!=`function`)throw Error(`loader is not a function`)}let y=e=>(t,n,r)=>(e&&t.set(n,r),r);t.exports.lilconfig=function(e,t){let{ignoreEmptySearchPlaces:r,loaders:a,packageProp:o,searchPlaces:c,stopDir:u,transform:d,cache:f}=h(e,t??{},!1),p=new Map,m=new Map,b=y(f);return{async search(e=process.cwd()){let t={config:null,filepath:``},m=new Set,h=e;dirLoop:for(;;){if(f){let e=p.get(h);if(e!==void 0){for(let t of m)p.set(t,e);return e}m.add(h)}for(let e of c){let c=n.join(h,e);try{await i.promises.access(c)}catch{continue}let l=String(await s(c)),u=n.extname(e)||`noExt`,d=a[u];if(e===`package.json`){let e=g(o,await d(c,l));if(e!=null){t.config=e,t.filepath=c;break dirLoop}continue}let f=l.trim()===``;if(!(f&&r)){f?(t.isEmpty=!0,t.config=void 0):(v(d,u),t.config=await d(c,l)),t.filepath=c;break dirLoop}}if(h===u||h===l(h))break dirLoop;h=l(h)}let _=t.filepath===``&&t.config===null?d(null):d(t);if(f)for(let e of m)p.set(e,_);return _},async load(e){_(e);let t=n.resolve(process.cwd(),e);if(f&&m.has(t))return m.get(t);let{base:i,ext:c}=n.parse(t),l=c||`noExt`,u=a[l];v(u,l);let p=String(await s(t));if(i===`package.json`)return b(m,t,d({config:g(o,await u(t,p)),filepath:t}));let h={config:null,filepath:t},y=p.trim()===``;return y&&r?b(m,t,d({config:void 0,filepath:t,isEmpty:!0})):(h.config=y?void 0:await u(t,p),b(m,t,d(y?{...h,isEmpty:y,config:void 0}:h)))},clearLoadCache(){f&&m.clear()},clearSearchCache(){f&&p.clear()},clearCaches(){f&&(m.clear(),p.clear())}}},t.exports.lilconfigSync=function(e,t){let{ignoreEmptySearchPlaces:r,loaders:a,packageProp:o,searchPlaces:s,stopDir:c,transform:u,cache:d}=h(e,t??{},!0),f=new Map,p=new Map,m=y(d);return{search(e=process.cwd()){let t={config:null,filepath:``},p=new Set,m=e;dirLoop:for(;;){if(d){let e=f.get(m);if(e!==void 0){for(let t of p)f.set(t,e);return e}p.add(m)}for(let e of s){let s=n.join(m,e);try{i.accessSync(s)}catch{continue}let c=n.extname(e)||`noExt`,l=a[c],u=String(i.readFileSync(s));if(e===`package.json`){let e=g(o,l(s,u));if(e!=null){t.config=e,t.filepath=s;break dirLoop}continue}let d=u.trim()===``;if(!(d&&r)){d?(t.isEmpty=!0,t.config=void 0):(v(l,c),t.config=l(s,u)),t.filepath=s;break dirLoop}}if(m===c||m===l(m))break dirLoop;m=l(m)}let h=t.filepath===``&&t.config===null?u(null):u(t);if(d)for(let e of p)f.set(e,h);return h},load(e){_(e);let t=n.resolve(process.cwd(),e);if(d&&p.has(t))return p.get(t);let{base:s,ext:c}=n.parse(t),l=c||`noExt`,f=a[l];v(f,l);let h=String(i.readFileSync(t));if(s===`package.json`)return u({config:g(o,f(t,h)),filepath:t});let y={config:null,filepath:t},b=h.trim()===``;return b&&r?m(p,t,u({filepath:t,config:void 0,isEmpty:!0})):(y.config=b?void 0:f(t,h),m(p,t,u(b?{...y,isEmpty:b,config:void 0}:y)))},clearLoadCache(){d&&p.clear()},clearSearchCache(){d&&f.clear()},clearCaches(){d&&(p.clear(),f.clear())}}}}))();const o=async e=>{let n=await import(t(e).href);return n&&typeof n==`object`&&`default`in n?n.default:n};function s(e){let t=[],n=new Set;for(let r of e.checks)if(r.__runnerFactory&&!n.has(r.config.type)){n.add(r.config.type);let e=r.__runnerFactory();t.push(e)}return{checks:e.checks,runners:t}}const c=async(e=process.cwd())=>{let t=await(0,a.lilconfig)(`mejora`,{loaders:{".js":o,".mjs":o,".mts":o,".ts":o},searchPlaces:[`mejora.config.ts`,`mejora.config.mts`,`mejora.config.js`,`mejora.config.mjs`]}).search(e);if(!t?.config)throw Error(`No configuration file found.`);return t.config};export{c as n,i as r,s as t};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { d as RegexCheckConfig, h as TypeScriptCheckConfig, i as CheckRunner, n as Check, o as CustomCheckDefinition, s as ESLintCheckConfig } from "./run-BHo9Tcdv.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/core/config.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Define a mejora configuration.
|
|
6
|
+
*
|
|
7
|
+
* @param config - Configuration object
|
|
8
|
+
*
|
|
9
|
+
* @param config.checks - Array of checks to run
|
|
10
|
+
*
|
|
11
|
+
* @returns A mejora configuration object
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { defineConfig, eslint, typescript, regex } from "mejora";
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* checks: [
|
|
19
|
+
* eslint({
|
|
20
|
+
* name: "no-console",
|
|
21
|
+
* files: ["src/**\/*.ts"],
|
|
22
|
+
* rules: { "no-console": "error" }
|
|
23
|
+
* }),
|
|
24
|
+
* typescript({
|
|
25
|
+
* name: "strict",
|
|
26
|
+
* compilerOptions: { noImplicitAny: true }
|
|
27
|
+
* }),
|
|
28
|
+
* regex({
|
|
29
|
+
* name: "no-todos",
|
|
30
|
+
* files: ["**\/*"],
|
|
31
|
+
* patterns: [{ pattern: /TODO/g }]
|
|
32
|
+
* })
|
|
33
|
+
* ]
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function defineConfig(config: {
|
|
38
|
+
checks: Check[];
|
|
39
|
+
}): {
|
|
40
|
+
checks: Check[];
|
|
41
|
+
runners: CheckRunner<unknown>[];
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/core/define-check.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Configuration for a check instance, combining the check-specific config
|
|
47
|
+
* with required metadata fields.
|
|
48
|
+
*/
|
|
49
|
+
type UserCheckConfig<TConfig> = TConfig & {
|
|
50
|
+
/**
|
|
51
|
+
* Unique identifier for this check instance.
|
|
52
|
+
* Used in baseline tracking and output.
|
|
53
|
+
*/
|
|
54
|
+
name: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Define a custom check for use with mejora().
|
|
58
|
+
*
|
|
59
|
+
* This is the primitive used by all checks (built-in and custom) to create
|
|
60
|
+
* reusable check definitions. Multiple check instances with the same type
|
|
61
|
+
* share a single runner for optimal performance.
|
|
62
|
+
*
|
|
63
|
+
* @param definition - Custom check definition with type, run function, and optional hooks.
|
|
64
|
+
*
|
|
65
|
+
* @returns A factory function that creates Check objects with different configurations.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { defineConfig, defineCheck } from "mejora";
|
|
70
|
+
*
|
|
71
|
+
* const noHardcodedUrls = defineCheck<{ files: string[] }>({
|
|
72
|
+
* type: "no-hardcoded-urls",
|
|
73
|
+
* async run(config) {
|
|
74
|
+
* const violations: IssueInput[] = [];
|
|
75
|
+
* // Custom checking logic using config.files
|
|
76
|
+
* return violations;
|
|
77
|
+
* }
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* export default defineConfig({
|
|
81
|
+
* checks: [
|
|
82
|
+
* noHardcodedUrls({ name: "urls-in-src", files: ["src/**\/*.ts"] }),
|
|
83
|
+
* noHardcodedUrls({ name: "urls-in-lib", files: ["lib/**\/*.ts"] }),
|
|
84
|
+
* ]
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function defineCheck<TConfig extends Record<string, any> = Record<string, any>>(definition: CustomCheckDefinition<TConfig>): (userConfig: UserCheckConfig<TConfig>) => Check;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/runners/eslint.d.ts
|
|
91
|
+
/**
|
|
92
|
+
* Create an ESLint check for use with mejora().
|
|
93
|
+
*
|
|
94
|
+
* @param config - ESLint check configuration options including name.
|
|
95
|
+
*
|
|
96
|
+
* @returns A Check object for use with mejora().
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { defineConfig, eslint } from "mejora";
|
|
101
|
+
*
|
|
102
|
+
* export default defineConfig({
|
|
103
|
+
* checks: [
|
|
104
|
+
* eslint({
|
|
105
|
+
* name: "no-console",
|
|
106
|
+
* files: ["src/**\/*.ts"],
|
|
107
|
+
* rules: { "no-console": "error" }
|
|
108
|
+
* })
|
|
109
|
+
* ]
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
declare const eslint: (userConfig: ESLintCheckConfig & {
|
|
114
|
+
name: string;
|
|
115
|
+
}) => Check;
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/runners/regex.d.ts
|
|
118
|
+
/**
|
|
119
|
+
* Create a regex check for use with mejora().
|
|
120
|
+
*
|
|
121
|
+
* @param config - Regex check configuration options including name.
|
|
122
|
+
*
|
|
123
|
+
* @returns A Check object for use with mejora().
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* import { defineConfig, regex } from "mejora";
|
|
128
|
+
*
|
|
129
|
+
* export default defineConfig({
|
|
130
|
+
* checks: [
|
|
131
|
+
* regex({
|
|
132
|
+
* name: "no-todos",
|
|
133
|
+
* files: ["src/**\/*.ts"],
|
|
134
|
+
* patterns: [
|
|
135
|
+
* { pattern: /TODO/g, message: "TODO comment found" }
|
|
136
|
+
* ]
|
|
137
|
+
* })
|
|
138
|
+
* ]
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare const regex: (userConfig: RegexCheckConfig & {
|
|
143
|
+
name: string;
|
|
144
|
+
}) => Check;
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/runners/typescript.d.ts
|
|
147
|
+
/**
|
|
148
|
+
* Create a TypeScript check for use with mejora().
|
|
149
|
+
*
|
|
150
|
+
* @param config - TypeScript check configuration options including name.
|
|
151
|
+
*
|
|
152
|
+
* @returns A Check object for use with mejora().
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import { defineConfig, typescript } from "mejora";
|
|
157
|
+
*
|
|
158
|
+
* export default defineConfig({
|
|
159
|
+
* checks: [
|
|
160
|
+
* typescript({
|
|
161
|
+
* name: "strict-types",
|
|
162
|
+
* compilerOptions: { noImplicitAny: true }
|
|
163
|
+
* })
|
|
164
|
+
* ]
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare const typescript: (userConfig: TypeScriptCheckConfig & {
|
|
169
|
+
name: string;
|
|
170
|
+
}) => Check;
|
|
171
|
+
//#endregion
|
|
172
|
+
export { defineConfig as a, defineCheck as i, regex as n, eslint as r, typescript as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
1
|
+
import { a as Config, c as Issue, f as RunOptions, i as CheckRunner, l as IssueInput, m as Snapshot, n as Check, p as RunResult, r as CheckResult, t as run, u as RawSnapshot } from "./run-BHo9Tcdv.mjs";
|
|
2
|
+
import { a as defineConfig, i as defineCheck, n as regex, r as eslint, t as typescript } from "./index-BqVC-9Iv.mjs";
|
|
3
|
+
export { Check, CheckResult, CheckRunner, Config, Issue, IssueInput, RawSnapshot, RunOptions, RunResult, Snapshot, defineCheck, defineConfig, eslint, regex, run, typescript };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{t as e}from"./config-
|
|
2
|
-
`);throw TypeError(`Failed to read TypeScript config: ${e}`)}let x=l(y,d,p,e.compilerOptions),S=
|
|
3
|
-
`),
|
|
1
|
+
import{t as e}from"./config-VQG8vlqE.mjs";import{_ as t,g as n,h as r,n as i,t as a,v as o,y as s}from"./run-B9dk_cJ1.mjs";import{glob as c,mkdir as l,readFile as u,stat as d,writeFile as f}from"node:fs/promises";import{createReadStream as p}from"node:fs";import{createInterface as m}from"node:readline/promises";function h(e){let{defaults:t,type:n}=e,r=()=>{let t={async run(t){let{type:n,...r}=t;return{items:await e.run(r),type:`items`}},type:n};return e.setup&&(t.setup=e.setup),e.validate&&(t.validate=e.validate),t};return e=>{let{name:i,...a}=e;return{__runnerFactory:r,config:{type:n,...t,...a},id:i}}}function g(){let e=new WeakSet;return(t,n)=>{if(n&&typeof n==`object`){if(e.has(n))return`[Circular]`;if(e.add(n),!Array.isArray(n)){let e=n,t={};for(let n of Object.keys(e).toSorted()){let r=e[n];typeof r==`function`||typeof r==`symbol`||(t[n]=r)}return t}}return n}}function _(e){return i(JSON.stringify(e??null,g()))}function v(e,t=process.cwd()){return o(t,`node_modules`,`.cache`,`mejora`,e)}async function y(e){try{let t=await u(e,`utf8`);return JSON.parse(t)}catch{return{}}}async function b(e,t){try{await f(e,JSON.stringify(t),`utf8`)}catch{}}async function x(e){try{let t=await d(e);return`${t.mtimeMs}-${t.size}`}catch{return null}}const S=h({async run(e){let{ESLint:n}=await import(`eslint`),r=process.cwd(),i=v(`eslint`,r),a=_(e),{concurrency:o,...s}=e,c=new Set;if(s.rules)for(let e of Object.keys(s.rules))c.add(e);let l=c.size>0,u=await new n({cache:!0,cacheLocation:`${i}/${a}.eslintcache`,overrideConfig:s,...!l&&{concurrency:o??`auto`},...l&&{ruleFilter:({ruleId:e})=>c.has(e)}}).lintFiles(s.files),d=[];for(let{filePath:e,messages:n}of u){let i=t(r,e);for(let{column:e,line:t,message:r,ruleId:a}of n)a&&d.push({column:e,file:i,line:t,message:r,rule:a})}return d},async setup(){let e=v(`eslint`,process.cwd()),{mkdir:t}=await import(`node:fs/promises`);await t(e,{recursive:!0})},type:`eslint`,async validate(){try{await import(`eslint`)}catch{throw Error(`eslint check requires "eslint" package to be installed. Run: npm install eslint`)}}}),C=[`**/node_modules/**`,`**/dist/**`,`**/.git/**`],w=/^([^*]+\/)/,T=/^\*\*\//;function E(e,t){if(t?.length)return t;let n=e.map(e=>w.exec(e)?.[1]).filter(e=>e!==void 0);return[...C,...n.flatMap(e=>C.map(t=>t.replace(T,e)))]}async function D(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}const O=h({async run(e){let t=process.cwd(),r=Array.isArray(e.files)?e.files:[e.files],i=E(r,e.ignore),a=await Promise.all(r.map(e=>Array.fromAsync(c(e,{cwd:t,exclude:i})))),o=[...new Set(a.flat())],s=e.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}}),l=n(v(`regex`,t),`${_(e)}.json`),u=await y(l),d={},f=(await D(o,e.concurrency??10,async e=>{let r=n(t,e),i=await x(r);if(!i)return[];let a=u[e];if(a?.hash===i)return d[e]=a,a.items;try{let t=[],n=m({crlfDelay:1/0,input:p(r,{encoding:`utf8`})}),a=0;try{for await(let r of n){a++;for(let n of s){n.regex.lastIndex=0;let i;for(;(i=n.regex.exec(r))!==null;){let r=i.index+1,o=typeof n.message==`function`?n.message(i):n.message??`Pattern matched: ${i[0]}`;t.push({column:r,file:e,line:a,message:o,rule:n.ruleText})}}}}finally{n.close()}return d[e]={hash:i,items:t},t}catch{return[]}})).flat();return await b(l,d),f},async setup(){await l(v(`regex`,process.cwd()),{recursive:!0})},type:`regex`}),k=/import\("([^"]+)"\)/g;function A(e,n){return e.replaceAll(k,(e,i)=>{try{if(r(i)){let e=t(n,i);if(!e.startsWith(`..`))return`import("${e||`.`}")`}}catch{}return e})}const j=h({async run(e){let{createIncrementalCompilerHost:n,createIncrementalProgram:r,findConfigFile:i,flattenDiagnosticMessageText:a,getPreEmitDiagnostics:c,parseJsonConfigFileContent:l,readConfigFile:u,sys:d,version:f}=await import(`typescript`),p=process.cwd(),m=d.fileExists.bind(d),h=d.readFile.bind(d),g=e.tsconfig?o(e.tsconfig):i(p,m,`tsconfig.json`);if(!g)throw Error(`TypeScript config file not found`);let{config:y,error:b}=u(g,h);if(b){let e=typeof b.messageText==`string`?b.messageText:a(b.messageText,`
|
|
2
|
+
`);throw TypeError(`Failed to read TypeScript config: ${e}`)}let x=l(y,d,p,e.compilerOptions),S=o(v(`typescript`,p),`${_({compilerOptions:e.compilerOptions??{},configPath:g,parsedOptions:x.options,typescriptVersion:f})}.tsbuildinfo`),C={...x.options,incremental:!0,noEmit:!0,skipLibCheck:x.options.skipLibCheck??!0,tsBuildInfoFile:S},w=n(C,d),T=w.writeFile.bind(w);w.writeFile=(e,t,...n)=>{o(e)===S&&T(e,t,...n)};let E=r({host:w,options:C,projectReferences:x.projectReferences??[],rootNames:x.fileNames}),D=c(E.getProgram());E.emit();let O=o(p),k=O+s,j=D.filter(e=>{if(!e.file)return!0;let t=o(e.file.fileName);return t===O||t.startsWith(k)}),M=[];for(let e of j){let n=a(e.messageText,`
|
|
3
|
+
`),r=`TS${e.code}`;if(e.file&&e.start!==void 0){let{character:i,line:a}=e.file.getLineAndCharacterOfPosition(e.start),o=t(p,e.file.fileName);M.push({column:i+1,file:o,line:a+1,message:A(n,p),rule:r})}else M.push({column:0,file:`(global)`,line:0,message:A(n,p),rule:r})}return M},async setup(){let e=v(`typescript`,process.cwd()),{mkdir:t}=await import(`node:fs/promises`);await t(e,{recursive:!0})},type:`typescript`,async validate(){try{await import(`typescript`)}catch{throw Error(`typescript check requires "typescript" package to be installed. Run: npm install typescript`)}}});export{h as defineCheck,e as defineConfig,S as eslint,O as regex,a as run,j as typescript};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import{n as e,r as t}from"./config-VQG8vlqE.mjs";import{inspect as n,styleText as r}from"node:util";import{fileURLToPath as i}from"node:url";import{Worker as a}from"node:worker_threads";import{hash as o}from"node:crypto";import{mkdir as s,readFile as c,writeFile as l}from"node:fs/promises";import{env as u}from"node:process";const d=/^[A-Za-z]:\//;function f(e=``){return e&&e.replace(/\\/g,`/`).replace(d,e=>e.toUpperCase())}const p=/^[/\\]{2}/,m=/^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/,h=/^[A-Za-z]:$/,g=/^\/([A-Za-z]:)?$/,ee=`/`,te=function(e){if(e.length===0)return`.`;e=f(e);let t=e.match(p),n=b(e),r=e[e.length-1]===`/`;return e=y(e,!n),e.length===0?n?`/`:r?`./`:`.`:(r&&(e+=`/`),h.test(e)&&(e+=`/`),t?n?`//${e}`:`//./${e}`:n&&!b(e)?`/${e}`:e)},_=function(...e){let t=``;for(let n of e)if(n)if(t.length>0){let e=t[t.length-1]===`/`,r=n[0]===`/`;e&&r?t+=n.slice(1):t+=e||r?n:`/${n}`}else t+=n;return te(t)};function ne(){return typeof process<`u`&&typeof process.cwd==`function`?process.cwd().replace(/\\/g,`/`):`/`}const v=function(...e){e=e.map(e=>f(e));let t=``,n=!1;for(let r=e.length-1;r>=-1&&!n;r--){let i=r>=0?e[r]:ne();!i||i.length===0||(t=`${i}/${t}`,n=b(i))}return t=y(t,!n),n&&!b(t)?`/${t}`:t.length>0?t:`.`};function y(e,t){let n=``,r=0,i=-1,a=0,o=null;for(let s=0;s<=e.length;++s){if(s<e.length)o=e[s];else if(o===`/`)break;else o=`/`;if(o===`/`){if(!(i===s-1||a===1))if(a===2){if(n.length<2||r!==2||n[n.length-1]!==`.`||n[n.length-2]!==`.`){if(n.length>2){let e=n.lastIndexOf(`/`);e===-1?(n=``,r=0):(n=n.slice(0,e),r=n.length-1-n.lastIndexOf(`/`)),i=s,a=0;continue}else if(n.length>0){n=``,r=0,i=s,a=0;continue}}t&&(n+=n.length>0?`/..`:`..`,r=2)}else n.length>0?n+=`/${e.slice(i+1,s)}`:n=e.slice(i+1,s),r=s-i-1;i=s,a=0}else o===`.`&&a!==-1?++a:a=-1}return n}const b=function(e){return m.test(e)},x=function(e,t){let n=v(e).replace(g,`$1`).split(`/`),r=v(t).replace(g,`$1`).split(`/`);if(r[0][1]===`:`&&n[0][1]===`:`&&n[0]!==r[0])return r.join(`/`);let i=[...n];for(let e of i){if(r[0]!==e)break;n.shift(),r.shift()}return[...n.map(()=>`..`),...r].join(`/`)},S=function(e){let t=f(e).replace(/\/$/,``).split(`/`).slice(0,-1);return t.length===1&&h.test(t[0])&&(t[0]+=`/`),t.join(`/`)||(b(e)?`/`:`.`)},re=function(e,t){let n=f(e).split(`/`),r=``;for(let e=n.length-1;e>=0;e--){let t=n[e];if(t){r=t;break}}return t&&r.endsWith(t)?r.slice(0,-t.length):r},C=e=>t=>r(e,typeof t==`number`?t.toString():t),ie=C(`blue`),ae=C(`bold`),oe=C(`cyan`),w=C(`dim`),T=C(`green`),E=C(`red`),D=C(`gray`),O=C(`underline`),k=C(`yellow`);function A(e,t){return e===1?t:`${t}s`}function se(e){let t=[e.message];if(e.stack){let n=e.stack.split(`
|
|
2
|
+
`).slice(1).map(e=>w(e.trim())).join(`
|
|
3
|
+
`);t.push(n)}return t.join(`
|
|
4
|
+
`)}function j(...e){return e.map(e=>typeof e==`string`?e:e instanceof Error?se(e):n(e,{colors:!1,depth:10})).join(` `)}const M={error:(...e)=>{console.error(E(`✖`),j(...e))},log:(...e)=>{console.log(j(...e))},start:(...e)=>{console.error(oe(`◐`),j(...e))},success:(...e)=>{console.error(T(`✔`),j(...e))}},N=e=>o(`sha256`,e,`hex`);function P(e,t){return e.file===t.file?e.line===t.line?e.column-t.column:e.line-t.line:e.file<t.file?-1:1}function ce(e){return`${e.file} - ${e.rule}: ${e.message}`}function le(e){let t=Map.groupBy(e,ce),n=[];for(let[e,r]of t){r.sort(P);for(let[t,i]of r.entries())n.push({...i,id:N(`${e}:${t}`)})}return n}function F(e){return{items:le(e.items).toSorted(P),type:`items`}}const I=`__unparsable__`,ue={"<":`<`,">":`>`,"[":`[`,"]":`]`},de=/[<>[\]]/g;function L(e){return e.replaceAll(de,e=>ue[e])}function R(e,t,n){let r=x(t,e);return n?`${r}#L${n}`:r}function z(e,t){return`[${e}](${t})`}function B(e,t){let n=R(e.file,t,e.line);return`- ${z(e.line?`Line ${e.line}`:e.file,n)} - ${`${e.rule}: ${L(e.message)}`}`}function V(e){let t=Object.groupBy(e,e=>e.file||I);return Object.entries(t).map(([e,t=[]])=>({filePath:e,items:t})).toSorted((e,t)=>e.filePath===I?1:t.filePath===I||e.filePath<t.filePath?-1:e.filePath>t.filePath?1:0)}function H(e,t){let n=e.length,r=A(n,`issue`),i=[`\n### Other Issues · ${t}\n`];for(let t of e)i.push(`- ${t.rule}: ${L(t.message)}`);return i.push(`\n${n} ${r} in Other Issues`,``),i.join(`
|
|
5
|
+
`)}function fe(e,t,n){if(e.filePath===I)return H(e.items,n);let r=R(e.filePath,t),i=z(e.filePath,r),a=e.items.length,o=A(a,`issue`),s=[`\n### ${i} · ${n}\n`];for(let n of e.items)s.push(B(n,t));return s.push(`\n${a} ${o} in ${e.filePath}`,``),s.join(`
|
|
6
|
+
`)}function pe(e,t,n){let r=t.length,i=A(r,`issue`),a=[`\n## ${e}\n`];if(t.length===0)return a.push(`No issues`),a.join(`
|
|
7
|
+
`);let o=V(t);for(let t of o)a.push(fe(t,n,e));return a.push(`---\n${r} total ${i} for ${e}`),a.join(`
|
|
8
|
+
`)}function me(e){return`${e.replaceAll(/\n{3,}/g,`
|
|
9
|
+
|
|
10
|
+
`).trimEnd()}\n`}function U(e,t){let n=[`<!-- prettier-ignore-start -->
|
|
11
|
+
`,`# Mejora Baseline
|
|
12
|
+
`,`This file represents the current accepted state of the codebase.`];for(let[r,{items:i=[]}]of Object.entries(e.checks))n.push(pe(r,i,t));return n.push(`
|
|
13
|
+
<!-- prettier-ignore-end -->`),me(n.join(`
|
|
14
|
+
`))}const he=[`CI`,`CONTINUOUS_INTEGRATION`].some(e=>u[e]&&u[e]!==`0`&&u[e]!==`false`),W=(e,t)=>e.id<t.id?-1:e.id>t.id?1:0;function ge(){return{hasImprovement:!1,hasRegression:!1,hasRelocation:!1,isInitial:!0,newIssues:[],removedIssues:[]}}function _e(e,t,n){return{hasImprovement:t.length>0,hasRegression:e.length>0,hasRelocation:n,isInitial:!1,newIssues:e.toSorted(W),removedIssues:t.toSorted(W)}}function G(e=[]){return new Map(e.map(e=>[e.id,e]))}function K(e){return new Set(e.keys())}function q(e,t){let n=[];for(let r of t){let t=e.get(r);n.push(t)}return n}function ve(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 ye(e,t){let n=G(e.items),r=G(t.items),i=K(n),a=K(r),o=i.difference(a),s=a.difference(i);return _e(q(n,o),q(r,s),i.size>o.size?ve(n,r):!1)}function J(e,t){return t?ye(e,t):ge()}const Y=(e,t)=>{if(!t)return!1;let n=e.items,r=t.items;if(n.length!==r.length)return!1;let i=n.toSorted(W),a=r.toSorted(W);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 be(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 xe(e,t){return`${e}${`
|
|
15
|
+
}`.repeat(t)}`}function Se(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t===0?e:t<0?be(e,-t):xe(e,t)}function Ce(e){try{return JSON.parse(e)}catch{return}}function X(e){let t=e.trim();return t.endsWith(`,`)?t.slice(0,-1):t}function we(e){return`{
|
|
16
|
+
"version": 2,
|
|
17
|
+
${X(e)}
|
|
18
|
+
}`}function Z(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 Q(e){try{let t=e.trim();if(t===``)return{checks:{},version:2};let n=Ce(t);if(n)return Z(n);let r=we(Se(X(t)));return Z(JSON.parse(r))}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to parse baseline during conflict resolution: ${t}`,{cause:e})}}function Te(e){let t=[...e.matchAll(/^<<<<<<< .+\r?\n([\s\S]*?)^=======(?:\r?\n([\s\S]*?)\r?\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 Ee(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(W),type:`items`};return{checks:n,version:2}}function De(e){let t=Te(e),n=[];for(let{ours:e,theirs:r}of t)n.push(Q(e),Q(r));return Ee(n)}var $=class e{baselineDir;baselinePath;mdPath;constructor(e=`.mejora/baseline.json`){this.baselinePath=e,this.baselineDir=S(e),this.mdPath=e.replace(/\.json$/,`.md`)}static batchUpdate(t,n){let r=t??e.create({}),i={...r.checks},a=!1;for(let{checkId:e,entry:t}of n)Y(t,r.checks[e])||(i[e]=t,a=!0);return a?{...r,checks:i}:r}static create(e){return{checks:e,version:2}}static getEntry(e,t){return e?.checks[t]}static prune(e,t){let n=new Set(t),r=Object.keys(e.checks).filter(e=>!n.has(e));if(r.length===0)return{baseline:e,prunedIds:r};let i=Object.fromEntries(Object.entries(e.checks).filter(([e])=>n.has(e)));return{baseline:{...e,checks:i},prunedIds:r}}static update(t,n,r){let i=t??e.create({}),a=i.checks[n];return Y(r,a)?i:{...i,checks:{...i.checks,[n]:r}}}async load(){try{let e=await c(this.baselinePath,`utf8`);if(e.includes(`<<<<<<<`)){M.start(`Merge conflict detected in baseline, auto-resolving...`);let t=De(e);return await this.save(t,!0),M.success(`Baseline conflict resolved`),t}let t=JSON.parse(e);return await this.resolveMarkdownConflictIfNeeded(t),t}catch(e){if(e.code===`ENOENT`)return null;throw e}}async save(e,t=!1){if(he&&!t)return;let n=`${JSON.stringify(e,null,2)}\n`,r=U(e,this.baselineDir);await s(this.baselineDir,{recursive:!0}),await Promise.all([l(this.baselinePath,n,`utf8`),l(this.mdPath,r,`utf8`)])}async resolveMarkdownConflictIfNeeded(e){try{if((await c(this.mdPath,`utf8`)).includes(`<<<<<<<`)){M.start(`Merge conflict detected in markdown report, regenerating...`);let t=U(e,this.baselineDir);await l(this.mdPath,t,`utf8`),M.success(`Markdown report regenerated`)}}catch{}}};const Oe=i(new URL(`workers/check.mjs`,import.meta.url));var ke=class e{baselineManager;cwd;registry;constructor(e,t,n=process.cwd()){this.registry=e,this.baselineManager=new $(t),this.cwd=n}static async executeChecksParallel(t,n,r){try{let i=t.map(async t=>{let i=await e.executeWorker(t.id,r),a=F(i.snapshot),o=$.getEntry(n,t.id),s=J(a,o);return{baseline:o,checkId:t.id,duration:i.duration,hasImprovement:s.hasImprovement,hasRegression:s.hasRegression,hasRelocation:s.hasRelocation,isInitial:s.isInitial,newIssues:s.newIssues,removedIssues:s.removedIssues,snapshot:a}});return await Promise.all(i)}catch(e){return M.error(`Parallel execution failed:`,e),null}}static async executeWorker(e,t){return new Promise((n,r)=>{let i=new a(Oe,{workerData:{checkId:e,cwd:t}});i.on(`message`,n),i.on(`error`,r),i.on(`exit`,e=>{e!==0&&r(Error(`Worker stopped with exit code ${e}`))})})}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:t.filter(e=>!(r&&!r.test(e.id)||i?.test(e.id)))};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=a.length;M.start(`Running ${o} check${o===1?``:`s`}...`);let s=o>1?await e.executeChecksParallel(a,i,this.cwd):await this.executeChecksSequential(a,i);if(!s)return{exitCode:2,hasImprovement:!1,hasRegression:!0,results:[],totalDuration:performance.now()-r};let c=!1,l=!1,u=!1,d=[];for(let e of s)e.hasRegression&&(c=!0),e.hasImprovement&&(l=!0),e.isInitial&&(u=!0),(e.hasImprovement||e.hasRelocation||n.force||e.isInitial)&&d.push({checkId:e.checkId,entry:{items:e.snapshot.items,type:e.snapshot.type}});let f=d.length>0?$.batchUpdate(i,d):i??(s.length>0?$.create({}):null),p=!!(n.only??n.skip),{baseline:m,prunedIds:h}=f!==null&&!p?$.prune(f,t.checks.map(e=>e.id)):{baseline:f,prunedIds:[]};return m!==null&&m!==i&&(!c||(n.force??!1)||u||h.length>0)&&await this.baselineManager.save(m,n.force),{exitCode:c&&!n.force?1:0,hasImprovement:l,hasRegression:c,results:s,totalDuration:performance.now()-r}}async executeChecksSequential(e,n){try{let n=t.getRequiredTypes(e);await Promise.all([this.registry.setup(n),this.registry.validate(n)])}catch(e){return M.error(`Setup failed:`,e),null}let r=[];for(let t of e)try{let e=performance.now(),i=await this.registry.get(t.config.type).run(t.config),a=performance.now()-e,o=F(i),s=$.getEntry(n,t.id),c=J(o,s);r.push({baseline:s,checkId:t.id,duration:a,hasImprovement:c.hasImprovement,hasRegression:c.hasRegression,hasRelocation:c.hasRelocation,isInitial:c.isInitial,newIssues:c.newIssues,removedIssues:c.removedIssues,snapshot:o})}catch(e){return M.error(`Error running check "${t.id}":`,e),null}return r}};async function Ae(n={}){let{config:r,...i}=n,a=r??await e(),o=new t;return o.init(a),new ke(o).run(a,i)}export{x as _,ie as a,D as c,O as d,k as f,_ as g,b as h,A as i,T as l,S as m,N as n,ae as o,re as p,M as r,w as s,Ae as t,E as u,v,ee as y};
|
|
@@ -50,6 +50,10 @@ interface Snapshot {
|
|
|
50
50
|
items: Issue[];
|
|
51
51
|
type: SnapshotType;
|
|
52
52
|
}
|
|
53
|
+
interface BaselineEntry {
|
|
54
|
+
items: Issue[];
|
|
55
|
+
type: SnapshotType;
|
|
56
|
+
}
|
|
53
57
|
/**
|
|
54
58
|
* Configuration for an ESLint check.
|
|
55
59
|
*
|
|
@@ -257,6 +261,41 @@ interface Config {
|
|
|
257
261
|
*/
|
|
258
262
|
runners?: CheckRunner[];
|
|
259
263
|
}
|
|
264
|
+
interface CheckResult {
|
|
265
|
+
baseline: BaselineEntry | undefined;
|
|
266
|
+
checkId: string;
|
|
267
|
+
/**
|
|
268
|
+
* Duration of the check run in milliseconds.
|
|
269
|
+
*/
|
|
270
|
+
duration?: number;
|
|
271
|
+
hasImprovement: boolean;
|
|
272
|
+
hasRegression: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Indicates whether any issues were relocated (i.e., their
|
|
275
|
+
* file, line, or column changed) compared to the baseline.
|
|
276
|
+
*/
|
|
277
|
+
hasRelocation: boolean;
|
|
278
|
+
isInitial: boolean;
|
|
279
|
+
newIssues: Issue[];
|
|
280
|
+
removedIssues: Issue[];
|
|
281
|
+
snapshot: Snapshot;
|
|
282
|
+
}
|
|
283
|
+
interface RunResult {
|
|
284
|
+
exitCode: number;
|
|
285
|
+
hasImprovement: boolean;
|
|
286
|
+
hasRegression: boolean;
|
|
287
|
+
results: CheckResult[];
|
|
288
|
+
/**
|
|
289
|
+
* Total duration of the run in milliseconds.
|
|
290
|
+
*/
|
|
291
|
+
totalDuration?: number;
|
|
292
|
+
}
|
|
293
|
+
interface RunOptions {
|
|
294
|
+
config?: Config | undefined;
|
|
295
|
+
force?: boolean | undefined;
|
|
296
|
+
only?: string | undefined;
|
|
297
|
+
skip?: string | undefined;
|
|
298
|
+
}
|
|
260
299
|
/**
|
|
261
300
|
* Interface that all check runners must implement.
|
|
262
301
|
*
|
|
@@ -348,173 +387,7 @@ interface CustomCheckDefinition<TConfig extends Record<string, unknown> = Record
|
|
|
348
387
|
validate?(): Promise<void>;
|
|
349
388
|
}
|
|
350
389
|
//#endregion
|
|
351
|
-
//#region src/
|
|
352
|
-
|
|
353
|
-
* Define a mejora configuration.
|
|
354
|
-
*
|
|
355
|
-
* @param config - Configuration object
|
|
356
|
-
*
|
|
357
|
-
* @param config.checks - Array of checks to run
|
|
358
|
-
*
|
|
359
|
-
* @returns A mejora configuration object
|
|
360
|
-
*
|
|
361
|
-
* @example
|
|
362
|
-
* ```ts
|
|
363
|
-
* import { defineConfig, eslint, typescript, regex } from "mejora";
|
|
364
|
-
*
|
|
365
|
-
* export default defineConfig({
|
|
366
|
-
* checks: [
|
|
367
|
-
* eslint({
|
|
368
|
-
* name: "no-console",
|
|
369
|
-
* files: ["src/**\/*.ts"],
|
|
370
|
-
* rules: { "no-console": "error" }
|
|
371
|
-
* }),
|
|
372
|
-
* typescript({
|
|
373
|
-
* name: "strict",
|
|
374
|
-
* compilerOptions: { noImplicitAny: true }
|
|
375
|
-
* }),
|
|
376
|
-
* regex({
|
|
377
|
-
* name: "no-todos",
|
|
378
|
-
* files: ["**\/*"],
|
|
379
|
-
* patterns: [{ pattern: /TODO/g }]
|
|
380
|
-
* })
|
|
381
|
-
* ]
|
|
382
|
-
* });
|
|
383
|
-
* ```
|
|
384
|
-
*/
|
|
385
|
-
declare function defineConfig(config: {
|
|
386
|
-
checks: Check[];
|
|
387
|
-
}): {
|
|
388
|
-
checks: Check[];
|
|
389
|
-
runners: CheckRunner<unknown>[];
|
|
390
|
-
};
|
|
391
|
-
//#endregion
|
|
392
|
-
//#region src/core/define-check.d.ts
|
|
393
|
-
/**
|
|
394
|
-
* Configuration for a check instance, combining the check-specific config
|
|
395
|
-
* with required metadata fields.
|
|
396
|
-
*/
|
|
397
|
-
type UserCheckConfig<TConfig> = TConfig & {
|
|
398
|
-
/**
|
|
399
|
-
* Unique identifier for this check instance.
|
|
400
|
-
* Used in baseline tracking and output.
|
|
401
|
-
*/
|
|
402
|
-
name: string;
|
|
403
|
-
};
|
|
404
|
-
/**
|
|
405
|
-
* Define a custom check for use with mejora().
|
|
406
|
-
*
|
|
407
|
-
* This is the primitive used by all checks (built-in and custom) to create
|
|
408
|
-
* reusable check definitions. Multiple check instances with the same type
|
|
409
|
-
* share a single runner for optimal performance.
|
|
410
|
-
*
|
|
411
|
-
* @param definition - Custom check definition with type, run function, and optional hooks.
|
|
412
|
-
*
|
|
413
|
-
* @returns A factory function that creates Check objects with different configurations.
|
|
414
|
-
*
|
|
415
|
-
* @example
|
|
416
|
-
* ```ts
|
|
417
|
-
* import { defineConfig, defineCheck } from "mejora";
|
|
418
|
-
*
|
|
419
|
-
* const noHardcodedUrls = defineCheck<{ files: string[] }>({
|
|
420
|
-
* type: "no-hardcoded-urls",
|
|
421
|
-
* async run(config) {
|
|
422
|
-
* const violations: IssueInput[] = [];
|
|
423
|
-
* // Custom checking logic using config.files
|
|
424
|
-
* return violations;
|
|
425
|
-
* }
|
|
426
|
-
* });
|
|
427
|
-
*
|
|
428
|
-
* export default defineConfig({
|
|
429
|
-
* checks: [
|
|
430
|
-
* noHardcodedUrls({ name: "urls-in-src", files: ["src/**\/*.ts"] }),
|
|
431
|
-
* noHardcodedUrls({ name: "urls-in-lib", files: ["lib/**\/*.ts"] }),
|
|
432
|
-
* ]
|
|
433
|
-
* });
|
|
434
|
-
* ```
|
|
435
|
-
*/
|
|
436
|
-
declare function defineCheck<TConfig extends Record<string, any> = Record<string, any>>(definition: CustomCheckDefinition<TConfig>): (userConfig: UserCheckConfig<TConfig>) => Check;
|
|
437
|
-
//#endregion
|
|
438
|
-
//#region src/runners/eslint.d.ts
|
|
439
|
-
/**
|
|
440
|
-
* Create an ESLint check for use with mejora().
|
|
441
|
-
*
|
|
442
|
-
* @param config - ESLint check configuration options including name.
|
|
443
|
-
*
|
|
444
|
-
* @returns A Check object for use with mejora().
|
|
445
|
-
*
|
|
446
|
-
* @example
|
|
447
|
-
* ```ts
|
|
448
|
-
* import { defineConfig, eslint } from "mejora";
|
|
449
|
-
*
|
|
450
|
-
* export default defineConfig({
|
|
451
|
-
* checks: [
|
|
452
|
-
* eslint({
|
|
453
|
-
* name: "no-console",
|
|
454
|
-
* files: ["src/**\/*.ts"],
|
|
455
|
-
* rules: { "no-console": "error" }
|
|
456
|
-
* })
|
|
457
|
-
* ]
|
|
458
|
-
* });
|
|
459
|
-
* ```
|
|
460
|
-
*/
|
|
461
|
-
declare const eslint: (userConfig: ESLintCheckConfig & {
|
|
462
|
-
name: string;
|
|
463
|
-
}) => Check;
|
|
464
|
-
//#endregion
|
|
465
|
-
//#region src/runners/regex.d.ts
|
|
466
|
-
/**
|
|
467
|
-
* Create a regex check for use with mejora().
|
|
468
|
-
*
|
|
469
|
-
* @param config - Regex check configuration options including name.
|
|
470
|
-
*
|
|
471
|
-
* @returns A Check object for use with mejora().
|
|
472
|
-
*
|
|
473
|
-
* @example
|
|
474
|
-
* ```ts
|
|
475
|
-
* import { defineConfig, regex } from "mejora";
|
|
476
|
-
*
|
|
477
|
-
* export default defineConfig({
|
|
478
|
-
* checks: [
|
|
479
|
-
* regex({
|
|
480
|
-
* name: "no-todos",
|
|
481
|
-
* files: ["src/**\/*.ts"],
|
|
482
|
-
* patterns: [
|
|
483
|
-
* { pattern: /TODO/g, message: "TODO comment found" }
|
|
484
|
-
* ]
|
|
485
|
-
* })
|
|
486
|
-
* ]
|
|
487
|
-
* });
|
|
488
|
-
* ```
|
|
489
|
-
*/
|
|
490
|
-
declare const regex: (userConfig: RegexCheckConfig & {
|
|
491
|
-
name: string;
|
|
492
|
-
}) => Check;
|
|
493
|
-
//#endregion
|
|
494
|
-
//#region src/runners/typescript.d.ts
|
|
495
|
-
/**
|
|
496
|
-
* Create a TypeScript check for use with mejora().
|
|
497
|
-
*
|
|
498
|
-
* @param config - TypeScript check configuration options including name.
|
|
499
|
-
*
|
|
500
|
-
* @returns A Check object for use with mejora().
|
|
501
|
-
*
|
|
502
|
-
* @example
|
|
503
|
-
* ```ts
|
|
504
|
-
* import { defineConfig, typescript } from "mejora";
|
|
505
|
-
*
|
|
506
|
-
* export default defineConfig({
|
|
507
|
-
* checks: [
|
|
508
|
-
* typescript({
|
|
509
|
-
* name: "strict-types",
|
|
510
|
-
* compilerOptions: { noImplicitAny: true }
|
|
511
|
-
* })
|
|
512
|
-
* ]
|
|
513
|
-
* });
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
declare const typescript: (userConfig: TypeScriptCheckConfig & {
|
|
517
|
-
name: string;
|
|
518
|
-
}) => Check;
|
|
390
|
+
//#region src/run.d.ts
|
|
391
|
+
declare function run(options?: RunOptions): Promise<RunResult>;
|
|
519
392
|
//#endregion
|
|
520
|
-
export {
|
|
393
|
+
export { Config as a, Issue as c, RegexCheckConfig as d, RunOptions as f, TypeScriptCheckConfig as h, CheckRunner as i, IssueInput as l, Snapshot as m, Check as n, CustomCheckDefinition as o, RunResult as p, CheckResult as r, ESLintCheckConfig as s, run as t, RawSnapshot as u };
|
package/dist/run.d.mts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { t as run } from "./run-BHo9Tcdv.mjs";
|
|
2
|
+
export { run };
|
package/dist/run.mjs
CHANGED
|
@@ -1,43 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import{n as e}from"./config-C0qrgNuO.mjs";import{n as t,o as n,r,t as i}from"./hash-BaLFZO2A.mjs";import{t as a}from"./check-registry-mjn8FfPF.mjs";import{fileURLToPath as o}from"node:url";import{mkdir as s,readFile as c,writeFile as l}from"node:fs/promises";import{inspect as u,parseArgs as d,styleText as f}from"node:util";import{Worker as p}from"node:worker_threads";import{env as m}from"node:process";const h=e=>t=>f(e,typeof t==`number`?t.toString():t),g=h(`blue`),_=h(`bold`),ee=h(`cyan`),v=h(`dim`),y=h(`green`),b=h(`red`),x=h(`gray`),S=h(`underline`),te=h(`yellow`);function ne(e){let t=[e.message];if(e.stack){let n=e.stack.split(`
|
|
3
|
-
`).slice(1).map(e=>v(e.trim())).join(`
|
|
4
|
-
`);t.push(n)}return t.join(`
|
|
5
|
-
`)}function C(...e){return e.map(e=>typeof e==`string`?e:e instanceof Error?ne(e):u(e,{colors:!1,depth:10})).join(` `)}const w={error:(...e)=>{console.error(b(`✖`),C(...e))},log:(...e)=>{console.log(C(...e))},start:(...e)=>{console.error(ee(`◐`),C(...e))},success:(...e)=>{console.error(y(`✔`),C(...e))}};function T(e,t){return e.file===t.file?e.line===t.line?e.column-t.column:e.line-t.line:e.file<t.file?-1:1}function re(e){return`${e.file} - ${e.rule}: ${e.message}`}function ie(e){let t=Map.groupBy(e,re),n=[];for(let[e,r]of t){r.sort(T);for(let[t,a]of r.entries())n.push({...a,id:i(`${e}:${t}`)})}return n}function E(e){return{items:ie(e.items).toSorted(T),type:`items`}}function D(e,t){return e===1?t:`${t}s`}const O=`__unparsable__`,k={"<":`<`,">":`>`,"[":`[`,"]":`]`},A=/[<>[\]]/g;function j(e){return e.replaceAll(A,e=>k[e])}function M(e,t,r){let i=n(t,e);return r?`${i}#L${r}`:i}function N(e,t){return`[${e}](${t})`}function ae(e,t){let n=M(e.file,t,e.line);return`- ${N(e.line?`Line ${e.line}`:e.file,n)} - ${`${e.rule}: ${j(e.message)}`}`}function oe(e){let t=Object.groupBy(e,e=>e.file||O);return Object.entries(t).map(([e,t=[]])=>({filePath:e,items:t})).toSorted((e,t)=>e.filePath===O?1:t.filePath===O||e.filePath<t.filePath?-1:e.filePath>t.filePath?1:0)}function se(e,t){let n=e.length,r=D(n,`issue`),i=[`\n### Other Issues · ${t}\n`];for(let t of e)i.push(`- ${t.rule}: ${j(t.message)}`);return i.push(`\n${n} ${r} in Other Issues`,``),i.join(`
|
|
6
|
-
`)}function ce(e,t,n){if(e.filePath===O)return se(e.items,n);let r=M(e.filePath,t),i=N(e.filePath,r),a=e.items.length,o=D(a,`issue`),s=[`\n### ${i} · ${n}\n`];for(let n of e.items)s.push(ae(n,t));return s.push(`\n${a} ${o} in ${e.filePath}`,``),s.join(`
|
|
7
|
-
`)}function le(e,t,n){let r=t.length,i=D(r,`issue`),a=[`\n## ${e}\n`];if(t.length===0)return a.push(`No issues`),a.join(`
|
|
8
|
-
`);let o=oe(t);for(let t of o)a.push(ce(t,n,e));return a.push(`---\n${r} total ${i} for ${e}`),a.join(`
|
|
9
|
-
`)}function ue(e){return`${e.replaceAll(/\n{3,}/g,`
|
|
10
|
-
|
|
11
|
-
`).trimEnd()}\n`}function P(e,t){let n=[`<!-- prettier-ignore-start -->
|
|
12
|
-
`,`# Mejora Baseline
|
|
13
|
-
`,`This file represents the current accepted state of the codebase.`];for(let[r,{items:i=[]}]of Object.entries(e.checks))n.push(le(r,i,t));return n.push(`
|
|
14
|
-
<!-- prettier-ignore-end -->`),ue(n.join(`
|
|
15
|
-
`))}const de=[`CI`,`CONTINUOUS_INTEGRATION`].some(e=>m[e]&&m[e]!==`0`&&m[e]!==`false`),F=(e,t)=>e.id<t.id?-1:e.id>t.id?1:0;function fe(){return{hasImprovement:!1,hasRegression:!1,hasRelocation:!1,isInitial:!0,newIssues:[],removedIssues:[]}}function pe(e,t,n){return{hasImprovement:t.length>0,hasRegression:e.length>0,hasRelocation:n,isInitial:!1,newIssues:e.toSorted(F),removedIssues:t.toSorted(F)}}function I(e=[]){return new Map(e.map(e=>[e.id,e]))}function L(e){return new Set(e.keys())}function R(e,t){let n=[];for(let r of t){let t=e.get(r);n.push(t)}return n}function z(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 B(e,t){let n=I(e.items),r=I(t.items),i=L(n),a=L(r),o=i.difference(a),s=a.difference(i);return pe(R(n,o),R(r,s),i.size>o.size?z(n,r):!1)}function V(e,t){return t?B(e,t):fe()}const H=(e,t)=>{if(!t)return!1;let n=e.items,r=t.items;if(n.length!==r.length)return!1;let i=n.toSorted(F),a=r.toSorted(F);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 me(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 he(e,t){return`${e}${`
|
|
16
|
-
}`.repeat(t)}`}function ge(e){let t=0;for(let n of e)n===`{`?t++:n===`}`&&t--;return t===0?e:t<0?me(e,-t):he(e,t)}function _e(e){try{return JSON.parse(e)}catch{return}}function U(e){let t=e.trim();return t.endsWith(`,`)?t.slice(0,-1):t}function ve(e){return`{
|
|
17
|
-
"version": 2,
|
|
18
|
-
${U(e)}
|
|
19
|
-
}`}function W(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 G(e){try{let t=e.trim();if(t===``)return{checks:{},version:2};let n=_e(t);if(n)return W(n);let r=ve(ge(U(t)));return W(JSON.parse(r))}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to parse baseline during conflict resolution: ${t}`,{cause:e})}}function ye(e){let t=[...e.matchAll(/^<<<<<<< .+\r?\n([\s\S]*?)^=======(?:\r?\n([\s\S]*?)\r?\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 be(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(F),type:`items`};return{checks:n,version:2}}function xe(e){let t=ye(e),n=[];for(let{ours:e,theirs:r}of t)n.push(G(e),G(r));return be(n)}var K=class e{baselineDir;baselinePath;mdPath;constructor(e=`.mejora/baseline.json`){this.baselinePath=e,this.baselineDir=r(e),this.mdPath=e.replace(/\.json$/,`.md`)}static batchUpdate(t,n){let r=t??e.create({}),i={...r.checks},a=!1;for(let{checkId:e,entry:t}of n)H(t,r.checks[e])||(i[e]=t,a=!0);return a?{...r,checks:i}:r}static create(e){return{checks:e,version:2}}static getEntry(e,t){return e?.checks[t]}static prune(e,t){let n=new Set(t),r=Object.keys(e.checks).filter(e=>!n.has(e));if(r.length===0)return{baseline:e,prunedIds:r};let i=Object.fromEntries(Object.entries(e.checks).filter(([e])=>n.has(e)));return{baseline:{...e,checks:i},prunedIds:r}}static update(t,n,r){let i=t??e.create({}),a=i.checks[n];return H(r,a)?i:{...i,checks:{...i.checks,[n]:r}}}async load(){try{let e=await c(this.baselinePath,`utf8`);if(e.includes(`<<<<<<<`)){w.start(`Merge conflict detected in baseline, auto-resolving...`);let t=xe(e);return await this.save(t,!0),w.success(`Baseline conflict resolved`),t}let t=JSON.parse(e);return await this.resolveMarkdownConflictIfNeeded(t),t}catch(e){if(e.code===`ENOENT`)return null;throw e}}async save(e,t=!1){if(de&&!t)return;let n=`${JSON.stringify(e,null,2)}\n`,r=P(e,this.baselineDir);await s(this.baselineDir,{recursive:!0}),await Promise.all([l(this.baselinePath,n,`utf8`),l(this.mdPath,r,`utf8`)])}async resolveMarkdownConflictIfNeeded(e){try{if((await c(this.mdPath,`utf8`)).includes(`<<<<<<<`)){w.start(`Merge conflict detected in markdown report, regenerating...`);let t=P(e,this.baselineDir);await l(this.mdPath,t,`utf8`),w.success(`Markdown report regenerated`)}}catch{}}};const Se=o(new URL(`workers/check.mjs`,import.meta.url));var Ce=class e{baselineManager;registry;constructor(e,t){this.registry=e,this.baselineManager=new K(t)}static async executeChecksParallel(t,n){try{let r=t.map(async t=>{let r=await e.executeWorker(t.id),i=E(r.snapshot),a=K.getEntry(n,t.id),o=V(i,a);return{baseline:a,checkId:t.id,duration:r.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(r)}catch(e){return w.error(`Parallel execution failed:`,e),null}}static async executeWorker(e){return new Promise((t,n)=>{let r=new p(Se,{workerData:{checkId:e}});r.on(`message`,t),r.on(`error`,n),r.on(`exit`,e=>{e!==0&&n(Error(`Worker stopped with exit code ${e}`))})})}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:t.filter(e=>!(r&&!r.test(e.id)||i?.test(e.id)))};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=a.length;w.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=!1,l=!1,u=!1,d=[];for(let e of s)e.hasRegression&&(c=!0),e.hasImprovement&&(l=!0),e.isInitial&&(u=!0),(e.hasImprovement||e.hasRelocation||n.force||e.isInitial)&&d.push({checkId:e.checkId,entry:{items:e.snapshot.items,type:e.snapshot.type}});let f=d.length>0?K.batchUpdate(i,d):i??(s.length>0?K.create({}):null),p=!!(n.only??n.skip),{baseline:m,prunedIds:h}=f!==null&&!p?K.prune(f,t.checks.map(e=>e.id)):{baseline:f,prunedIds:[]};return m!==null&&m!==i&&(!c||(n.force??!1)||u||h.length>0)&&await this.baselineManager.save(m,n.force),{exitCode:c&&!n.force?1:0,hasImprovement:l,hasRegression:c,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 w.error(`Setup failed:`,e),null}let n=[];for(let r of e)try{let e=performance.now(),i=await this.registry.get(r.config.type).run(r.config),a=performance.now()-e,o=E(i),s=K.getEntry(t,r.id),c=V(o,s);n.push({baseline:s,checkId:r.id,duration:a,hasImprovement:c.hasImprovement,hasRegression:c.hasRegression,hasRelocation:c.hasRelocation,isInitial:c.isInitial,newIssues:c.newIssues,removedIssues:c.removedIssues,snapshot:o})}catch(e){return w.error(`Error running check "${r.id}":`,e),null}return n}};function q(e,t){if(!(e===void 0||t===0))return e/t}function we(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:q(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)}function Te(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 J(e){let t=Math.round(e);return t<1?`<1ms`:Te(t)}const Y=` `,Ee=`${Y} `;function De(e){return e===`initial`?v(`→`):e===`improvement`?y(`↑`):b(`↓`)}function Oe(e,n,i){let a=r(e),o=t(e),s=n>0?`:${n}:${i>0?i:1}`:``;return`${a===`.`?``:v(`${a}/`)}${S(o)}${s?v(s):``}`}function ke(e,t){return[`${De(t)} ${Oe(e.file,e.line,e.column)} ${v(e.rule)}`,e.message]}function X(e,t){let n=[],r=e.slice(0,10);for(let e of r){let[r,i]=ke(e,t);n.push(`${Y}${r}`,`${Ee}${i}`)}let i=e.length-r.length;return i>0&&n.push(`${Y}${v(`... and ${i} more`)}`),n}function Z(e){return e===void 0?[]:[` ${v(`Duration`)} ${J(e)}`]}function Ae(e){return[` ${v(`Issues`)} ${_(e)}`]}function Q(e){return[...Z(e.duration),...Ae(e.snapshot.items.length)]}function je(e){if(!e.hasRegression)return[];let t=e.newIssues.length;return[` ${b(t)} new ${D(t,`issue`)} (${D(t,`regression`)}):`,...X(e.newIssues,`regression`)]}function Me(e){if(!e.hasImprovement)return[];let t=e.removedIssues.length;return[` ${y(t)} ${D(t,`issue`)} fixed (${D(t,`improvement`)}):`,...X(e.removedIssues,`improvement`)]}function Ne(e,t){let n=t?``:`
|
|
20
|
-
`,r=e.snapshot.items.length,i=[`${n}${g(`ℹ`)} ${e.checkId}:`,` Initial baseline created with ${g(r)} ${D(r,`issue`)}`];return e.snapshot.items.length>0&&i.push(...X(e.snapshot.items,`initial`)),i.push(``,...Q(e)),i}function Pe(e,t){return[`${t?``:`
|
|
21
|
-
`}${e.hasRegression?b(`✖`):y(`✔`)} ${e.checkId}:`,...je(e),...Me(e),``,...Q(e)]}function Fe(e,t){let n=t?``:`
|
|
22
|
-
`,r=e.snapshot.items.length,i=r===0?y(`✔`):x(`ℹ`),a=r===0?y(r):_(r);return e.duration===void 0?[`${n}${i} ${e.checkId} (${a})`]:[`${n}${i} ${e.checkId} (${a}) ${v(J(e.duration))}`]}function Ie(e,t){return e.isInitial?Ne(e,t):e.hasRegression||e.hasImprovement?Pe(e,t):Fe(e,t)}function Le(e,t,n){return t?g(`✔ Initial baseline created successfully`):e.hasRegression?n?te(`⚠ Regressions detected (forced)`):`${b(`✗ Regressions detected`)} - Run failed`:e.hasImprovement?`${y(`✔ Improvements detected`)} - Baseline updated`:y(`✔ All checks passed`)}function Re(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=[` ${v(`Improvements`)} ${y(n.totalImprovements)}`,` ${v(`Regressions`)} ${b(n.totalRegressions)}`,` ${v(`Initial`)} ${g(n.totalInitial)}`,` ${v(`Checks`)} ${e.results.length}`,` ${v(`Issues`)} ${_(n.totalIssues)}`],i=q(e.totalDuration,e.results.length);return e.totalDuration!==void 0&&i!==void 0&&r.push(` ${v(`Duration`)} ${J(e.totalDuration)} ${x(`(avg ${J(i)})`)}`),r.push(``,Le(e,n.hasAnyInitial,t)),r.join(`
|
|
23
|
-
`)}function ze(e,t){let n=[];for(let[t,r]of e.results.entries())n.push(...Ie(r,t===0));return n.length>0&&n.push(``),n.push(Re(e,t)),n.join(`
|
|
24
|
-
`)}const{values:$}=d({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&&(w.log(`
|
|
25
|
-
mejora - Prevent regressions by allowing only improvement
|
|
26
|
-
|
|
27
|
-
Usage:
|
|
28
|
-
mejora [options]
|
|
29
|
-
|
|
30
|
-
Options:
|
|
31
|
-
-f, --force Update baseline even with regressions
|
|
32
|
-
--json Output results as JSON
|
|
33
|
-
--only <pattern> Run only checks matching pattern (e.g., "eslint > *")
|
|
34
|
-
--skip <pattern> Skip checks matching pattern
|
|
35
|
-
-h, --help Show this help message
|
|
36
|
-
|
|
37
|
-
Examples:
|
|
38
|
-
mejora
|
|
39
|
-
mejora --force
|
|
40
|
-
mejora --json
|
|
41
|
-
mejora --only "eslint > *"
|
|
42
|
-
mejora --skip typescript
|
|
43
|
-
`),process.exit(0));try{let t=new a,n=await e();t.init(n);let r=await new Ce(t).run(n,{force:$.force,json:$.json,only:$.only,skip:$.skip});$.json?w.log(we(r)):(w.log(``),w.log(ze(r,$.force))),process.exit(r.exitCode)}catch(e){e instanceof Error?w.error(e.message):w.error(e),process.exit(2)}export{};
|
|
1
|
+
import"./config-VQG8vlqE.mjs";import{t as e}from"./run-B9dk_cJ1.mjs";export{e as run};
|
package/dist/workers/check.d.mts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as RawSnapshot } from "../run-BHo9Tcdv.mjs";
|
|
2
|
+
import "../index-BqVC-9Iv.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/workers/check.d.ts
|
|
4
5
|
declare function checkWorker({
|
|
5
|
-
checkId
|
|
6
|
+
checkId,
|
|
7
|
+
cwd
|
|
6
8
|
}: {
|
|
7
9
|
checkId: string;
|
|
10
|
+
cwd: string;
|
|
8
11
|
}): Promise<{
|
|
9
12
|
duration: number;
|
|
10
13
|
snapshot: RawSnapshot;
|
package/dist/workers/check.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"../config-
|
|
1
|
+
import{n as e,r as t}from"../config-VQG8vlqE.mjs";import{parentPort as n,workerData as r}from"node:worker_threads";let i=null,a=null;const o=new Map;async function s({checkId:n,cwd:r}){i||(a??=(async()=>{i=await e(r)})(),await a);let s=i?.checks.find(e=>e.id===n);if(!s)throw Error(`Check not found in config: ${n}`);let c=s.config,l=o.get(c.type);if(!l){l=new t,l.init(i);let e=new Set([c.type]);await Promise.all([l.setup(e),l.validate(e)]),o.set(c.type,l)}let u=performance.now(),d=await l.get(c.type).run(c);return{duration:performance.now()-u,snapshot:d}}if(n){let e=await s(r);n.postMessage(e)}export{s as checkWorker};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mejora",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Prevent regressions. Allow improvement.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"regression",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"main": "./dist/index.mjs",
|
|
32
32
|
"types": "./dist/index.d.mts",
|
|
33
33
|
"bin": {
|
|
34
|
-
"mejora": "./dist/
|
|
34
|
+
"mejora": "./dist/cli.mjs"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=class{runners=new Map;static getRequiredTypes(e){return new Set(e.map(e=>e.config.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)}init(e={}){if(e.runners)for(let t of e.runners)this.register(t)}register(e){this.runners.has(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)}};export{e as t};
|
package/dist/config-C0qrgNuO.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import{pathToFileURL as t}from"node:url";var n=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),r=e(import.meta.url),i=n(((e,t)=>{let n=r(`path`),i=r(`fs`),a=r(`os`),o=r(`url`),s=i.promises.readFile;function c(e,t){return[`package.json`,`.${e}rc.json`,`.${e}rc.js`,`.${e}rc.cjs`,...t?[]:[`.${e}rc.mjs`],`.config/${e}rc`,`.config/${e}rc.json`,`.config/${e}rc.js`,`.config/${e}rc.cjs`,...t?[]:[`.config/${e}rc.mjs`],`${e}.config.js`,`${e}.config.cjs`,...t?[]:[`${e}.config.mjs`]]}function l(e){return n.dirname(e)||n.sep}let u=(e,t)=>JSON.parse(t),d=typeof __webpack_require__==`function`?__non_webpack_require__:r,f=Object.freeze({".js":d,".json":d,".cjs":d,noExt:u});t.exports.defaultLoadersSync=f;let p=async e=>{try{return(await import(o.pathToFileURL(e).href)).default}catch(t){try{return d(e)}catch(e){throw e.code===`ERR_REQUIRE_ESM`||e instanceof SyntaxError&&e.toString().includes(`Cannot use import statement outside a module`)?t:e}}},m=Object.freeze({".js":p,".mjs":p,".cjs":p,".json":u,noExt:u});t.exports.defaultLoaders=m;function h(e,t,r){let i={stopDir:a.homedir(),searchPlaces:c(e,r),ignoreEmptySearchPlaces:!0,cache:!0,transform:e=>e,packageProp:[e],...t,loaders:{...r?f:m,...t.loaders}};return i.searchPlaces.forEach(e=>{let t=n.extname(e)||`noExt`,r=i.loaders[t];if(!r)throw Error(`Missing loader for extension "${e}"`);if(typeof r!=`function`)throw Error(`Loader for extension "${e}" is not a function: Received ${typeof r}.`)}),i}function g(e,t){return typeof e==`string`&&e in t?t[e]:(Array.isArray(e)?e:e.split(`.`)).reduce((e,t)=>e===void 0?e:e[t],t)||null}function _(e){if(!e)throw Error(`load must pass a non-empty string`)}function v(e,t){if(!e)throw Error(`No loader specified for extension "${t}"`);if(typeof e!=`function`)throw Error(`loader is not a function`)}let y=e=>(t,n,r)=>(e&&t.set(n,r),r);t.exports.lilconfig=function(e,t){let{ignoreEmptySearchPlaces:r,loaders:a,packageProp:o,searchPlaces:c,stopDir:u,transform:d,cache:f}=h(e,t??{},!1),p=new Map,m=new Map,b=y(f);return{async search(e=process.cwd()){let t={config:null,filepath:``},m=new Set,h=e;dirLoop:for(;;){if(f){let e=p.get(h);if(e!==void 0){for(let t of m)p.set(t,e);return e}m.add(h)}for(let e of c){let c=n.join(h,e);try{await i.promises.access(c)}catch{continue}let l=String(await s(c)),u=n.extname(e)||`noExt`,d=a[u];if(e===`package.json`){let e=g(o,await d(c,l));if(e!=null){t.config=e,t.filepath=c;break dirLoop}continue}let f=l.trim()===``;if(!(f&&r)){f?(t.isEmpty=!0,t.config=void 0):(v(d,u),t.config=await d(c,l)),t.filepath=c;break dirLoop}}if(h===u||h===l(h))break dirLoop;h=l(h)}let _=t.filepath===``&&t.config===null?d(null):d(t);if(f)for(let e of m)p.set(e,_);return _},async load(e){_(e);let t=n.resolve(process.cwd(),e);if(f&&m.has(t))return m.get(t);let{base:i,ext:c}=n.parse(t),l=c||`noExt`,u=a[l];v(u,l);let p=String(await s(t));if(i===`package.json`)return b(m,t,d({config:g(o,await u(t,p)),filepath:t}));let h={config:null,filepath:t},y=p.trim()===``;return y&&r?b(m,t,d({config:void 0,filepath:t,isEmpty:!0})):(h.config=y?void 0:await u(t,p),b(m,t,d(y?{...h,isEmpty:y,config:void 0}:h)))},clearLoadCache(){f&&m.clear()},clearSearchCache(){f&&p.clear()},clearCaches(){f&&(m.clear(),p.clear())}}},t.exports.lilconfigSync=function(e,t){let{ignoreEmptySearchPlaces:r,loaders:a,packageProp:o,searchPlaces:s,stopDir:c,transform:u,cache:d}=h(e,t??{},!0),f=new Map,p=new Map,m=y(d);return{search(e=process.cwd()){let t={config:null,filepath:``},p=new Set,m=e;dirLoop:for(;;){if(d){let e=f.get(m);if(e!==void 0){for(let t of p)f.set(t,e);return e}p.add(m)}for(let e of s){let s=n.join(m,e);try{i.accessSync(s)}catch{continue}let c=n.extname(e)||`noExt`,l=a[c],u=String(i.readFileSync(s));if(e===`package.json`){let e=g(o,l(s,u));if(e!=null){t.config=e,t.filepath=s;break dirLoop}continue}let d=u.trim()===``;if(!(d&&r)){d?(t.isEmpty=!0,t.config=void 0):(v(l,c),t.config=l(s,u)),t.filepath=s;break dirLoop}}if(m===c||m===l(m))break dirLoop;m=l(m)}let h=t.filepath===``&&t.config===null?u(null):u(t);if(d)for(let e of p)f.set(e,h);return h},load(e){_(e);let t=n.resolve(process.cwd(),e);if(d&&p.has(t))return p.get(t);let{base:s,ext:c}=n.parse(t),l=c||`noExt`,f=a[l];v(f,l);let h=String(i.readFileSync(t));if(s===`package.json`)return u({config:g(o,f(t,h)),filepath:t});let y={config:null,filepath:t},b=h.trim()===``;return b&&r?m(p,t,u({filepath:t,config:void 0,isEmpty:!0})):(y.config=b?void 0:f(t,h),m(p,t,u(b?{...y,isEmpty:b,config:void 0}:y)))},clearLoadCache(){d&&p.clear()},clearSearchCache(){d&&f.clear()},clearCaches(){d&&(p.clear(),f.clear())}}}}))();const a=async e=>{let n=await import(t(e).href);return n&&typeof n==`object`&&`default`in n?n.default:n};function o(e){let t=[],n=new Set;for(let r of e.checks)if(r.__runnerFactory&&!n.has(r.config.type)){n.add(r.config.type);let e=r.__runnerFactory();t.push(e)}return{checks:e.checks,runners:t}}const s=async()=>{let e=await(0,i.lilconfig)(`mejora`,{loaders:{".js":a,".mjs":a,".mts":a,".ts":a},searchPlaces:[`mejora.config.ts`,`mejora.config.mts`,`mejora.config.js`,`mejora.config.mjs`]}).search(process.cwd());if(!e?.config)throw Error(`No configuration file found.`);return e.config};export{s as n,o as t};
|
package/dist/hash-BaLFZO2A.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{hash as e}from"node:crypto";const t=/^[A-Za-z]:\//;function n(e=``){return e&&e.replace(/\\/g,`/`).replace(t,e=>e.toUpperCase())}const r=/^[/\\]{2}/,i=/^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[A-Za-z]:[/\\]/,a=/^[A-Za-z]:$/,o=/^\/([A-Za-z]:)?$/,s=`/`,c=function(e){if(e.length===0)return`.`;e=n(e);let t=e.match(r),i=p(e),o=e[e.length-1]===`/`;return e=f(e,!i),e.length===0?i?`/`:o?`./`:`.`:(o&&(e+=`/`),a.test(e)&&(e+=`/`),t?i?`//${e}`:`//./${e}`:i&&!p(e)?`/${e}`:e)},l=function(...e){let t=``;for(let n of e)if(n)if(t.length>0){let e=t[t.length-1]===`/`,r=n[0]===`/`;e&&r?t+=n.slice(1):t+=e||r?n:`/${n}`}else t+=n;return c(t)};function u(){return typeof process<`u`&&typeof process.cwd==`function`?process.cwd().replace(/\\/g,`/`):`/`}const d=function(...e){e=e.map(e=>n(e));let t=``,r=!1;for(let n=e.length-1;n>=-1&&!r;n--){let i=n>=0?e[n]:u();!i||i.length===0||(t=`${i}/${t}`,r=p(i))}return t=f(t,!r),r&&!p(t)?`/${t}`:t.length>0?t:`.`};function f(e,t){let n=``,r=0,i=-1,a=0,o=null;for(let s=0;s<=e.length;++s){if(s<e.length)o=e[s];else if(o===`/`)break;else o=`/`;if(o===`/`){if(!(i===s-1||a===1))if(a===2){if(n.length<2||r!==2||n[n.length-1]!==`.`||n[n.length-2]!==`.`){if(n.length>2){let e=n.lastIndexOf(`/`);e===-1?(n=``,r=0):(n=n.slice(0,e),r=n.length-1-n.lastIndexOf(`/`)),i=s,a=0;continue}else if(n.length>0){n=``,r=0,i=s,a=0;continue}}t&&(n+=n.length>0?`/..`:`..`,r=2)}else n.length>0?n+=`/${e.slice(i+1,s)}`:n=e.slice(i+1,s),r=s-i-1;i=s,a=0}else o===`.`&&a!==-1?++a:a=-1}return n}const p=function(e){return i.test(e)},m=function(e,t){let n=d(e).replace(o,`$1`).split(`/`),r=d(t).replace(o,`$1`).split(`/`);if(r[0][1]===`:`&&n[0][1]===`:`&&n[0]!==r[0])return r.join(`/`);let i=[...n];for(let e of i){if(r[0]!==e)break;n.shift(),r.shift()}return[...n.map(()=>`..`),...r].join(`/`)},h=function(e){let t=n(e).replace(/\/$/,``).split(`/`).slice(0,-1);return t.length===1&&a.test(t[0])&&(t[0]+=`/`),t.join(`/`)||(p(e)?`/`:`.`)},g=function(e,t){let r=n(e).split(`/`),i=``;for(let e=r.length-1;e>=0;e--){let t=r[e];if(t){i=t;break}}return t&&i.endsWith(t)?i.slice(0,-t.length):i},_=t=>e(`sha256`,t,`hex`);export{l as a,s as c,p as i,g as n,m as o,h as r,d as s,_ as t};
|