legion-audit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/args.d.ts +18 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +97 -0
- package/dist/args.js.map +1 -0
- package/dist/audit.d.ts +4 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +81 -0
- package/dist/audit.js.map +1 -0
- package/dist/checks/directives.d.ts +8 -0
- package/dist/checks/directives.d.ts.map +1 -0
- package/dist/checks/directives.js +108 -0
- package/dist/checks/directives.js.map +1 -0
- package/dist/checks/gates.d.ts +16 -0
- package/dist/checks/gates.d.ts.map +1 -0
- package/dist/checks/gates.js +153 -0
- package/dist/checks/gates.js.map +1 -0
- package/dist/checks/git.d.ts +4 -0
- package/dist/checks/git.d.ts.map +1 -0
- package/dist/checks/git.js +36 -0
- package/dist/checks/git.js.map +1 -0
- package/dist/checks/lint.d.ts +10 -0
- package/dist/checks/lint.d.ts.map +1 -0
- package/dist/checks/lint.js +157 -0
- package/dist/checks/lint.js.map +1 -0
- package/dist/checks/lockfile.d.ts +3 -0
- package/dist/checks/lockfile.d.ts.map +1 -0
- package/dist/checks/lockfile.js +21 -0
- package/dist/checks/lockfile.js.map +1 -0
- package/dist/checks/test-coverage.d.ts +4 -0
- package/dist/checks/test-coverage.d.ts.map +1 -0
- package/dist/checks/test-coverage.js +62 -0
- package/dist/checks/test-coverage.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -0
- package/dist/exec.d.ts +15 -0
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +43 -0
- package/dist/exec.js.map +1 -0
- package/dist/files.d.ts +7 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +87 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/report/baseline.d.ts +3 -0
- package/dist/report/baseline.d.ts.map +1 -0
- package/dist/report/baseline.js +54 -0
- package/dist/report/baseline.js.map +1 -0
- package/dist/report/markdown.d.ts +3 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +122 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/report/score.d.ts +18 -0
- package/dist/report/score.d.ts.map +1 -0
- package/dist/report/score.js +40 -0
- package/dist/report/score.js.map +1 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Veracium LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# legion-audit
|
|
2
|
+
|
|
3
|
+
A read-only scorecard of a repo against
|
|
4
|
+
[LEGION-STANDARDS](https://github.com/Kprince1101/legion-toolkit#readme). It
|
|
5
|
+
runs the gates your CI already runs, counts what the
|
|
6
|
+
[`legion/*` rules](https://www.npmjs.com/package/eslint-plugin-legion) find,
|
|
7
|
+
lists every lint bypass, checks PR hygiene and which components and hooks
|
|
8
|
+
have no test file, and writes the result as Markdown and JSON. Nothing is
|
|
9
|
+
modified.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
yarn add -D legion-audit
|
|
13
|
+
yarn legion-audit --md AUDIT.md --json audit.json
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What it checks
|
|
17
|
+
|
|
18
|
+
| Check | Source of truth |
|
|
19
|
+
| ------------------ | ------------------------------------------------------------------------------------- |
|
|
20
|
+
| typecheck | `tsc --noEmit -p tsconfig.json` (skipped when there is no tsconfig or local tsc) |
|
|
21
|
+
| lint | the repo's oxlint or ESLint, `--format json`; counts per rule, `legion/*` findings |
|
|
22
|
+
| tests | the `test:ci` or `test` script, run through the detected package manager |
|
|
23
|
+
| format | `prettier --check .` (skipped when prettier is not installed) |
|
|
24
|
+
| dependency audit | `yarn npm audit` / `npm audit` / `pnpm audit` at high severity (skipped when offline) |
|
|
25
|
+
| lint bypasses | every `eslint-disable` / `oxlint-disable` directive and `NOSONAR`, counted per rule |
|
|
26
|
+
| file-wide disables | a bare `/* eslint-disable */` or `/* oxlint-disable */`; always a hard fail |
|
|
27
|
+
| PR hygiene | share of the last 80 commits whose subject carries a `(#123)` PR number |
|
|
28
|
+
| tests by file | components (`components/**`, `app/**`) and `use*` hooks without a matching test file |
|
|
29
|
+
| lockfile | exactly one lockfile at the root |
|
|
30
|
+
|
|
31
|
+
A tool that is not installed reports `skipped`, never `pass`.
|
|
32
|
+
|
|
33
|
+
## Output
|
|
34
|
+
|
|
35
|
+
`AUDIT.md` opens with the gate table, then per-rule counts, findings with
|
|
36
|
+
file and line, the bypass list, PR hygiene, tests by file, and the score
|
|
37
|
+
formula. `audit.json` carries the same data (`schema: 1`).
|
|
38
|
+
|
|
39
|
+
The score starts at 100 and subtracts documented penalties (see the bottom of
|
|
40
|
+
any report). It is meant for comparing runs of the same repo over time, not
|
|
41
|
+
for comparing repos to each other.
|
|
42
|
+
|
|
43
|
+
## In CI
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
legion-audit --baseline audit.json --json audit.json --md AUDIT.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
With `--baseline`, the exit code is non-zero on any regression: a gate that
|
|
50
|
+
flipped from pass to fail, more `legion/*` errors, more lint errors, more
|
|
51
|
+
file-wide disables, more files without tests, or a lower score. `--fail-on
|
|
52
|
+
error` fails on any failed gate or `legion/*` error regardless of baseline;
|
|
53
|
+
`--fail-on never` always exits 0.
|
|
54
|
+
|
|
55
|
+
## `legion-audit directives`
|
|
56
|
+
|
|
57
|
+
The fast subcommand: only the file-wide disable scan, exit 1 if any. A
|
|
58
|
+
file-wide disable suppresses every rule in the file including the one that
|
|
59
|
+
would report it, so no lint rule can catch it. Put this in front of the
|
|
60
|
+
linter:
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{ "scripts": { "lint": "legion-audit directives && oxlint ." } }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Options
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
--root <dir> repo root (default: cwd)
|
|
70
|
+
--md <file> write the Markdown report (default: stdout)
|
|
71
|
+
--json <file> write the JSON report
|
|
72
|
+
--baseline <file> previous JSON report to diff against
|
|
73
|
+
--fail-on <mode> regression | error | never
|
|
74
|
+
--ignore <path> skip a directory for the directive scan and tests-by-file (repeatable)
|
|
75
|
+
--no-deps-audit skip the dependency audit
|
|
76
|
+
--no-tests skip the test script
|
|
77
|
+
--no-typecheck skip tsc
|
|
78
|
+
--no-format skip prettier
|
|
79
|
+
--quiet, -q no progress on stderr
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Node 22 or newer. No runtime dependencies.
|
|
83
|
+
|
|
84
|
+
MIT, Copyright (c) 2026 Veracium LLC.
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type FailOn = 'regression' | 'error' | 'never';
|
|
2
|
+
export interface CliArgs {
|
|
3
|
+
command: 'audit' | 'directives' | 'help';
|
|
4
|
+
root: string;
|
|
5
|
+
md: string | null;
|
|
6
|
+
json: string | null;
|
|
7
|
+
baseline: string | null;
|
|
8
|
+
failOn: FailOn | null;
|
|
9
|
+
depsAudit: boolean;
|
|
10
|
+
runTests: boolean;
|
|
11
|
+
runTypecheck: boolean;
|
|
12
|
+
runFormat: boolean;
|
|
13
|
+
quiet: boolean;
|
|
14
|
+
ignore: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare const parseArgs: (argv: string[]) => CliArgs;
|
|
17
|
+
export declare const HELP = "legion-audit [directives] [options]\n\nRuns a read-only scorecard of the current repo against LEGION-STANDARDS.\n\nCommands\n (none) full audit: gates, rule counts, bypasses, PR hygiene, tests by file\n directives only scan for file-wide lint disables; exit 1 if any (fast, for lint scripts)\n\nOptions\n --root <dir> repo root (default: cwd)\n --md <file> write the markdown report (default: print to stdout)\n --json <file> write the JSON report\n --baseline <file> compare against a previous JSON report\n --fail-on <mode> regression | error | never\n default: regression when --baseline is given, otherwise never\n --ignore <path> skip a directory for the directive scan and tests-by-file (repeatable)\n --no-deps-audit skip the dependency audit (needs registry access)\n --no-tests skip running the test script\n --no-typecheck skip tsc\n --no-format skip prettier --check\n --quiet, -q no progress output on stderr\n";
|
|
18
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAYD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,OAoD1C,CAAC;AAEF,eAAO,MAAM,IAAI,2jCAqBhB,CAAC"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const FAIL_ON_VALUES = new Set(['regression', 'error', 'never']);
|
|
2
|
+
const takeValue = (argv, index, flag) => {
|
|
3
|
+
const value = argv[index + 1];
|
|
4
|
+
if (value === undefined || value.startsWith('--')) {
|
|
5
|
+
throw new Error(`${flag} needs a value`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
};
|
|
9
|
+
export const parseArgs = (argv) => {
|
|
10
|
+
const args = {
|
|
11
|
+
command: 'audit',
|
|
12
|
+
root: process.cwd(),
|
|
13
|
+
md: null,
|
|
14
|
+
json: null,
|
|
15
|
+
baseline: null,
|
|
16
|
+
failOn: null,
|
|
17
|
+
depsAudit: true,
|
|
18
|
+
runTests: true,
|
|
19
|
+
runTypecheck: true,
|
|
20
|
+
runFormat: true,
|
|
21
|
+
quiet: false,
|
|
22
|
+
ignore: [],
|
|
23
|
+
};
|
|
24
|
+
let index = 0;
|
|
25
|
+
while (index < argv.length) {
|
|
26
|
+
const arg = argv[index] ?? '';
|
|
27
|
+
if (arg === 'directives')
|
|
28
|
+
args.command = 'directives';
|
|
29
|
+
else if (arg === 'help' || arg === '--help' || arg === '-h')
|
|
30
|
+
args.command = 'help';
|
|
31
|
+
else if (arg === '--root') {
|
|
32
|
+
args.root = takeValue(argv, index, arg);
|
|
33
|
+
index += 1;
|
|
34
|
+
}
|
|
35
|
+
else if (arg === '--md') {
|
|
36
|
+
args.md = takeValue(argv, index, arg);
|
|
37
|
+
index += 1;
|
|
38
|
+
}
|
|
39
|
+
else if (arg === '--json') {
|
|
40
|
+
args.json = takeValue(argv, index, arg);
|
|
41
|
+
index += 1;
|
|
42
|
+
}
|
|
43
|
+
else if (arg === '--baseline') {
|
|
44
|
+
args.baseline = takeValue(argv, index, arg);
|
|
45
|
+
index += 1;
|
|
46
|
+
}
|
|
47
|
+
else if (arg === '--fail-on') {
|
|
48
|
+
const value = takeValue(argv, index, arg);
|
|
49
|
+
if (!FAIL_ON_VALUES.has(value)) {
|
|
50
|
+
throw new Error(`--fail-on must be one of regression, error, never`);
|
|
51
|
+
}
|
|
52
|
+
args.failOn = value;
|
|
53
|
+
index += 1;
|
|
54
|
+
}
|
|
55
|
+
else if (arg === '--ignore') {
|
|
56
|
+
args.ignore.push(takeValue(argv, index, arg));
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
59
|
+
else if (arg === '--no-deps-audit')
|
|
60
|
+
args.depsAudit = false;
|
|
61
|
+
else if (arg === '--no-tests')
|
|
62
|
+
args.runTests = false;
|
|
63
|
+
else if (arg === '--no-typecheck')
|
|
64
|
+
args.runTypecheck = false;
|
|
65
|
+
else if (arg === '--no-format')
|
|
66
|
+
args.runFormat = false;
|
|
67
|
+
else if (arg === '--quiet' || arg === '-q')
|
|
68
|
+
args.quiet = true;
|
|
69
|
+
else
|
|
70
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
71
|
+
index += 1;
|
|
72
|
+
}
|
|
73
|
+
return args;
|
|
74
|
+
};
|
|
75
|
+
export const HELP = `legion-audit [directives] [options]
|
|
76
|
+
|
|
77
|
+
Runs a read-only scorecard of the current repo against LEGION-STANDARDS.
|
|
78
|
+
|
|
79
|
+
Commands
|
|
80
|
+
(none) full audit: gates, rule counts, bypasses, PR hygiene, tests by file
|
|
81
|
+
directives only scan for file-wide lint disables; exit 1 if any (fast, for lint scripts)
|
|
82
|
+
|
|
83
|
+
Options
|
|
84
|
+
--root <dir> repo root (default: cwd)
|
|
85
|
+
--md <file> write the markdown report (default: print to stdout)
|
|
86
|
+
--json <file> write the JSON report
|
|
87
|
+
--baseline <file> compare against a previous JSON report
|
|
88
|
+
--fail-on <mode> regression | error | never
|
|
89
|
+
default: regression when --baseline is given, otherwise never
|
|
90
|
+
--ignore <path> skip a directory for the directive scan and tests-by-file (repeatable)
|
|
91
|
+
--no-deps-audit skip the dependency audit (needs registry access)
|
|
92
|
+
--no-tests skip running the test script
|
|
93
|
+
--no-typecheck skip tsc
|
|
94
|
+
--no-format skip prettier --check
|
|
95
|
+
--quiet, -q no progress output on stderr
|
|
96
|
+
`;
|
|
97
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AAExE,eAAO,MAAM,eAAe,EAAE,YAS7B,CAAC;AASF,eAAO,MAAM,QAAQ,GAAI,UAAS,OAAO,CAAC,YAAY,CAAM,KAAG,WA4D9D,CAAC"}
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { scanDirectives } from './checks/directives.js';
|
|
3
|
+
import { depsAuditGate, formatGate, testsGate, typecheckGate, } from './checks/gates.js';
|
|
4
|
+
import { prHygiene } from './checks/git.js';
|
|
5
|
+
import { runLint } from './checks/lint.js';
|
|
6
|
+
import { lockfileCheck } from './checks/lockfile.js';
|
|
7
|
+
import { testCoverage } from './checks/test-coverage.js';
|
|
8
|
+
import { computeScore } from './report/score.js';
|
|
9
|
+
export const DEFAULT_OPTIONS = {
|
|
10
|
+
root: process.cwd(),
|
|
11
|
+
depsAudit: true,
|
|
12
|
+
runTests: true,
|
|
13
|
+
runTypecheck: true,
|
|
14
|
+
runFormat: true,
|
|
15
|
+
quiet: false,
|
|
16
|
+
commitsToInspect: 80,
|
|
17
|
+
ignore: [],
|
|
18
|
+
};
|
|
19
|
+
const loggerFor = (options) => {
|
|
20
|
+
if (options.quiet)
|
|
21
|
+
return () => undefined;
|
|
22
|
+
return (message) => console.error(message);
|
|
23
|
+
};
|
|
24
|
+
export const runAudit = (partial = {}) => {
|
|
25
|
+
const options = { ...DEFAULT_OPTIONS, ...partial };
|
|
26
|
+
const root = resolve(options.root);
|
|
27
|
+
const log = loggerFor(options);
|
|
28
|
+
log('legion-audit: lockfile');
|
|
29
|
+
const lockfile = lockfileCheck(root);
|
|
30
|
+
const { packageManager } = lockfile;
|
|
31
|
+
const gates = [];
|
|
32
|
+
if (options.runTypecheck) {
|
|
33
|
+
log('legion-audit: typecheck');
|
|
34
|
+
gates.push(typecheckGate(root));
|
|
35
|
+
}
|
|
36
|
+
log('legion-audit: lint');
|
|
37
|
+
const lint = runLint(root);
|
|
38
|
+
const lintGate = {
|
|
39
|
+
name: 'lint',
|
|
40
|
+
status: lint.status,
|
|
41
|
+
summary: `${lint.linter}: ${lint.errors} error(s), ${lint.warnings} warning(s)`,
|
|
42
|
+
};
|
|
43
|
+
if (lint.command !== undefined)
|
|
44
|
+
lintGate.command = lint.command;
|
|
45
|
+
gates.push(lintGate);
|
|
46
|
+
if (options.runTests) {
|
|
47
|
+
log('legion-audit: tests');
|
|
48
|
+
gates.push(testsGate(root, packageManager));
|
|
49
|
+
}
|
|
50
|
+
if (options.runFormat) {
|
|
51
|
+
log('legion-audit: format');
|
|
52
|
+
gates.push(formatGate(root));
|
|
53
|
+
}
|
|
54
|
+
if (options.depsAudit) {
|
|
55
|
+
log('legion-audit: dependency audit');
|
|
56
|
+
gates.push(depsAuditGate(root, packageManager));
|
|
57
|
+
}
|
|
58
|
+
log('legion-audit: directives');
|
|
59
|
+
const directives = scanDirectives(root, options.ignore);
|
|
60
|
+
log('legion-audit: git history');
|
|
61
|
+
const hygiene = prHygiene(root, options.commitsToInspect);
|
|
62
|
+
log('legion-audit: tests by file');
|
|
63
|
+
const coverage = testCoverage(root, options.ignore);
|
|
64
|
+
const partialResult = {
|
|
65
|
+
root,
|
|
66
|
+
packageManager,
|
|
67
|
+
gates,
|
|
68
|
+
lint,
|
|
69
|
+
directives,
|
|
70
|
+
prHygiene: hygiene,
|
|
71
|
+
testCoverage: coverage,
|
|
72
|
+
lockfile,
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
schema: 1,
|
|
76
|
+
generatedAt: new Date().toISOString(),
|
|
77
|
+
score: computeScore(partialResult),
|
|
78
|
+
...partialResult,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DirectiveHit, DirectivesResult, NosonarHit } from '../types.js';
|
|
2
|
+
export declare const parseDirectivesInSource: (file: string, source: string) => {
|
|
3
|
+
hits: DirectiveHit[];
|
|
4
|
+
nosonar: NosonarHit[];
|
|
5
|
+
};
|
|
6
|
+
export declare const isFileWideDisable: (hit: DirectiveHit) => boolean;
|
|
7
|
+
export declare const scanDirectives: (root: string, ignore?: string[]) => DirectivesResult;
|
|
8
|
+
//# sourceMappingURL=directives.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives.d.ts","sourceRoot":"","sources":["../../src/checks/directives.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+C9E,eAAO,MAAM,uBAAuB,GAClC,MAAM,MAAM,EACZ,QAAQ,MAAM,KACb;IAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,UAAU,EAAE,CAAA;CAuB/C,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,KAAK,YAAY,KAAG,OACoB,CAAC;AAS3E,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,SAAQ,MAAM,EAAO,KACpB,gBA0BF,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { listSourceFiles, toPosix } from '../files.js';
|
|
3
|
+
const DIRECTIVE_PATTERN = /(?:\/\/|\/\*)\s*(eslint|oxlint)-(disable|enable)(-next-line|-line)?\b([^\n]*?)(?:\*\/|$)/g;
|
|
4
|
+
const NOSONAR_PATTERN = /\bNOSONAR\b/;
|
|
5
|
+
const scopeFromSuffix = (suffix) => {
|
|
6
|
+
if (suffix === '-next-line')
|
|
7
|
+
return 'next-line';
|
|
8
|
+
if (suffix === '-line')
|
|
9
|
+
return 'line';
|
|
10
|
+
return 'file';
|
|
11
|
+
};
|
|
12
|
+
const splitRest = (rest) => {
|
|
13
|
+
const text = rest.trim();
|
|
14
|
+
const separator = text.indexOf('--');
|
|
15
|
+
let rulesText = text;
|
|
16
|
+
let description = '';
|
|
17
|
+
if (separator !== -1) {
|
|
18
|
+
rulesText = text.slice(0, separator);
|
|
19
|
+
description = text.slice(separator + 2).trim();
|
|
20
|
+
}
|
|
21
|
+
const rules = rulesText
|
|
22
|
+
.split(',')
|
|
23
|
+
.map((rule) => rule.trim())
|
|
24
|
+
.filter((rule) => rule.length > 0);
|
|
25
|
+
return { rules, description };
|
|
26
|
+
};
|
|
27
|
+
const QUOTES = new Set(["'", '"', '`']);
|
|
28
|
+
const isInsideString = (text, index) => {
|
|
29
|
+
let open = null;
|
|
30
|
+
let position = 0;
|
|
31
|
+
while (position < index) {
|
|
32
|
+
const char = text[position] ?? '';
|
|
33
|
+
if (char === '\\') {
|
|
34
|
+
position += 2;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (open === null && QUOTES.has(char))
|
|
38
|
+
open = char;
|
|
39
|
+
else if (open === char)
|
|
40
|
+
open = null;
|
|
41
|
+
position += 1;
|
|
42
|
+
}
|
|
43
|
+
return open !== null;
|
|
44
|
+
};
|
|
45
|
+
export const parseDirectivesInSource = (file, source) => {
|
|
46
|
+
const hits = [];
|
|
47
|
+
const nosonar = [];
|
|
48
|
+
const lines = source.split('\n');
|
|
49
|
+
lines.forEach((text, index) => {
|
|
50
|
+
const line = index + 1;
|
|
51
|
+
if (NOSONAR_PATTERN.test(text))
|
|
52
|
+
nosonar.push({ file, line });
|
|
53
|
+
for (const match of text.matchAll(DIRECTIVE_PATTERN)) {
|
|
54
|
+
if (isInsideString(text, match.index))
|
|
55
|
+
continue;
|
|
56
|
+
const [, tool, kind, suffix, rest] = match;
|
|
57
|
+
const { rules, description } = splitRest(rest ?? '');
|
|
58
|
+
hits.push({
|
|
59
|
+
file,
|
|
60
|
+
line,
|
|
61
|
+
tool: tool,
|
|
62
|
+
kind: kind,
|
|
63
|
+
scope: scopeFromSuffix(suffix),
|
|
64
|
+
rules,
|
|
65
|
+
description,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return { hits, nosonar };
|
|
70
|
+
};
|
|
71
|
+
export const isFileWideDisable = (hit) => hit.kind === 'disable' && hit.scope === 'file' && hit.rules.length === 0;
|
|
72
|
+
const ALL_RULES = '(all rules)';
|
|
73
|
+
const namesFor = (hit) => {
|
|
74
|
+
if (hit.rules.length === 0)
|
|
75
|
+
return [ALL_RULES];
|
|
76
|
+
return hit.rules;
|
|
77
|
+
};
|
|
78
|
+
export const scanDirectives = (root, ignore = []) => {
|
|
79
|
+
const all = [];
|
|
80
|
+
const nosonar = [];
|
|
81
|
+
for (const file of listSourceFiles(root, ignore)) {
|
|
82
|
+
let source = '';
|
|
83
|
+
try {
|
|
84
|
+
source = readFileSync(file, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const relativeFile = toPosix(root, file);
|
|
90
|
+
const parsed = parseDirectivesInSource(relativeFile, source);
|
|
91
|
+
all.push(...parsed.hits);
|
|
92
|
+
nosonar.push(...parsed.nosonar);
|
|
93
|
+
}
|
|
94
|
+
const byRule = {};
|
|
95
|
+
for (const hit of all) {
|
|
96
|
+
if (hit.kind !== 'disable')
|
|
97
|
+
continue;
|
|
98
|
+
for (const name of namesFor(hit))
|
|
99
|
+
byRule[name] = (byRule[name] ?? 0) + 1;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
fileWide: all.filter(isFileWideDisable),
|
|
103
|
+
all,
|
|
104
|
+
nosonar,
|
|
105
|
+
byRule,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives.js","sourceRoot":"","sources":["../../src/checks/directives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GateResult, PackageManager } from '../types.js';
|
|
2
|
+
interface PackageJson {
|
|
3
|
+
scripts?: Record<string, string>;
|
|
4
|
+
workspaces?: string[] | {
|
|
5
|
+
packages?: string[];
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare const readPackageJson: (root: string) => PackageJson;
|
|
9
|
+
export declare const countTypeErrors: (output: string) => number;
|
|
10
|
+
export declare const typecheckGate: (root: string) => GateResult;
|
|
11
|
+
export declare const summarizeTestOutput: (output: string) => string;
|
|
12
|
+
export declare const testsGate: (root: string, manager: PackageManager) => GateResult;
|
|
13
|
+
export declare const formatGate: (root: string) => GateResult;
|
|
14
|
+
export declare const depsAuditGate: (root: string, manager: PackageManager) => GateResult;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=gates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/checks/gates.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9D,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjD;AAED,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,WAQ9C,CAAC;AASF,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MACF,CAAC;AAEhD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,UAoB5C,CAAC;AASF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,KAAG,MAkBpD,CAAC;AAEF,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,SAAS,cAAc,KACtB,UAiBF,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,UAmBzC,CAAC;AA2CF,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,SAAS,cAAc,KACtB,UAUF,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { localBin, packageManagerRun, run } from '../exec.js';
|
|
4
|
+
export const readPackageJson = (root) => {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const statusFromCode = (code) => {
|
|
13
|
+
if (code === 0)
|
|
14
|
+
return 'pass';
|
|
15
|
+
return 'fail';
|
|
16
|
+
};
|
|
17
|
+
const TS_ERROR_PATTERN = /error TS\d+/g;
|
|
18
|
+
export const countTypeErrors = (output) => (output.match(TS_ERROR_PATTERN) ?? []).length;
|
|
19
|
+
export const typecheckGate = (root) => {
|
|
20
|
+
const bin = localBin(root, 'tsc');
|
|
21
|
+
if (!bin || !existsSync(join(root, 'tsconfig.json'))) {
|
|
22
|
+
return {
|
|
23
|
+
name: 'typecheck',
|
|
24
|
+
status: 'skipped',
|
|
25
|
+
summary: 'no tsconfig.json or no local tsc',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const result = run(bin, ['--noEmit', '-p', 'tsconfig.json'], root);
|
|
29
|
+
const errors = countTypeErrors(result.stdout + result.stderr);
|
|
30
|
+
let summary = 'clean';
|
|
31
|
+
if (result.code !== 0)
|
|
32
|
+
summary = `${errors} error(s)`;
|
|
33
|
+
return {
|
|
34
|
+
name: 'typecheck',
|
|
35
|
+
status: statusFromCode(result.code),
|
|
36
|
+
summary,
|
|
37
|
+
command: result.command,
|
|
38
|
+
exitCode: result.code,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const JEST_TESTS_PATTERN = /Tests:\s+(?:(\d+) failed, )?(?:(\d+) skipped, )?(?:(\d+) passed, )?(\d+) total/;
|
|
42
|
+
const JEST_SUITES_PATTERN = /Test Suites:\s+(?:(\d+) failed, )?(?:(\d+) skipped, )?(?:(\d+) passed, )?(\d+) total/;
|
|
43
|
+
const VITEST_PATTERN = /Tests\s+(?:(\d+) failed \| )?(\d+) passed(?: \| (\d+) skipped)?\s*\((\d+)\)/;
|
|
44
|
+
export const summarizeTestOutput = (output) => {
|
|
45
|
+
const jest = JEST_TESTS_PATTERN.exec(output);
|
|
46
|
+
if (jest) {
|
|
47
|
+
const [, failed, , passed, total] = jest;
|
|
48
|
+
const suites = JEST_SUITES_PATTERN.exec(output);
|
|
49
|
+
let suiteText = '';
|
|
50
|
+
if (suites)
|
|
51
|
+
suiteText = `, ${suites[4]} suite(s)`;
|
|
52
|
+
if (failed)
|
|
53
|
+
return `${failed} failed, ${passed ?? 0}/${total} passed${suiteText}`;
|
|
54
|
+
return `${passed ?? total}/${total} passed${suiteText}`;
|
|
55
|
+
}
|
|
56
|
+
const vitest = VITEST_PATTERN.exec(output);
|
|
57
|
+
if (vitest) {
|
|
58
|
+
const [, failed, passed, , total] = vitest;
|
|
59
|
+
if (failed)
|
|
60
|
+
return `${failed} failed, ${passed}/${total} passed`;
|
|
61
|
+
return `${passed}/${total} passed`;
|
|
62
|
+
}
|
|
63
|
+
return 'no summary parsed, exit code only';
|
|
64
|
+
};
|
|
65
|
+
export const testsGate = (root, manager) => {
|
|
66
|
+
const scripts = readPackageJson(root).scripts ?? {};
|
|
67
|
+
let script = null;
|
|
68
|
+
if (scripts['test:ci'])
|
|
69
|
+
script = 'test:ci';
|
|
70
|
+
else if (scripts['test'])
|
|
71
|
+
script = 'test';
|
|
72
|
+
if (!script) {
|
|
73
|
+
return { name: 'tests', status: 'skipped', summary: 'no test script' };
|
|
74
|
+
}
|
|
75
|
+
const { file, args } = packageManagerRun(manager, script);
|
|
76
|
+
const result = run(file, args, root);
|
|
77
|
+
return {
|
|
78
|
+
name: 'tests',
|
|
79
|
+
status: statusFromCode(result.code),
|
|
80
|
+
summary: summarizeTestOutput(result.stdout + result.stderr),
|
|
81
|
+
command: result.command,
|
|
82
|
+
exitCode: result.code,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export const formatGate = (root) => {
|
|
86
|
+
const bin = localBin(root, 'prettier');
|
|
87
|
+
if (!bin) {
|
|
88
|
+
return { name: 'format', status: 'skipped', summary: 'no local prettier' };
|
|
89
|
+
}
|
|
90
|
+
const result = run(bin, ['--check', '--no-color', '.'], root);
|
|
91
|
+
const unformatted = (result.stdout + result.stderr)
|
|
92
|
+
.split('\n')
|
|
93
|
+
.filter((line) => line.startsWith('[warn]') && !line.includes('Code style'))
|
|
94
|
+
.filter((line) => !line.includes('Run Prettier')).length;
|
|
95
|
+
let summary = 'clean';
|
|
96
|
+
if (result.code !== 0)
|
|
97
|
+
summary = `${unformatted} file(s) need formatting`;
|
|
98
|
+
return {
|
|
99
|
+
name: 'format',
|
|
100
|
+
status: statusFromCode(result.code),
|
|
101
|
+
summary,
|
|
102
|
+
command: result.command,
|
|
103
|
+
exitCode: result.code,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
const NETWORK_FAILURE = /ENOTFOUND|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|network|offline/i;
|
|
107
|
+
const depsAuditCommand = (manager) => {
|
|
108
|
+
if (manager === 'yarn') {
|
|
109
|
+
return {
|
|
110
|
+
file: 'yarn',
|
|
111
|
+
args: ['npm', 'audit', '--all', '--severity', 'high'],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (manager === 'npm')
|
|
115
|
+
return { file: 'npm', args: ['audit', '--audit-level=high'] };
|
|
116
|
+
if (manager === 'pnpm')
|
|
117
|
+
return { file: 'pnpm', args: ['audit', '--audit-level=high'] };
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
const depsAuditSummary = (result) => {
|
|
121
|
+
const output = result.stdout + result.stderr;
|
|
122
|
+
if (result.code !== 0 && NETWORK_FAILURE.test(output)) {
|
|
123
|
+
return {
|
|
124
|
+
name: 'dependency audit',
|
|
125
|
+
status: 'skipped',
|
|
126
|
+
summary: 'registry unreachable',
|
|
127
|
+
command: result.command,
|
|
128
|
+
exitCode: result.code,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
let summary = 'no high or critical advisories';
|
|
132
|
+
if (result.code !== 0)
|
|
133
|
+
summary = 'advisories at high severity or above';
|
|
134
|
+
return {
|
|
135
|
+
name: 'dependency audit',
|
|
136
|
+
status: statusFromCode(result.code),
|
|
137
|
+
summary,
|
|
138
|
+
command: result.command,
|
|
139
|
+
exitCode: result.code,
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
export const depsAuditGate = (root, manager) => {
|
|
143
|
+
const command = depsAuditCommand(manager);
|
|
144
|
+
if (!command) {
|
|
145
|
+
return {
|
|
146
|
+
name: 'dependency audit',
|
|
147
|
+
status: 'skipped',
|
|
148
|
+
summary: `no audit command for ${manager}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return depsAuditSummary(run(command.file, command.args, root));
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gates.js","sourceRoot":"","sources":["../../src/checks/gates.ts"],"names":[],"mappings":"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"}
|