ng-checkup 0.12.0 → 1.0.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 +55 -10
- package/dist/cache.d.ts +1 -0
- package/dist/cache.js.map +1 -1
- package/dist/checks/architecture.js +31 -0
- package/dist/checks/architecture.js.map +1 -1
- package/dist/checks/context.d.ts +6 -0
- package/dist/checks/context.js.map +1 -1
- package/dist/checks/security.js +43 -5
- package/dist/checks/security.js.map +1 -1
- package/dist/churn.d.ts +25 -0
- package/dist/churn.js +44 -0
- package/dist/churn.js.map +1 -0
- package/dist/cli.d.ts +21 -1
- package/dist/cli.js +120 -10
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +9 -1
- package/dist/config.js.map +1 -1
- package/dist/history.d.ts +66 -0
- package/dist/history.js +103 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +49 -8
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +3 -3
- package/dist/init.js +3 -3
- package/dist/report/history.d.ts +17 -0
- package/dist/report/history.js +138 -0
- package/dist/report/history.js.map +1 -0
- package/dist/report/html.d.ts +5 -0
- package/dist/report/html.js +1 -1
- package/dist/report/html.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/vulnerabilities.d.ts +54 -0
- package/dist/vulnerabilities.js +116 -0
- package/dist/vulnerabilities.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ npx ng-checkup
|
|
|
23
23
|
- [Configuration](#configuration)
|
|
24
24
|
- [Baseline: adopt it on legacy code](#baseline-adopt-it-on-legacy-code)
|
|
25
25
|
- [Watch mode](#watch-mode)
|
|
26
|
+
- [History](#history)
|
|
26
27
|
- [Upgrade plan](#upgrade-plan)
|
|
27
28
|
- [AngularJS and hybrid apps](#angularjs-and-hybrid-apps)
|
|
28
29
|
- [Primo VE to NDE](#primo-ve-to-nde)
|
|
@@ -117,6 +118,8 @@ npx ng-checkup init --ci gitlab # pick the CI instead of detecting it (github
|
|
|
117
118
|
| `ignore` | paths left out of the analysis; a path matches everything below it, `*` stays within a folder, `**` crosses folders | |
|
|
118
119
|
| `baseline` | the baseline file, relative to the config file (default `ng-checkup.baseline.json` in the root) | |
|
|
119
120
|
| `plan` | add the upgrade plan to every run | `--plan` |
|
|
121
|
+
| `only` | report only these areas, by [check id](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md) (`["security", "dependencies"]`) | `--only` |
|
|
122
|
+
| `skip` | leave these areas out of the report, the score and the baseline | `--skip` |
|
|
120
123
|
|
|
121
124
|
An unknown key or a wrong type stops the run with an error, so a typo cannot silently switch off a CI threshold. Use `--config path/to/file.json` for a file elsewhere.
|
|
122
125
|
|
|
@@ -158,6 +161,35 @@ Score 72 -> 76 (+4 since the watch started)
|
|
|
158
161
|
|
|
159
162
|
Only the changed files are parsed again, and the registry is asked again only when `package.json` or a lockfile changes, so a run after a save takes a fraction of the first one. Keep it open next to the editor while working through a refactor; run `npx ng-checkup baseline` in another terminal to lock a drop in, and the next run compares against it.
|
|
160
163
|
|
|
164
|
+
## History
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npx ng-checkup history --report history.html
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
ng-checkup 1.0.0 history of angular-realworld-example-app, 19 points (shortened)
|
|
172
|
+
|
|
173
|
+
date commit Angular score lines
|
|
174
|
+
2023-10-30 0d28f5c 16.2.11 60 1 407
|
|
175
|
+
2023-12-19 bcc6d42 17.0.7 64 1 360
|
|
176
|
+
2024-05-24 932dff0 18.0.0 68 1 441
|
|
177
|
+
2025-05-29 a420aa2 20.0.0 72 1 401
|
|
178
|
+
2025-11-10 987b634 20.3.9 76 1 356
|
|
179
|
+
2026-01-30 6809ae9 21.1.1 91 1 471
|
|
180
|
+
2026-10-23 now 21.2.12 91 1 709
|
|
181
|
+
|
|
182
|
+
Moved most since the first point
|
|
183
|
+
-27 *ngIf / *ngFor / [ngSwitch] uses (27 -> 0)
|
|
184
|
+
-16 Components with eager change detection (16 -> 0)
|
|
185
|
+
-9 Components and services without a spec (27 -> 18)
|
|
186
|
+
-7 Explicit any in application code (7 -> 0)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
How the score and every measure moved over the last months, without having to collect it as you go. It takes the last commit of each month from git (`--points 12` months by default; a month without a commit is skipped), extracts it into a temporary folder with `git archive`, runs the checkup offline on it, and ends with the working tree as it is, uncommitted changes included. Your checkout is never touched. The HTML report has a chart of the score and one per measure that moved most; `.md` and `.json` work too.
|
|
190
|
+
|
|
191
|
+
It is the slide for "what did the refactoring buy us", and the first thing to look at on a codebase you inherit: whether the debt is shrinking or growing. A shallow clone has no history, so in CI fetch it in full (`fetch-depth: 0`).
|
|
192
|
+
|
|
161
193
|
## Upgrade plan
|
|
162
194
|
|
|
163
195
|
```bash
|
|
@@ -294,14 +326,14 @@ The codebase is named by its repository and commit, never by a path on your disk
|
|
|
294
326
|
| [**Change detection**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#change-detection) | zoneless or zone.js, OnPush coverage, `NgZone`, zone-only test helpers | zone.js with less than half the components on OnPush |
|
|
295
327
|
| [**Components & templates**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#components) | standalone, signal inputs and outputs, built-in control flow, `@defer` | average adoption below 40% |
|
|
296
328
|
| [**State management**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#state) | classic NgRx, NgRx signal store, signals, `BehaviorSubject` services | the removed `@Effect()` decorator |
|
|
297
|
-
| [**Architecture**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#architecture) | eager vs. lazy routes (following `loadChildren` into lazy modules), files over 500 and 1 000 lines, constructor injection, class-based guards and interceptors | ten eager routes and none lazy, or three files over 1 000 lines |
|
|
329
|
+
| [**Architecture**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#architecture) | eager vs. lazy routes (following `loadChildren` into lazy modules), files over 500 and 1 000 lines, hotspots (large files changed most often, from git), constructor injection, class-based guards and interceptors | ten eager routes and none lazy, or three files over 1 000 lines |
|
|
298
330
|
| [**Testing**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#testing) | Karma / Jest / Vitest, specs next to components and services, Protractor / Playwright / Cypress | Karma, Protractor or no specs |
|
|
299
331
|
| [**Type safety**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#type-safety) | `strictNullChecks`, `strictTemplates`, `noImplicitAny`, `any`, `x!`, `@ts-ignore`, `$any()` | both `strictNullChecks` and `strictTemplates` off |
|
|
300
|
-
| [**Security**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#security) | `bypassSecurityTrust*`, HTML written straight into the DOM (`innerHTML =`, `insertAdjacentHTML`, `Renderer2.setProperty`), `eval`, `[innerHTML]` bindings | HTML written into the DOM past the sanitizer, or `eval` |
|
|
332
|
+
| [**Security**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#security) | known vulnerabilities of the dependencies (from [osv.dev](https://osv.dev), AngularJS included), `bypassSecurityTrust*`, HTML written straight into the DOM (`innerHTML =`, `insertAdjacentHTML`, `Renderer2.setProperty`), `eval`, `[innerHTML]` bindings | a high or critical advisory in a runtime dependency, HTML written into the DOM past the sanitizer, or `eval` |
|
|
301
333
|
| [**Circular dependencies**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#circular-deps) | import cycles through relative paths, barrels and tsconfig `paths` | more than three groups or ten files in loops |
|
|
302
334
|
| [**Bundle size**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#bundle) | the initial bundle from the last `ng build --stats-json` (esbuild or webpack) against the build's budget, its largest packages, lazy chunks | over the `initial` error budget (1 MB by default) |
|
|
303
335
|
|
|
304
|
-
Each finding says why it matters and which `ng generate` migration fixes it. **[Every check, its grading and its fix →](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md)**
|
|
336
|
+
`--only security,dependencies` reports just those areas, and `--skip bundle` leaves one out; the score is then the average of what is left. Each finding says why it matters and which `ng generate` migration fixes it. **[Every check, its grading and its fix →](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md)**
|
|
305
337
|
|
|
306
338
|
Defaults that changed between versions are applied per version: a component without `standalone` is standalone from Angular 19, one without `changeDetection` is OnPush from 22, and an app without a zone provider is zoneless from 21.
|
|
307
339
|
|
|
@@ -331,6 +363,7 @@ Usage: ng-checkup [path | repository] [options]
|
|
|
331
363
|
ng-checkup init [path] [init options]
|
|
332
364
|
ng-checkup baseline [path] [--force]
|
|
333
365
|
ng-checkup url <address> [--json] [--offline]
|
|
366
|
+
ng-checkup history [path] [--points <n>] [--report <file>] [--json]
|
|
334
367
|
|
|
335
368
|
Arguments:
|
|
336
369
|
path Repository root (default: current directory)
|
|
@@ -365,8 +398,11 @@ Options:
|
|
|
365
398
|
--fail-on <grade> Exit with code 1 if any area is graded <grade> or worse
|
|
366
399
|
(poor or fair), or with regression if a measure is
|
|
367
400
|
worse than the baseline (for CI)
|
|
401
|
+
--only <ids> Report only these areas, comma-separated check ids
|
|
402
|
+
(security,testing); the rest stay out of the score
|
|
403
|
+
--skip <ids> Leave these areas out of the report and the score
|
|
368
404
|
--max-examples <n> Example files listed per finding (default 5)
|
|
369
|
-
--offline
|
|
405
|
+
--offline Nothing online: no npm registry, no osv.dev lookup
|
|
370
406
|
--config <file> Config file (default: ng-checkup.config.json in the root)
|
|
371
407
|
--no-color Plain output (also off when NO_COLOR is set or not a TTY)
|
|
372
408
|
-h, --help Show this help
|
|
@@ -386,6 +422,16 @@ Url (ng-checkup url <address>):
|
|
|
386
422
|
versions, zone.js, React, Vue, Next.js, Nuxt. Reads
|
|
387
423
|
the page and up to 12 scripts of the same site
|
|
388
424
|
--json Print the result as JSON
|
|
425
|
+
|
|
426
|
+
History (ng-checkup history [path]):
|
|
427
|
+
How the score and the measures moved over the last
|
|
428
|
+
months: checks out one commit per month into a
|
|
429
|
+
temporary folder (the working tree is never touched),
|
|
430
|
+
runs the checkup offline on each, and ends with now.
|
|
431
|
+
Needs the full git history, not a shallow clone
|
|
432
|
+
--points <n> Months back (default 12)
|
|
433
|
+
-r, --report <file> Write it as .html (with charts), .md or .json
|
|
434
|
+
--json Print it as JSON
|
|
389
435
|
```
|
|
390
436
|
|
|
391
437
|
Exit codes: `0` done, `1` `--fail-on` tripped, `2` usage error (including `--fail-on regression` without a baseline). Areas graded `info` never fail a run.
|
|
@@ -412,7 +458,7 @@ jobs:
|
|
|
412
458
|
runs-on: ubuntu-latest
|
|
413
459
|
steps:
|
|
414
460
|
- uses: actions/checkout@v4
|
|
415
|
-
- uses: stefankuburovic/ng-checkup@
|
|
461
|
+
- uses: stefankuburovic/ng-checkup@v1
|
|
416
462
|
with:
|
|
417
463
|
fail-on: regression # needs a committed baseline: npx ng-checkup baseline
|
|
418
464
|
sarif: true
|
|
@@ -441,7 +487,7 @@ No `npm ci` needed: versions come from the lockfile. Inputs: `path` (a folder in
|
|
|
441
487
|
|
|
442
488
|
```yaml
|
|
443
489
|
include:
|
|
444
|
-
- component: $CI_SERVER_FQDN/stefankuburovic/ng-checkup/ng-checkup@
|
|
490
|
+
- component: $CI_SERVER_FQDN/stefankuburovic/ng-checkup/ng-checkup@1
|
|
445
491
|
inputs:
|
|
446
492
|
fail-on: regression
|
|
447
493
|
```
|
|
@@ -450,7 +496,7 @@ On a self-managed GitLab, which cannot include components from gitlab.com, use t
|
|
|
450
496
|
|
|
451
497
|
```yaml
|
|
452
498
|
include:
|
|
453
|
-
- remote: https://raw.githubusercontent.com/stefankuburovic/ng-checkup/
|
|
499
|
+
- remote: https://raw.githubusercontent.com/stefankuburovic/ng-checkup/v1/gitlab/ng-checkup.yml
|
|
454
500
|
|
|
455
501
|
ng-checkup:
|
|
456
502
|
variables:
|
|
@@ -498,11 +544,11 @@ for (const check of report.checks) {
|
|
|
498
544
|
await writeFile('checkup.md', renderMarkdown(report));
|
|
499
545
|
```
|
|
500
546
|
|
|
501
|
-
`runCheckup` never writes anything. Its options mirror the flags (`offline`, `plan`, `project`, `ignore`, `maxExamples`), plus `cache` from `createAnalysisCache()` to make repeated runs incremental. `renderHtml`, `renderMarkdown`, `renderSarif`, `overallScore`, `topPriorities`, `loadConfig`, `planInit` (what `init` would change, without writing it) and the baseline functions (`createBaseline`, `compareWithBaseline`, `readBaseline`, `serializeBaseline`) are exported too, along with the `Report`, `CheckResult`, `Finding` and `Grade` types. `Report` is also the schema of the JSON output.
|
|
547
|
+
`runCheckup` never writes anything. Its options mirror the flags (`offline`, `plan`, `project`, `ignore`, `only`, `skip`, `maxExamples`), plus `cache` from `createAnalysisCache()` to make repeated runs incremental. `renderHtml`, `renderMarkdown`, `renderSarif`, `overallScore`, `topPriorities`, `loadConfig`, `planInit` (what `init` would change, without writing it), `collectHistory` with `renderHistoryHtml` and `renderHistoryMarkdown`, and the baseline functions (`createBaseline`, `compareWithBaseline`, `readBaseline`, `serializeBaseline`) are exported too, along with the `Report`, `CheckResult`, `Finding` and `Grade` types. `Report` is also the schema of the JSON output.
|
|
502
548
|
|
|
503
549
|
## What leaves your machine
|
|
504
550
|
|
|
505
|
-
Package names and versions,
|
|
551
|
+
Package names and versions, to two public services. To the npm registry: the latest `@angular/core` version (three-second timeout); for every third-party package that depends on Angular, its public metadata, to find the release that supports the next Angular and whether it is deprecated (eight at a time, four-second timeout each); and, when `node_modules` is missing, the published tarballs of the `@angular/*`, `@ngrx/*` and `rxjs` versions your code imports, of which only `package.json` and `.d.ts` files are kept, in `~/.cache/ng-checkup` (`NG_CHECKUP_CACHE` moves it). To [osv.dev](https://osv.dev): the direct dependencies with their exact versions, to look up known vulnerabilities (eight-second timeout). `--offline` skips all of it and uses only what is already cached; the report then compares against the newest major this release knows and says so. A remote repository argument also clones it from its host. No source, no file names, no telemetry. The tool reads files and writes only the reports you ask for.
|
|
506
552
|
|
|
507
553
|
## How it works
|
|
508
554
|
|
|
@@ -532,7 +578,6 @@ Package names and versions, and only to the public npm registry: the latest `@an
|
|
|
532
578
|
## Roadmap
|
|
533
579
|
|
|
534
580
|
- Per-project baselines for monorepos
|
|
535
|
-
- Change frequency from git history, to rank large files by how often they change
|
|
536
581
|
- A VS Code extension showing findings inline
|
|
537
582
|
|
|
538
583
|
Ideas and bug reports are welcome in [issues](https://github.com/stefankuburovic/ng-checkup/issues).
|
package/dist/cache.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface AnalysisCache {
|
|
|
25
25
|
deprecations: DeprecationIndex;
|
|
26
26
|
typings: import('./typings.js').TypingsSource;
|
|
27
27
|
compatibility: PackageCompatibility[];
|
|
28
|
+
vulnerabilities?: import('./vulnerabilities.js').VulnerabilityScan;
|
|
28
29
|
latestAngular: {
|
|
29
30
|
major: number;
|
|
30
31
|
source: 'registry' | 'bundled';
|
package/dist/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAkCjC,sBAAsB;AACtB,MAAM,UAAU,mBAAmB;IACjC,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;AAC9B,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,CAAC,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,gBAAgB,EAAE,iCAAiC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtK,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAwB,KAAgC,EAAE,IAAY,EAAE,OAAgB,EAAE,GAAG,GAAG,IAAI;IAC7H,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;QAAE,OAAO,GAAG,CAAC,KAAU,CAAC;IACtD,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC;IACxB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -3,6 +3,14 @@ import { applicationFiles, formatCount, percent, plural } from './context.js';
|
|
|
3
3
|
const LARGE_FILE = 500;
|
|
4
4
|
/** Lines of code from which a file counts against the grade. */
|
|
5
5
|
const VERY_LARGE_FILE = 1000;
|
|
6
|
+
/**
|
|
7
|
+
* A hotspot is at least this large… The thresholds keep the list to files
|
|
8
|
+
* that matter on a large app (on a 1 600-file app, 200 lines and 5 commits
|
|
9
|
+
* named 147 files, which is a list nobody acts on).
|
|
10
|
+
*/
|
|
11
|
+
const HOTSPOT_LINES = 300;
|
|
12
|
+
/** …and was changed in at least this many commits of the last year. */
|
|
13
|
+
const HOTSPOT_COMMITS = 10;
|
|
6
14
|
/**
|
|
7
15
|
* How the code is put together: lazy loading, oversized files, and the
|
|
8
16
|
* dependency-injection and guard style.
|
|
@@ -57,6 +65,26 @@ export function architectureCheck(context) {
|
|
|
57
65
|
count: large.length,
|
|
58
66
|
});
|
|
59
67
|
}
|
|
68
|
+
// Hotspots: large files that also change often, ranked by size times commits.
|
|
69
|
+
const churn = typeof context.churn === 'object' ? context.churn : undefined;
|
|
70
|
+
const hotspots = churn
|
|
71
|
+
? files
|
|
72
|
+
.map((file) => ({ file, commits: churn.files.get(file.path) ?? 0 }))
|
|
73
|
+
.filter(({ file, commits }) => file.linesOfCode >= HOTSPOT_LINES && commits >= HOTSPOT_COMMITS)
|
|
74
|
+
.sort((left, right) => right.file.linesOfCode * right.commits - left.file.linesOfCode * left.commits)
|
|
75
|
+
: [];
|
|
76
|
+
if (hotspots.length > 0) {
|
|
77
|
+
findings.push({
|
|
78
|
+
severity: hotspots.some(({ file }) => file.linesOfCode >= LARGE_FILE) ? 'medium' : 'low',
|
|
79
|
+
message: `Hotspots: ${hotspots.length > 5 ? 'the ' : ''}large files changed most often${hotspots.length > 5 ? ` (top 5 of ${formatCount(hotspots.length)})` : ''}`,
|
|
80
|
+
detail: `${hotspots
|
|
81
|
+
.slice(0, 5)
|
|
82
|
+
.map(({ file, commits }) => `${file.path.split('/').pop()} (${formatCount(file.linesOfCode)} lines, ${plural(commits, 'commit')})`)
|
|
83
|
+
.join(', ')}, over the last year of history. A file that is both large and edited all the time costs its size on every change; these are where splitting or a refactor pays back first, more than the largest file nobody touches.`,
|
|
84
|
+
files: hotspots.slice(0, max).map(({ file }) => ({ path: file.path })),
|
|
85
|
+
count: hotspots.length,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
60
88
|
if (classGuards.length > 0) {
|
|
61
89
|
findings.push({
|
|
62
90
|
severity: 'low',
|
|
@@ -95,6 +123,9 @@ export function architectureCheck(context) {
|
|
|
95
123
|
{ label: 'Large files', value: formatCount(large.length), hint: `over ${formatCount(LARGE_FILE)} lines` },
|
|
96
124
|
{ label: 'Constructor injection', value: formatCount(constructorInjections.length), hint: `classes; ${formatCount(injectCalls)} inject() calls` },
|
|
97
125
|
{ label: 'Class guards', value: formatCount(classGuards.length), hint: 'and interceptors' },
|
|
126
|
+
churn
|
|
127
|
+
? { label: 'Hotspots', value: formatCount(hotspots.length), hint: `from ${plural(churn.commits, 'commit')} in the last year` }
|
|
128
|
+
: { label: 'Hotspots', value: '-', hint: context.churn === 'shallow clone' ? 'shallow clone: no history (fetch-depth: 0)' : `${context.churn}` },
|
|
98
129
|
],
|
|
99
130
|
findings,
|
|
100
131
|
measures: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"architecture.js","sourceRoot":"","sources":["../../src/checks/architecture.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAwB,MAAM,cAAc,CAAC;AAEpG,0DAA0D;AAC1D,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,gEAAgE;AAChE,MAAM,eAAe,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"architecture.js","sourceRoot":"","sources":["../../src/checks/architecture.ts"],"names":[],"mappings":"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"}
|
package/dist/checks/context.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { AngularJsFacts } from '../angularjs.js';
|
|
2
2
|
import type { BundleStats } from '../bundle.js';
|
|
3
|
+
import type { Churn, ChurnUnavailable } from '../churn.js';
|
|
3
4
|
import type ts from 'typescript';
|
|
4
5
|
import type { PackageCompatibility } from '../compatibility.js';
|
|
5
6
|
import type { DeprecationIndex } from '../deprecations.js';
|
|
6
7
|
import type { DependencyTangle } from '../graph.js';
|
|
7
8
|
import type { TypingsSource } from '../typings.js';
|
|
9
|
+
import type { VulnerabilityScan } from '../vulnerabilities.js';
|
|
8
10
|
import type { SourceFileFacts, TemplateFact } from '../scan.js';
|
|
9
11
|
import type { CheckResult, FileRef } from '../types.js';
|
|
10
12
|
import type { WorkspaceInfo } from '../workspace.js';
|
|
@@ -37,6 +39,10 @@ export interface AnalysisContext {
|
|
|
37
39
|
bundles: BundleStats[];
|
|
38
40
|
/** Installed third-party packages that depend on Angular. Empty without node_modules. */
|
|
39
41
|
compatibility: PackageCompatibility[];
|
|
42
|
+
/** Commits per file over the last year from git, or why there is no history to read (see `readChurn`). */
|
|
43
|
+
churn: Churn | ChurnUnavailable;
|
|
44
|
+
/** Known vulnerabilities of the direct dependencies, from osv.dev. Undefined offline or when OSV did not answer. */
|
|
45
|
+
vulnerabilities?: VulnerabilityScan;
|
|
40
46
|
/** Newest Angular major, and whether it came from the registry or the tool's bundled fallback. */
|
|
41
47
|
latestAngular: {
|
|
42
48
|
major: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/checks/context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/checks/context.ts"],"names":[],"mappings":"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"}
|
package/dist/checks/security.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compareVersions, severityRank } from '../vulnerabilities.js';
|
|
1
2
|
import { applicationFiles, formatCount, plural } from './context.js';
|
|
2
3
|
/**
|
|
3
4
|
* The places where the code steps around Angular's built-in XSS protection.
|
|
@@ -55,21 +56,58 @@ export function securityCheck(context) {
|
|
|
55
56
|
...examples(innerHtml.map((template) => ({ path: template.path }))),
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
|
-
const
|
|
59
|
+
const scan = context.vulnerabilities;
|
|
60
|
+
const runtime = scan?.packages.filter((entry) => !entry.dev) ?? [];
|
|
61
|
+
const tooling = scan?.packages.filter((entry) => entry.dev) ?? [];
|
|
62
|
+
const runtimeCount = runtime.reduce((sum, entry) => sum + entry.vulnerabilities.length, 0);
|
|
63
|
+
const severe = runtime.reduce((sum, entry) => sum + entry.vulnerabilities.filter((vulnerability) => severityRank(vulnerability.severity) >= 3).length, 0);
|
|
64
|
+
const moderate = runtime.some((entry) => entry.vulnerabilities.some((vulnerability) => vulnerability.severity === 'moderate'));
|
|
65
|
+
if (runtimeCount > 0) {
|
|
66
|
+
findings.unshift({
|
|
67
|
+
severity: severe > 0 ? 'high' : moderate ? 'medium' : 'low',
|
|
68
|
+
message: `${plural(runtimeCount, 'known vulnerability', 'known vulnerabilities')} in ${plural(runtime.length, 'dependency', 'dependencies')}${severe > 0 ? ` (${formatCount(severe)} high or critical)` : ''}`,
|
|
69
|
+
detail: `${runtime
|
|
70
|
+
.slice(0, 6)
|
|
71
|
+
.map((entry) => {
|
|
72
|
+
const cves = [...new Set(entry.vulnerabilities.flatMap((vulnerability) => vulnerability.cves))];
|
|
73
|
+
const fixes = [...new Set(entry.vulnerabilities.map((vulnerability) => vulnerability.fixedIn).filter((fixed) => fixed !== undefined))];
|
|
74
|
+
const unfixed = entry.vulnerabilities.some((vulnerability) => vulnerability.fixedIn === undefined);
|
|
75
|
+
const fix = unfixed ? (fixes.length > 0 ? 'some without a fixed version' : 'no fixed version') : `all fixed by ${fixes.sort(compareVersions).pop()}`;
|
|
76
|
+
return `\`${entry.name}\` ${entry.version}: ${plural(entry.vulnerabilities.length, 'advisory', 'advisories')}${cves.length > 0 ? ` (${cves.slice(0, 3).join(', ')}${cves.length > 3 ? ', …' : ''})` : ''}, ${fix}`;
|
|
77
|
+
})
|
|
78
|
+
.join('; ')}${runtime.length > 6 ? `; and ${plural(runtime.length - 6, 'more dependency', 'more dependencies')}` : ''}. Direct dependencies at their exact versions, from the advisories in osv.dev; the full dependency tree is \`npm audit\`'s job.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (tooling.length > 0) {
|
|
82
|
+
findings.push({
|
|
83
|
+
severity: 'low',
|
|
84
|
+
message: `${plural(tooling.reduce((sum, entry) => sum + entry.vulnerabilities.length, 0), 'known vulnerability', 'known vulnerabilities')} in development tooling`,
|
|
85
|
+
detail: `${tooling.map((entry) => `\`${entry.name}\` ${entry.version}`).join(', ')}. Not shipped to users, but they run on developer machines and in CI.`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const codeGrade = files.length === 0 ? 'info' : unsafeDom.length > 0 || dynamicCode.length > 0 ? 'poor' : bypass.length > 0 ? 'fair' : 'good';
|
|
89
|
+
// Known vulnerabilities grade an app even without TypeScript to read (an AngularJS app on 1.8.3).
|
|
90
|
+
const grade = severe > 0 ? 'poor' : moderate && codeGrade !== 'poor' ? 'fair' : codeGrade === 'info' && scan && scan.checked > 0 ? (runtimeCount > 0 ? 'fair' : 'good') : codeGrade;
|
|
91
|
+
const vulnerabilitySummary = runtimeCount > 0 ? `; ${plural(runtimeCount, 'known vulnerability', 'known vulnerabilities')} in dependencies` : '';
|
|
59
92
|
return {
|
|
60
93
|
id: 'security',
|
|
61
94
|
title: 'Security',
|
|
62
95
|
grade,
|
|
63
96
|
summary: files.length === 0
|
|
64
|
-
?
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
? runtimeCount > 0
|
|
98
|
+
? `No TypeScript application code to check; ${plural(runtimeCount, 'known vulnerability', 'known vulnerabilities')} in ${plural(runtime.length, 'dependency', 'dependencies')}.`
|
|
99
|
+
: 'No application code to check.'
|
|
100
|
+
: codeGrade === 'good'
|
|
101
|
+
? `No sanitizer bypasses, direct DOM HTML writes or eval in ${plural(files.length, 'file')}${innerHtmlCount > 0 ? `; ${plural(innerHtmlCount, 'sanitized [innerHTML] binding')}` : ''}${vulnerabilitySummary}.`
|
|
102
|
+
: `${plural(unsafeDom.length, 'direct DOM HTML write')}, ${plural(dynamicCode.length, 'eval')}, ${plural(bypass.length, 'sanitizer bypass', 'sanitizer bypasses')}${vulnerabilitySummary}.`,
|
|
68
103
|
metrics: [
|
|
69
104
|
{ label: 'Direct DOM HTML writes', value: formatCount(unsafeDom.length), hint: 'innerHTML =, insertAdjacentHTML' },
|
|
70
105
|
{ label: 'Sanitizer bypasses', value: formatCount(bypass.length), hint: 'bypassSecurityTrust*' },
|
|
71
106
|
{ label: 'eval / new Function', value: formatCount(dynamicCode.length) },
|
|
72
107
|
{ label: '[innerHTML] bindings', value: formatCount(innerHtmlCount), hint: 'sanitized by Angular' },
|
|
108
|
+
scan
|
|
109
|
+
? { label: 'Known vulnerabilities', value: formatCount(runtimeCount), hint: `in ${plural(scan.checked, 'direct dependency', 'direct dependencies')} checked` }
|
|
110
|
+
: { label: 'Known vulnerabilities', value: 'not checked', hint: 'offline' },
|
|
73
111
|
],
|
|
74
112
|
findings,
|
|
75
113
|
measures: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/checks/security.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/checks/security.ts"],"names":[],"mappings":"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"}
|
package/dist/churn.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** How often each file changed recently, from git, as `AnalysisContext.churn`. */
|
|
2
|
+
export interface Churn {
|
|
3
|
+
/** The window, as git understands it (`12.months`). */
|
|
4
|
+
since: string;
|
|
5
|
+
/** Commits in the window that touched the analysed folder, merges left out. */
|
|
6
|
+
commits: number;
|
|
7
|
+
/** Commits per report path (relative to the analysed root). */
|
|
8
|
+
files: Map<string, number>;
|
|
9
|
+
}
|
|
10
|
+
/** Why there is no churn to report. */
|
|
11
|
+
export type ChurnUnavailable = 'not a git repository' | 'shallow clone' | 'git not installed';
|
|
12
|
+
/**
|
|
13
|
+
* Commits per file over the last `since`, for the files under `root`.
|
|
14
|
+
*
|
|
15
|
+
* A file that is both large and changed often is where a refactoring pays
|
|
16
|
+
* off first: it is read and edited all the time, so its size costs on every
|
|
17
|
+
* change. Static analysis cannot see that; the history can. `--relative`
|
|
18
|
+
* keeps paths relative to `root` and limits the log to it, so a checkup of
|
|
19
|
+
* one folder of a larger repository counts that folder only.
|
|
20
|
+
*
|
|
21
|
+
* @returns The reason instead when there is no usable history: not a git
|
|
22
|
+
* repository, or a shallow clone (the remote mode and CI checkouts with
|
|
23
|
+
* `fetch-depth: 1`), whose history is one commit and would rank nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readChurn(root: string, since?: string): Churn | ChurnUnavailable;
|
package/dist/churn.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Commits per file over the last `since`, for the files under `root`.
|
|
4
|
+
*
|
|
5
|
+
* A file that is both large and changed often is where a refactoring pays
|
|
6
|
+
* off first: it is read and edited all the time, so its size costs on every
|
|
7
|
+
* change. Static analysis cannot see that; the history can. `--relative`
|
|
8
|
+
* keeps paths relative to `root` and limits the log to it, so a checkup of
|
|
9
|
+
* one folder of a larger repository counts that folder only.
|
|
10
|
+
*
|
|
11
|
+
* @returns The reason instead when there is no usable history: not a git
|
|
12
|
+
* repository, or a shallow clone (the remote mode and CI checkouts with
|
|
13
|
+
* `fetch-depth: 1`), whose history is one commit and would rank nothing.
|
|
14
|
+
*/
|
|
15
|
+
export function readChurn(root, since = '12.months') {
|
|
16
|
+
const git = (...args) => execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 256 * 1024 * 1024, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
17
|
+
try {
|
|
18
|
+
if (git('rev-parse', '--is-inside-work-tree').trim() !== 'true')
|
|
19
|
+
return 'not a git repository';
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return error.code === 'ENOENT' ? 'git not installed' : 'not a git repository';
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
if (git('rev-parse', '--is-shallow-repository').trim() === 'true')
|
|
26
|
+
return 'shallow clone';
|
|
27
|
+
const log = git('log', `--since=${since}`, '--no-merges', '--relative', '--format=%x01', '--name-only', '--', '.');
|
|
28
|
+
const files = new Map();
|
|
29
|
+
let commits = 0;
|
|
30
|
+
for (const block of log.split('\u0001')) {
|
|
31
|
+
const paths = block.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
32
|
+
if (paths.length === 0)
|
|
33
|
+
continue;
|
|
34
|
+
commits++;
|
|
35
|
+
for (const path of new Set(paths))
|
|
36
|
+
files.set(path, (files.get(path) ?? 0) + 1);
|
|
37
|
+
}
|
|
38
|
+
return { since, commits, files };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return 'not a git repository';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=churn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churn.js","sourceRoot":"","sources":["../src/churn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAelD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAK,GAAG,WAAW;IACzD,MAAM,GAAG,GAAG,CAAC,GAAG,IAAc,EAAU,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClO,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,sBAAsB,CAAC;IACjG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IACvG,CAAC;IACD,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,WAAW,EAAE,yBAAyB,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,eAAe,CAAC;QAC1F,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACnH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;QACxC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACjC,OAAO,EAAE,CAAC;YACV,KAAK,MAAM,IAAI,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,sBAAsB,CAAC;IAChC,CAAC;AACH,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type CheckupConfig } from './config.js';
|
|
|
3
3
|
import { type CiChoice, type InitPlan } from './init.js';
|
|
4
4
|
import { type RemoteRepository } from './remote.js';
|
|
5
5
|
import type { Report } from './types.js';
|
|
6
|
+
import { type HistoryReport } from './history.js';
|
|
6
7
|
/** Parsed and validated arguments of a checkup run, before the config file is applied. */
|
|
7
8
|
export interface CliArguments {
|
|
8
9
|
/** The local root; for a remote run, the repository argument until `main` has cloned it. */
|
|
@@ -20,6 +21,9 @@ export interface CliArguments {
|
|
|
20
21
|
plan: boolean;
|
|
21
22
|
/** `--project`: one project of the workspace, by name. */
|
|
22
23
|
project?: string;
|
|
24
|
+
/** `--only` and `--skip`, comma-separated check ids; undefined when not given, so the config can supply them. */
|
|
25
|
+
only?: string[];
|
|
26
|
+
skip?: string[];
|
|
23
27
|
/** `--client`, `--prepared-by`, `--notes`: the client report. */
|
|
24
28
|
client?: {
|
|
25
29
|
name: string;
|
|
@@ -55,6 +59,8 @@ export interface RunSettings {
|
|
|
55
59
|
offline: boolean;
|
|
56
60
|
plan: boolean;
|
|
57
61
|
ignore?: string[];
|
|
62
|
+
only?: string[];
|
|
63
|
+
skip?: string[];
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
66
|
* Parses `argv` (without the node and script entries) of a checkup run.
|
|
@@ -105,7 +111,6 @@ export declare function analysisIgnores(root: string, settings: Pick<RunSettings
|
|
|
105
111
|
* @throws When a measure went up and `force` is not set, listing each one.
|
|
106
112
|
*/
|
|
107
113
|
export declare function writeBaseline(args: BaselineArguments): Promise<string>;
|
|
108
|
-
/** Prints a user-facing error with the hint to `--help` and returns exit code 2. */
|
|
109
114
|
/** Parsed arguments of `ng-checkup url`. */
|
|
110
115
|
export interface UrlArguments {
|
|
111
116
|
address: string;
|
|
@@ -115,3 +120,18 @@ export interface UrlArguments {
|
|
|
115
120
|
}
|
|
116
121
|
/** Parses the arguments after `url`: one address, `--json`, `--offline`, `--no-color`. */
|
|
117
122
|
export declare function parseUrlArguments(argv: string[], environment?: NodeJS.ProcessEnv, isTty?: boolean): UrlArguments | 'help';
|
|
123
|
+
/** Parsed arguments of `ng-checkup history`. */
|
|
124
|
+
export interface HistoryArguments {
|
|
125
|
+
root: string;
|
|
126
|
+
/** Months back; one point per month, plus the working tree. */
|
|
127
|
+
points: number;
|
|
128
|
+
/** Resolved against the working directory; .html, .md or .json. Empty when none. */
|
|
129
|
+
reports: string[];
|
|
130
|
+
json: boolean;
|
|
131
|
+
color: boolean;
|
|
132
|
+
config?: string;
|
|
133
|
+
}
|
|
134
|
+
/** Parses the arguments after `history`. Throws a user-facing `Error` on anything invalid. */
|
|
135
|
+
export declare function parseHistoryArguments(argv: string[], environment?: NodeJS.ProcessEnv, isTty?: boolean): HistoryArguments | 'help';
|
|
136
|
+
/** Renders a history in the format its file extension names. */
|
|
137
|
+
export declare function renderHistoryForFile(report: HistoryReport, path: string): string;
|