ng-checkup 0.11.1 → 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.
Files changed (46) hide show
  1. package/README.md +85 -10
  2. package/dist/cache.d.ts +1 -0
  3. package/dist/cache.js.map +1 -1
  4. package/dist/checks/architecture.js +31 -0
  5. package/dist/checks/architecture.js.map +1 -1
  6. package/dist/checks/context.d.ts +6 -0
  7. package/dist/checks/context.js.map +1 -1
  8. package/dist/checks/security.js +43 -5
  9. package/dist/checks/security.js.map +1 -1
  10. package/dist/churn.d.ts +25 -0
  11. package/dist/churn.js +44 -0
  12. package/dist/churn.js.map +1 -0
  13. package/dist/cli.d.ts +30 -0
  14. package/dist/cli.js +173 -10
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config.d.ts +4 -0
  17. package/dist/config.js +9 -1
  18. package/dist/config.js.map +1 -1
  19. package/dist/history.d.ts +66 -0
  20. package/dist/history.js +103 -0
  21. package/dist/history.js.map +1 -0
  22. package/dist/index.d.ts +27 -0
  23. package/dist/index.js +50 -9
  24. package/dist/index.js.map +1 -1
  25. package/dist/init.d.ts +3 -3
  26. package/dist/init.js +3 -3
  27. package/dist/report/history.d.ts +17 -0
  28. package/dist/report/history.js +138 -0
  29. package/dist/report/history.js.map +1 -0
  30. package/dist/report/html.d.ts +5 -0
  31. package/dist/report/html.js +1 -1
  32. package/dist/report/html.js.map +1 -1
  33. package/dist/report/site.d.ts +19 -0
  34. package/dist/report/site.js +32 -0
  35. package/dist/report/site.js.map +1 -0
  36. package/dist/report/terminal.d.ts +11 -0
  37. package/dist/report/terminal.js +2 -1
  38. package/dist/report/terminal.js.map +1 -1
  39. package/dist/site.d.ts +105 -0
  40. package/dist/site.js +252 -0
  41. package/dist/site.js.map +1 -0
  42. package/dist/types.d.ts +7 -0
  43. package/dist/vulnerabilities.d.ts +54 -0
  44. package/dist/vulnerabilities.js +116 -0
  45. package/dist/vulnerabilities.js.map +1 -0
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -23,9 +23,11 @@ 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)
30
+ - [A live site](#a-live-site)
29
31
  - [Monorepos](#monorepos)
30
32
  - [Client report](#client-report)
31
33
  - [What it checks](#what-it-checks)
@@ -116,6 +118,8 @@ npx ng-checkup init --ci gitlab # pick the CI instead of detecting it (github
116
118
  | `ignore` | paths left out of the analysis; a path matches everything below it, `*` stays within a folder, `**` crosses folders | |
117
119
  | `baseline` | the baseline file, relative to the config file (default `ng-checkup.baseline.json` in the root) | |
118
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` |
119
123
 
120
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.
121
125
 
@@ -157,6 +161,35 @@ Score 72 -> 76 (+4 since the watch started)
157
161
 
158
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.
159
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
+
160
193
  ## Upgrade plan
161
194
 
162
195
  ```bash
@@ -222,6 +255,27 @@ Libraries on Ex Libris Primo VE customize it with an AngularJS package, and thos
222
255
 
223
256
  A `js/custom.js` built from the view's other files and copies outside the view folders are not counted twice. With `--plan`, the plan has four phases (decide what NDE still needs and set up the module, branding and home pages, rebuild the hooks, test in proxy mode and switch view by view), and the client report puts them in its roadmap.
224
257
 
258
+ ## A live site
259
+
260
+ ```bash
261
+ npx ng-checkup url https://app.example.com
262
+ ```
263
+
264
+ ```text
265
+ ng-checkup https://my.example-saas.de/ Login
266
+ Read the page and 5 scripts (36.7 MB) from my.example-saas.de
267
+
268
+ AngularJS 1.8.3 seen in vendor.bundle.js
269
+ React 19.2.3 seen in react.bundle.js
270
+
271
+ high AngularJS 1.8.3 is end of life since January 2022
272
+ It runs next to React, so a migration is under way. Until it is finished, the app ships a framework without security fixes.
273
+ ```
274
+
275
+ Which framework a public web app runs, without its code: the page and the scripts of the same site it loads, including the chunks they import, up to 12 requests. It recognises AngularJS and Angular with their versions (from the license header, the version object or `new Version("…")` in the bundle, and `ng-version`), whether zone.js is loaded, and React, Vue, Next.js and Nuxt, also several at once. Third-party scripts are skipped (a chat widget built with React does not make a site a React app), except AngularJS from a public CDN. It reads only what any browser downloads: nothing is submitted, nothing behind a sign-in is reached. `--json` prints the result for scripts.
276
+
277
+ Useful before a first conversation: what a company's product runs on, how far behind it is, and whether a migration has started.
278
+
225
279
  ## Monorepos
226
280
 
227
281
  In an Angular CLI workspace with several projects or an Nx workspace, the report adds a table with one row per project, worst score first (here [nx-examples](https://github.com/nrwl/nx-examples), shortened):
@@ -272,14 +326,14 @@ The codebase is named by its repository and commit, never by a path on your disk
272
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 |
273
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% |
274
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 |
275
- | [**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 |
276
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 |
277
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 |
278
- | [**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` |
279
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 |
280
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) |
281
335
 
282
- 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)**
283
337
 
284
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.
285
339
 
@@ -308,6 +362,8 @@ Defaults that changed between versions are applied per version: a component with
308
362
  Usage: ng-checkup [path | repository] [options]
309
363
  ng-checkup init [path] [init options]
310
364
  ng-checkup baseline [path] [--force]
365
+ ng-checkup url <address> [--json] [--offline]
366
+ ng-checkup history [path] [--points <n>] [--report <file>] [--json]
311
367
 
312
368
  Arguments:
313
369
  path Repository root (default: current directory)
@@ -342,8 +398,11 @@ Options:
342
398
  --fail-on <grade> Exit with code 1 if any area is graded <grade> or worse
343
399
  (poor or fair), or with regression if a measure is
344
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
345
404
  --max-examples <n> Example files listed per finding (default 5)
346
- --offline Do not look up the latest Angular version online
405
+ --offline Nothing online: no npm registry, no osv.dev lookup
347
406
  --config <file> Config file (default: ng-checkup.config.json in the root)
348
407
  --no-color Plain output (also off when NO_COLOR is set or not a TTY)
349
408
  -h, --help Show this help
@@ -356,6 +415,23 @@ Init options (ng-checkup init):
356
415
 
357
416
  Baseline options (ng-checkup baseline):
358
417
  --force Accept measures that went up since the last baseline
418
+
419
+ Url (ng-checkup url <address>):
420
+ Which framework a public web app runs, from what a
421
+ browser downloads: AngularJS and Angular with their
422
+ versions, zone.js, React, Vue, Next.js, Nuxt. Reads
423
+ the page and up to 12 scripts of the same site
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
359
435
  ```
360
436
 
361
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.
@@ -382,7 +458,7 @@ jobs:
382
458
  runs-on: ubuntu-latest
383
459
  steps:
384
460
  - uses: actions/checkout@v4
385
- - uses: stefankuburovic/ng-checkup@v0
461
+ - uses: stefankuburovic/ng-checkup@v1
386
462
  with:
387
463
  fail-on: regression # needs a committed baseline: npx ng-checkup baseline
388
464
  sarif: true
@@ -411,7 +487,7 @@ No `npm ci` needed: versions come from the lockfile. Inputs: `path` (a folder in
411
487
 
412
488
  ```yaml
413
489
  include:
414
- - component: $CI_SERVER_FQDN/stefankuburovic/ng-checkup/ng-checkup@0.10.0
490
+ - component: $CI_SERVER_FQDN/stefankuburovic/ng-checkup/ng-checkup@1
415
491
  inputs:
416
492
  fail-on: regression
417
493
  ```
@@ -420,7 +496,7 @@ On a self-managed GitLab, which cannot include components from gitlab.com, use t
420
496
 
421
497
  ```yaml
422
498
  include:
423
- - remote: https://raw.githubusercontent.com/stefankuburovic/ng-checkup/v0/gitlab/ng-checkup.yml
499
+ - remote: https://raw.githubusercontent.com/stefankuburovic/ng-checkup/v1/gitlab/ng-checkup.yml
424
500
 
425
501
  ng-checkup:
426
502
  variables:
@@ -468,11 +544,11 @@ for (const check of report.checks) {
468
544
  await writeFile('checkup.md', renderMarkdown(report));
469
545
  ```
470
546
 
471
- `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.
472
548
 
473
549
  ## What leaves your machine
474
550
 
475
- Package names and versions, and only to the public 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). `--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.
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.
476
552
 
477
553
  ## How it works
478
554
 
@@ -502,7 +578,6 @@ Package names and versions, and only to the public npm registry: the latest `@an
502
578
  ## Roadmap
503
579
 
504
580
  - Per-project baselines for monorepos
505
- - Change frequency from git history, to rank large files by how often they change
506
581
  - A VS Code extension showing findings inline
507
582
 
508
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;AAiCjC,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"}
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;AAE7B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAwB;IACxD,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,eAAe,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpH,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACjJ,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACjK,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,IAAI,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IACjI,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,IAAI,eAAe,CAAC,CAAC;IAC9E,MAAM,qBAAqB,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAClI,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9G,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChH,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC;IAEhC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QACnE,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,MAAM,CAAC,eAAe;YACtJ,MAAM,EAAE,gQAAgQ;YACxQ,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YAC1B,KAAK,EAAE,KAAK,CAAC,MAAM;SACpB,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;YACjD,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,SAAS,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;YACxL,MAAM,EAAE,gBAAgB,KAAK;iBAC1B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;iBACjF,IAAI,CAAC,IAAI,CAAC,+HAA+H;YAC5I,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/D,KAAK,EAAE,KAAK,CAAC,MAAM;SACpB,CAAC,CAAC;IACL,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,kCAAkC,EAAE,qCAAqC,CAAC,EAAE;YACnH,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oTAAoT;YAC9X,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzF,KAAK,EAAE,WAAW,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IACD,IAAI,qBAAqB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,GAAG,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,eAAe,EAAE,gBAAgB,CAAC,0BAA0B;YAC7G,MAAM,EAAE,wCAAwC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,0LAA0L;YACrQ,KAAK,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACnG,KAAK,EAAE,qBAAqB,CAAC,MAAM;SACpC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,KAAK,GACT,KAAK,CAAC,MAAM,KAAK,CAAC;QAChB,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC;YAClE,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;gBACnE,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,MAAM,CAAC;IACjB,OAAO;QACL,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,cAAc;QACrB,KAAK;QACL,OAAO,EACL,KAAK,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC,+BAA+B;YACjC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,WAAW,CAAC,UAAU,CAAC,WAAW,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,8BAA8B;QAChQ,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,SAAS,GAAG,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE;YAC/G,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE;YACzG,EAAE,KAAK,EAAE,uBAAuB,EAAE,KAAK,EAAE,WAAW,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,YAAY,WAAW,CAAC,WAAW,CAAC,iBAAiB,EAAE;YACjJ,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE;SAC5F;QACD,QAAQ;QACR,QAAQ,EAAE;YACR,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,uBAAuB,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE;YAC3E,EAAE,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,cAAc,eAAe,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE;YACjG,EAAE,EAAE,EAAE,uBAAuB,EAAE,KAAK,EAAE,oCAAoC,EAAE,KAAK,EAAE,qBAAqB,CAAC,MAAM,EAAE;YACjH,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,qCAAqC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE;SAChG;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"architecture.js","sourceRoot":"","sources":["../../src/checks/architecture.ts"],"names":[],"mappings":"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"}
@@ -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":"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"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/checks/context.ts"],"names":[],"mappings":"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"}
@@ -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 grade = files.length === 0 ? 'info' : unsafeDom.length > 0 || dynamicCode.length > 0 ? 'poor' : bypass.length > 0 ? 'fair' : 'good';
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
- ? 'No application code to check.'
65
- : grade === 'good'
66
- ? `No sanitizer bypasses, direct DOM HTML writes or eval in ${plural(files.length, 'file')}${innerHtmlCount > 0 ? `; ${plural(innerHtmlCount, 'sanitized [innerHTML] binding')}` : ''}.`
67
- : `${plural(unsafeDom.length, 'direct DOM HTML write')}, ${plural(dynamicCode.length, 'eval')}, ${plural(bypass.length, 'sanitizer bypass', 'sanitizer bypasses')}.`,
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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"}
@@ -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,3 +111,27 @@ 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>;
114
+ /** Parsed arguments of `ng-checkup url`. */
115
+ export interface UrlArguments {
116
+ address: string;
117
+ json: boolean;
118
+ offline: boolean;
119
+ color: boolean;
120
+ }
121
+ /** Parses the arguments after `url`: one address, `--json`, `--offline`, `--no-color`. */
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;