ng-checkup 0.4.0 → 0.5.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 +83 -9
- package/dist/bundle.d.ts +58 -0
- package/dist/bundle.js +189 -0
- package/dist/bundle.js.map +1 -0
- package/dist/cache.d.ts +41 -0
- package/dist/cache.js +36 -0
- package/dist/cache.js.map +1 -0
- package/dist/checks/architecture.d.ts +21 -0
- package/dist/checks/architecture.js +108 -0
- package/dist/checks/architecture.js.map +1 -0
- package/dist/checks/bundle.d.ts +24 -0
- package/dist/checks/bundle.js +112 -0
- package/dist/checks/bundle.js.map +1 -0
- package/dist/checks/context.d.ts +13 -0
- package/dist/checks/context.js.map +1 -1
- package/dist/checks/dependencies.d.ts +18 -0
- package/dist/checks/dependencies.js +89 -0
- package/dist/checks/dependencies.js.map +1 -0
- package/dist/checks/deprecated-apis.js +23 -3
- package/dist/checks/deprecated-apis.js.map +1 -1
- package/dist/checks/security.d.ts +17 -0
- package/dist/checks/security.js +87 -0
- package/dist/checks/security.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +102 -14
- package/dist/cli.js.map +1 -1
- package/dist/compatibility.d.ts +43 -0
- package/dist/compatibility.js +131 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/deprecations.js +21 -8
- package/dist/deprecations.js.map +1 -1
- package/dist/graph.d.ts +20 -0
- package/dist/graph.js +30 -0
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +137 -14
- package/dist/index.js.map +1 -1
- package/dist/plan.d.ts +2 -1
- package/dist/plan.js +37 -1
- package/dist/plan.js.map +1 -1
- package/dist/report/html.js +32 -1
- package/dist/report/html.js.map +1 -1
- package/dist/report/markdown.js +11 -1
- package/dist/report/markdown.js.map +1 -1
- package/dist/report/sarif.d.ts +20 -0
- package/dist/report/sarif.js +83 -0
- package/dist/report/sarif.js.map +1 -0
- package/dist/report/summary.d.ts +11 -2
- package/dist/report/summary.js +12 -0
- package/dist/report/summary.js.map +1 -1
- package/dist/report/terminal.js +20 -1
- package/dist/report/terminal.js.map +1 -1
- package/dist/scan.d.ts +43 -0
- package/dist/scan.js +60 -0
- package/dist/scan.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/watch.d.ts +47 -0
- package/dist/watch.js +107 -0
- package/dist/watch.js.map +1 -0
- package/dist/workspace.d.ts +41 -0
- package/dist/workspace.js +86 -3
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**A one-minute modernization report for any Angular codebase.**
|
|
6
6
|
|
|
7
|
-
Point it at a repository and it tells you how far the code is from current Angular, what will break on the next upgrade, and where the work is.
|
|
7
|
+
Point it at a repository and it tells you how far the code is from current Angular, what will break on the next upgrade, and where the work is. Twelve areas get a grade, from the Angular version and third-party packages to security and bundle size, every finding names the files, and the HTML report is one self-contained page you can hand to your team lead.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx ng-checkup
|
|
@@ -22,7 +22,9 @@ npx ng-checkup
|
|
|
22
22
|
- [Set it up in a project](#set-it-up-in-a-project)
|
|
23
23
|
- [Configuration](#configuration)
|
|
24
24
|
- [Baseline: adopt it on legacy code](#baseline-adopt-it-on-legacy-code)
|
|
25
|
+
- [Watch mode](#watch-mode)
|
|
25
26
|
- [Upgrade plan](#upgrade-plan)
|
|
27
|
+
- [Monorepos](#monorepos)
|
|
26
28
|
- [What it checks](#what-it-checks)
|
|
27
29
|
- [Reports](#reports)
|
|
28
30
|
- [In CI](#in-ci)
|
|
@@ -120,13 +122,29 @@ Baseline 2 worse
|
|
|
120
122
|
worse Type safety: Non-null assertions (x!) 0 → 1
|
|
121
123
|
```
|
|
122
124
|
|
|
123
|
-
- **It records counts, not grades:** explicit `any`, `x!`, files in import cycles, components without OnPush or a spec, NgModule-declared components, deprecated API uses and [
|
|
125
|
+
- **It records counts, not grades:** explicit `any`, `x!`, files in import cycles, components without OnPush or a spec, NgModule-declared components, deprecated API uses, sanitizer bypasses, eager routes and [24 more](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#measures). Lower is better for every one.
|
|
124
126
|
- **It only goes down.** When numbers drop, the run says so; `npx ng-checkup baseline` locks the new numbers in. If a number went up, `baseline` refuses and lists it, so a regression cannot be waved through by re-running the command. `--force` accepts it, which is the moment someone has to explain why in review.
|
|
125
127
|
- **It is a plain, sorted JSON file**, so lowering a number is a one-line diff in a merge request.
|
|
126
128
|
- **The gap to the latest Angular is not in it.** That grows when Angular ships, not when the code gets worse.
|
|
127
129
|
|
|
128
130
|
`ng add ng-checkup` and `npx ng-checkup init` create the first baseline and set CI to `--fail-on regression` for you.
|
|
129
131
|
|
|
132
|
+
## Watch mode
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx ng-checkup --watch
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Re-runs on every save and prints, under the usual summary, how the score and every measure moved since the watch started:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
Score 72 -> 76 (+4 since the watch started)
|
|
142
|
+
down Explicit any: 41 -> 37
|
|
143
|
+
down Components without OnPush: 12 -> 11
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
130
148
|
## Upgrade plan
|
|
131
149
|
|
|
132
150
|
```bash
|
|
@@ -156,18 +174,43 @@ Upgrade plan 12 → 22, 10 steps, one major at a time
|
|
|
156
174
|
|
|
157
175
|
The plan is in every format, including the HTML report ([example](https://stefankuburovic.github.io/ng-checkup/example.html#plan-title)) and the JSON (`report.plan`).
|
|
158
176
|
|
|
177
|
+
## Monorepos
|
|
178
|
+
|
|
179
|
+
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):
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
Projects 5, worst first, graded on their own code
|
|
183
|
+
65 products apps/products poor: Change detection & zoneless readiness, Components & templates, Type safety fair: State management
|
|
184
|
+
80 products-home-page libs/products/home-page poor: Type safety fair: Components & templates, State management
|
|
185
|
+
80 shared-cart-state libs/shared/cart/state poor: Type safety fair: State management
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Each project is graded on the files it owns (a nested library keeps its own) with its own tsconfig. The Angular version and the third-party packages are shared, so they are graded once, for the workspace. Projects that import nothing from Angular (React apps, Node services, Cypress e2e folders) get no row.
|
|
189
|
+
|
|
190
|
+
`--project <name>` narrows the whole report to one project, which is how a team owning one app runs it in CI:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
npx ng-checkup --project admin --report admin.html
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The baseline always covers the whole workspace, so `--project` runs do not compare against it.
|
|
197
|
+
|
|
159
198
|
## What it checks
|
|
160
199
|
|
|
161
200
|
| Area | It looks at | Poor when |
|
|
162
201
|
| --- | --- | --- |
|
|
163
202
|
| [**Angular version & tooling**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#version) | installed vs. latest Angular, mixed `@angular/*` majors, webpack builder, end-of-life packages | three or more majors behind, i.e. out of support |
|
|
164
203
|
| [**Deprecated APIs**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#deprecated-apis) | every imported Angular/NgRx symbol your **installed** version tags `@deprecated`, plus `*ngIf` / `*ngFor` in templates | something deprecated two majors ago is still used |
|
|
204
|
+
| [**Third-party compatibility**](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#dependencies) | the `@angular/core` peer range of every installed package (PrimeNG, Kendo, ng-bootstrap, NGXS, ...), the release that supports the next majors, npm deprecations | a package does not accept the installed Angular, or no release supports the latest |
|
|
165
205
|
| [**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 |
|
|
166
206
|
| [**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% |
|
|
167
207
|
| [**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 |
|
|
208
|
+
| [**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 |
|
|
168
209
|
| [**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 |
|
|
169
210
|
| [**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 |
|
|
211
|
+
| [**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` |
|
|
170
212
|
| [**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 |
|
|
213
|
+
| [**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) |
|
|
171
214
|
|
|
172
215
|
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)**
|
|
173
216
|
|
|
@@ -184,6 +227,7 @@ Defaults that changed between versions are applied per version: a component with
|
|
|
184
227
|
| `npx ng-checkup --report checkup.html` | self-contained page: light and dark mode, prints cleanly to PDF, loads nothing from the network |
|
|
185
228
|
| `npx ng-checkup --report checkup.md` | Markdown for a pull request, an issue or a wiki |
|
|
186
229
|
| `npx ng-checkup --report checkup.json` | everything, machine-readable |
|
|
230
|
+
| `npx ng-checkup --report checkup.sarif` | SARIF 2.1.0 for [GitHub code scanning](#in-ci) and other SARIF viewers |
|
|
187
231
|
| `npx ng-checkup --json` | the JSON on stdout, for piping |
|
|
188
232
|
|
|
189
233
|
`--report` is repeatable, so one run can write all three files.
|
|
@@ -201,11 +245,17 @@ Arguments:
|
|
|
201
245
|
|
|
202
246
|
Options:
|
|
203
247
|
-r, --report <file> Write the full report; format from the extension:
|
|
204
|
-
.html, .md or .
|
|
248
|
+
.html, .md, .json or .sarif (GitHub code scanning).
|
|
249
|
+
Repeatable.
|
|
205
250
|
--json Print the report as JSON to stdout instead of the summary
|
|
206
251
|
-v, --verbose Print every finding with its files
|
|
207
252
|
--plan Add an upgrade plan to the latest Angular, one major
|
|
208
253
|
at a time, with the migrations this code needs
|
|
254
|
+
-p, --project <name> Analyse one project of an Angular CLI or Nx workspace.
|
|
255
|
+
Without it, a multi-project workspace also gets a
|
|
256
|
+
per-project table
|
|
257
|
+
-w, --watch Re-run on every change and show how the score and the
|
|
258
|
+
measures moved since the watch started
|
|
209
259
|
--fail-on <grade> Exit with code 1 if any area is graded <grade> or worse
|
|
210
260
|
(poor or fair), or with regression if a measure is
|
|
211
261
|
worse than the baseline (for CI)
|
|
@@ -261,6 +311,26 @@ ng-checkup:
|
|
|
261
311
|
paths: [checkup.md, checkup.html]
|
|
262
312
|
```
|
|
263
313
|
|
|
314
|
+
**GitHub code scanning**
|
|
315
|
+
|
|
316
|
+
`--report ng-checkup.sarif` writes [SARIF](https://sarifweb.azurewebsites.net/), so findings show up as annotations on the changed lines of a pull request and in the Security tab:
|
|
317
|
+
|
|
318
|
+
```yaml
|
|
319
|
+
permissions:
|
|
320
|
+
security-events: write
|
|
321
|
+
steps:
|
|
322
|
+
- uses: actions/checkout@v4
|
|
323
|
+
- run: npm ci
|
|
324
|
+
- run: npx ng-checkup --offline --max-examples 1000 --report ng-checkup.sarif
|
|
325
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
326
|
+
if: always()
|
|
327
|
+
with:
|
|
328
|
+
sarif_file: ng-checkup.sarif
|
|
329
|
+
category: ng-checkup
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
One rule per area, so alerts survive a rerun; a finding about the whole project (the Angular version, a package that blocks the upgrade) is placed on `package.json`. `--max-examples` decides how many locations each finding reports. Code scanning is free on public repositories and needs GitHub Advanced Security on private ones.
|
|
333
|
+
|
|
264
334
|
On an existing codebase, start with a [baseline](#baseline-adopt-it-on-legacy-code) and `--fail-on regression`. `--fail-on poor` and `--fail-on fair` are for codebases that are already in good shape and want to stay there.
|
|
265
335
|
|
|
266
336
|
## Programmatic API
|
|
@@ -280,11 +350,11 @@ for (const check of report.checks) {
|
|
|
280
350
|
await writeFile('checkup.md', renderMarkdown(report));
|
|
281
351
|
```
|
|
282
352
|
|
|
283
|
-
`runCheckup` never writes anything. `renderHtml`, `renderMarkdown`, `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.
|
|
353
|
+
`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.
|
|
284
354
|
|
|
285
355
|
## What leaves your machine
|
|
286
356
|
|
|
287
|
-
|
|
357
|
+
Package names, and only to the public npm registry: the latest `@angular/core` version (three-second timeout), and for every installed 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). `--offline` skips all of it: the report then compares against the newest major this release knows and says so. No source, no file names, no telemetry. The tool reads files and writes only the reports you ask for.
|
|
288
358
|
|
|
289
359
|
## How it works
|
|
290
360
|
|
|
@@ -302,15 +372,19 @@ One request: the latest `@angular/core` version from the npm registry, with a th
|
|
|
302
372
|
|
|
303
373
|
**Is "with a spec" my test coverage?** No. It counts components and services with an `x.spec.ts` next to `x.ts`. It needs no test run, which is what keeps the tool fast and usable on a repository it has never seen.
|
|
304
374
|
|
|
305
|
-
**Does it support Nx?** Yes: it reads `project.json` targets and tsconfig `paths
|
|
375
|
+
**Does it support Nx?** Yes: it reads `project.json` targets and tsconfig `paths`, and reports [each project separately](#monorepos) as well as the whole workspace.
|
|
376
|
+
|
|
377
|
+
**Does it need RxJS or NgRx?** No. Checks report what they find: an app without RxJS has nothing for the RxJS deprecations to flag, and one without a state library is graded on signals and services. Other libraries (NGXS, Akita, PrimeNG, Kendo, Material) are covered where it matters for an upgrade, through their Angular peer ranges in the third-party check.
|
|
378
|
+
|
|
379
|
+
**Why is bundle size "info"?** It reads the stats file of your last build instead of running one. `ng build --stats-json` writes it; run the checkup after that.
|
|
306
380
|
|
|
307
381
|
**Which Angular versions?** Angular 2 and newer (not AngularJS). Checks whose rules changed between versions (standalone, OnPush, zoneless defaults) apply the rules of the version you run.
|
|
308
382
|
|
|
309
383
|
## Roadmap
|
|
310
384
|
|
|
311
|
-
- Per-project
|
|
312
|
-
-
|
|
313
|
-
-
|
|
385
|
+
- Per-project baselines for monorepos
|
|
386
|
+
- Change frequency from git history, to rank large files by how often they change
|
|
387
|
+
- A VS Code extension showing findings inline
|
|
314
388
|
|
|
315
389
|
Ideas and bug reports are welcome in [issues](https://github.com/stefankuburovic/ng-checkup/issues).
|
|
316
390
|
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type WorkspaceInfo } from './workspace.js';
|
|
2
|
+
/** Default budgets of a new Angular application (`initial`), used when the build target declares none. */
|
|
3
|
+
export declare const DEFAULT_BUDGET: {
|
|
4
|
+
warning: number;
|
|
5
|
+
error: number;
|
|
6
|
+
};
|
|
7
|
+
/** What one stats file says about one application's build. */
|
|
8
|
+
export interface BundleStats {
|
|
9
|
+
/** Project name from the build target, or the folder name when the file was found by searching `dist`. */
|
|
10
|
+
project: string;
|
|
11
|
+
/** The stats file, relative to the analysed root. */
|
|
12
|
+
file: string;
|
|
13
|
+
format: 'esbuild' | 'webpack';
|
|
14
|
+
/** When the stats file was written: a stale file describes an old build. ISO 8601. */
|
|
15
|
+
builtAt: string;
|
|
16
|
+
/** Bytes the browser loads before the app starts: entry scripts, what they import statically, global styles. */
|
|
17
|
+
initialBytes: number;
|
|
18
|
+
lazyChunks: number;
|
|
19
|
+
lazyBytes: number;
|
|
20
|
+
/** The largest lazily loaded file. Undefined without lazy chunks. */
|
|
21
|
+
largestLazy?: {
|
|
22
|
+
file: string;
|
|
23
|
+
bytes: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Bytes of the initial bundle per npm package, largest first; code outside
|
|
27
|
+
* `node_modules` is grouped as `(your code)`. Empty when the stats file does
|
|
28
|
+
* not break outputs down by input (webpack stats without modules).
|
|
29
|
+
*/
|
|
30
|
+
initialByPackage: Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
bytes: number;
|
|
33
|
+
}>;
|
|
34
|
+
/** The `initial` budget of the build target's production configuration, else `DEFAULT_BUDGET`. Bytes. */
|
|
35
|
+
budget: {
|
|
36
|
+
warning?: number;
|
|
37
|
+
error?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Finds and reads the stats file of every application build.
|
|
42
|
+
*
|
|
43
|
+
* `ng build --stats-json` writes `browser-stats.json` (Angular 18 and later)
|
|
44
|
+
* or `stats.json` (the esbuild builder in 17, and every webpack build) into
|
|
45
|
+
* the output folder. The folder comes from the build target's `outputPath`,
|
|
46
|
+
* or `dist/<project>` as the CLI defaults to; without targets, `dist` is
|
|
47
|
+
* searched two levels deep. Nothing is built: a project that was never built
|
|
48
|
+
* with `--stats-json` simply has no entry, and a stats file that does not
|
|
49
|
+
* parse is skipped rather than failing the run.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readBundleStats(workspace: WorkspaceInfo): BundleStats[];
|
|
52
|
+
/** The parts of a stats file that do not depend on where it was found. */
|
|
53
|
+
type ParsedStats = Pick<BundleStats, 'format' | 'initialBytes' | 'lazyChunks' | 'lazyBytes' | 'largestLazy' | 'initialByPackage'>;
|
|
54
|
+
/** Reads either format; undefined for JSON that is neither. Exported for tests. */
|
|
55
|
+
export declare function parseStats(json: unknown): ParsedStats | undefined;
|
|
56
|
+
/** `"500kB"` → 512000, `"1mb"` → 1048576, `"300"` → 300 bytes. Undefined for anything else. */
|
|
57
|
+
export declare function parseSize(value: unknown): number | undefined;
|
|
58
|
+
export {};
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
import { asRecord } from './workspace.js';
|
|
4
|
+
/** Default budgets of a new Angular application (`initial`), used when the build target declares none. */
|
|
5
|
+
export const DEFAULT_BUDGET = { warning: 500 * 1024, error: 1024 * 1024 };
|
|
6
|
+
/** Builders whose output is a browser bundle with a stats file. */
|
|
7
|
+
const APPLICATION_BUILDERS = /:(application|browser|browser-esbuild)$/;
|
|
8
|
+
/**
|
|
9
|
+
* Finds and reads the stats file of every application build.
|
|
10
|
+
*
|
|
11
|
+
* `ng build --stats-json` writes `browser-stats.json` (Angular 18 and later)
|
|
12
|
+
* or `stats.json` (the esbuild builder in 17, and every webpack build) into
|
|
13
|
+
* the output folder. The folder comes from the build target's `outputPath`,
|
|
14
|
+
* or `dist/<project>` as the CLI defaults to; without targets, `dist` is
|
|
15
|
+
* searched two levels deep. Nothing is built: a project that was never built
|
|
16
|
+
* with `--stats-json` simply has no entry, and a stats file that does not
|
|
17
|
+
* parse is skipped rather than failing the run.
|
|
18
|
+
*/
|
|
19
|
+
export function readBundleStats(workspace) {
|
|
20
|
+
const builds = workspace.targets.filter((target) => target.target === 'build' && APPLICATION_BUILDERS.test(target.builder));
|
|
21
|
+
const candidates = builds.map((target) => ({ project: target.project, directory: outputDirectory(workspace.root, target), target }));
|
|
22
|
+
if (candidates.length === 0) {
|
|
23
|
+
for (const directory of subdirectories(join(workspace.root, 'dist'), 2))
|
|
24
|
+
candidates.push({ project: relative(join(workspace.root, 'dist'), directory).split(/[\\/]/).join('/'), directory });
|
|
25
|
+
}
|
|
26
|
+
const results = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const { project, directory, target } of candidates) {
|
|
29
|
+
const path = ['browser-stats.json', 'stats.json'].map((name) => join(directory, name)).find((candidate) => existsSync(candidate));
|
|
30
|
+
if (!path || seen.has(path))
|
|
31
|
+
continue;
|
|
32
|
+
seen.add(path);
|
|
33
|
+
try {
|
|
34
|
+
const parsed = parseStats(JSON.parse(readFileSync(path, 'utf8')));
|
|
35
|
+
if (!parsed)
|
|
36
|
+
continue;
|
|
37
|
+
results.push({ project, file: relative(workspace.root, path).split(/[\\/]/).join('/'), builtAt: statSync(path).mtime.toISOString(), budget: target ? readInitialBudget(target) : DEFAULT_BUDGET, ...parsed });
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Unreadable or truncated (a build still writing it): no entry.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
/** Reads either format; undefined for JSON that is neither. Exported for tests. */
|
|
46
|
+
export function parseStats(json) {
|
|
47
|
+
const record = asRecord(json);
|
|
48
|
+
if (typeof record['outputs'] === 'object' && record['outputs'] !== null)
|
|
49
|
+
return parseEsbuildMetafile(asRecord(record['outputs']));
|
|
50
|
+
if (Array.isArray(record['assets']) && Array.isArray(record['chunks']))
|
|
51
|
+
return parseWebpackStats(record);
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* An esbuild metafile, as the Angular application builder writes it.
|
|
56
|
+
*
|
|
57
|
+
* The initial set is computed the way the CLI's "Initial total" is: every
|
|
58
|
+
* output with an entry point that no other output loads through a dynamic
|
|
59
|
+
* `import()`, plus everything those reach through static imports. Lazy
|
|
60
|
+
* routes are entry points too in this format, which is why "has an entry
|
|
61
|
+
* point" alone would count them as initial. Component CSS files without an
|
|
62
|
+
* entry point are inlined by Angular and not loaded as files, so they are
|
|
63
|
+
* left out like the CLI leaves them out.
|
|
64
|
+
*/
|
|
65
|
+
function parseEsbuildMetafile(outputs) {
|
|
66
|
+
const files = new Map();
|
|
67
|
+
for (const [name, value] of Object.entries(outputs)) {
|
|
68
|
+
if (name.endsWith('.map'))
|
|
69
|
+
continue;
|
|
70
|
+
const output = asRecord(value);
|
|
71
|
+
const imports = (Array.isArray(output['imports']) ? output['imports'] : []).map((entry) => asRecord(entry)).map((entry) => ({ path: String(entry['path']), kind: String(entry['kind']) }));
|
|
72
|
+
const inputs = Object.fromEntries(Object.entries(asRecord(output['inputs'])).map(([input, data]) => [input, Number(asRecord(data)['bytesInOutput']) || 0]));
|
|
73
|
+
files.set(name, { bytes: Number(output['bytes']) || 0, entryPoint: typeof output['entryPoint'] === 'string' ? output['entryPoint'] : undefined, imports, inputs });
|
|
74
|
+
}
|
|
75
|
+
const dynamicTargets = new Set([...files.values()].flatMap((file) => file.imports.filter((entry) => entry.kind === 'dynamic-import').map((entry) => entry.path)));
|
|
76
|
+
const initial = new Set();
|
|
77
|
+
const queue = [...files.entries()].filter(([name, file]) => file.entryPoint !== undefined && !dynamicTargets.has(name)).map(([name]) => name);
|
|
78
|
+
while (queue.length > 0) {
|
|
79
|
+
const name = queue.pop();
|
|
80
|
+
if (initial.has(name) || !files.has(name))
|
|
81
|
+
continue;
|
|
82
|
+
initial.add(name);
|
|
83
|
+
for (const entry of files.get(name).imports)
|
|
84
|
+
if (entry.kind === 'import-statement')
|
|
85
|
+
queue.push(entry.path);
|
|
86
|
+
}
|
|
87
|
+
const lazy = [...files.entries()].filter(([name]) => !initial.has(name) && name.endsWith('.js'));
|
|
88
|
+
const byPackage = new Map();
|
|
89
|
+
for (const name of initial) {
|
|
90
|
+
for (const [input, bytes] of Object.entries(files.get(name).inputs)) {
|
|
91
|
+
const owner = packageOf(input);
|
|
92
|
+
if (owner)
|
|
93
|
+
byPackage.set(owner, (byPackage.get(owner) ?? 0) + bytes);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return summarise('esbuild', [...initial].reduce((sum, name) => sum + files.get(name).bytes, 0), lazy.map(([file, data]) => ({ file, bytes: data.bytes })), byPackage);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Webpack stats (`--stats-json` with the browser builder, Angular 16 and
|
|
100
|
+
* earlier). Chunks marked `initial` hold the initial files; every other
|
|
101
|
+
* `.js` asset is lazy. Packages come from `modules` when the stats include
|
|
102
|
+
* them, which Angular's do.
|
|
103
|
+
*/
|
|
104
|
+
function parseWebpackStats(stats) {
|
|
105
|
+
const assets = new Map(stats['assets'].map((entry) => asRecord(entry)).map((asset) => [String(asset['name']), Number(asset['size']) || 0]));
|
|
106
|
+
const chunks = stats['chunks'].map((entry) => asRecord(entry));
|
|
107
|
+
const initialChunkIds = new Set(chunks.filter((chunk) => chunk['initial'] === true).map((chunk) => String(chunk['id'])));
|
|
108
|
+
const initialFiles = new Set(chunks.filter((chunk) => chunk['initial'] === true).flatMap((chunk) => (Array.isArray(chunk['files']) ? chunk['files'].map(String) : [])).filter((file) => /\.(js|css)$/.test(file)));
|
|
109
|
+
const lazy = [...assets.entries()].filter(([name]) => name.endsWith('.js') && !initialFiles.has(name)).map(([file, bytes]) => ({ file, bytes }));
|
|
110
|
+
const byPackage = new Map();
|
|
111
|
+
for (const module of Array.isArray(stats['modules']) ? stats['modules'].map((entry) => asRecord(entry)) : []) {
|
|
112
|
+
const inInitial = Array.isArray(module['chunks']) && module['chunks'].some((id) => initialChunkIds.has(String(id)));
|
|
113
|
+
const owner = inInitial ? packageOf(String(module['name'] ?? '')) : undefined;
|
|
114
|
+
if (owner)
|
|
115
|
+
byPackage.set(owner, (byPackage.get(owner) ?? 0) + (Number(module['size']) || 0));
|
|
116
|
+
}
|
|
117
|
+
return summarise('webpack', [...initialFiles].reduce((sum, file) => sum + (assets.get(file) ?? 0), 0), lazy, byPackage);
|
|
118
|
+
}
|
|
119
|
+
/** The shared tail of both parsers. */
|
|
120
|
+
function summarise(format, initialBytes, lazy, byPackage) {
|
|
121
|
+
const largestLazy = [...lazy].sort((left, right) => right.bytes - left.bytes)[0];
|
|
122
|
+
return {
|
|
123
|
+
format,
|
|
124
|
+
initialBytes,
|
|
125
|
+
lazyChunks: lazy.length,
|
|
126
|
+
lazyBytes: lazy.reduce((sum, file) => sum + file.bytes, 0),
|
|
127
|
+
...(largestLazy ? { largestLazy } : {}),
|
|
128
|
+
initialByPackage: [...byPackage.entries()].map(([name, bytes]) => ({ name, bytes })).filter((entry) => entry.bytes > 0).sort((left, right) => right.bytes - left.bytes),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The npm package an input path belongs to (`@angular/core`, `rxjs`), or
|
|
133
|
+
* `(your code)` for anything outside `node_modules`. Undefined for the
|
|
134
|
+
* builder's virtual inputs (`angular:…`, `<runtime>`), which no one can act on.
|
|
135
|
+
*/
|
|
136
|
+
function packageOf(input) {
|
|
137
|
+
const path = input.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
138
|
+
const index = path.lastIndexOf('node_modules/');
|
|
139
|
+
if (index >= 0) {
|
|
140
|
+
const parts = path.slice(index + 'node_modules/'.length).split('/');
|
|
141
|
+
return parts[0]?.startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
142
|
+
}
|
|
143
|
+
if (path.startsWith('angular:') || path.startsWith('<') || path === '')
|
|
144
|
+
return undefined;
|
|
145
|
+
return '(your code)';
|
|
146
|
+
}
|
|
147
|
+
/** The build output folder: `outputPath` as a string or `{ base }`, else the CLI default `dist/<project>`. */
|
|
148
|
+
function outputDirectory(root, target) {
|
|
149
|
+
const option = target.options['outputPath'];
|
|
150
|
+
const base = typeof option === 'string' ? option : typeof asRecord(option)['base'] === 'string' ? asRecord(option)['base'] : `dist/${target.project}`;
|
|
151
|
+
return resolve(target.baseDirectory || root, base);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The `initial` budget of the production configuration. Angular reads
|
|
155
|
+
* `kb` and `mb` as binary units (1 kB = 1024 bytes), so this does too.
|
|
156
|
+
* Absent when the target has no production configuration or no `initial`
|
|
157
|
+
* budget; `DEFAULT_BUDGET` then applies.
|
|
158
|
+
*/
|
|
159
|
+
function readInitialBudget(target) {
|
|
160
|
+
const budgets = asRecord(asRecord(target.configurations)['production'])['budgets'];
|
|
161
|
+
const initial = Array.isArray(budgets) ? budgets.map((entry) => asRecord(entry)).find((budget) => budget['type'] === 'initial') : undefined;
|
|
162
|
+
if (!initial)
|
|
163
|
+
return DEFAULT_BUDGET;
|
|
164
|
+
return { warning: parseSize(initial['maximumWarning']), error: parseSize(initial['maximumError']) };
|
|
165
|
+
}
|
|
166
|
+
/** `"500kB"` → 512000, `"1mb"` → 1048576, `"300"` → 300 bytes. Undefined for anything else. */
|
|
167
|
+
export function parseSize(value) {
|
|
168
|
+
if (typeof value !== 'string')
|
|
169
|
+
return undefined;
|
|
170
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?\s*$/i.exec(value);
|
|
171
|
+
if (!match)
|
|
172
|
+
return undefined;
|
|
173
|
+
const factor = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 }[(match[2] ?? 'b').toLowerCase()];
|
|
174
|
+
return Math.round(Number(match[1]) * factor);
|
|
175
|
+
}
|
|
176
|
+
/** Directories under `directory` down to `depth` levels, deepest last. Empty when it does not exist. */
|
|
177
|
+
function subdirectories(directory, depth) {
|
|
178
|
+
if (depth === 0 || !existsSync(directory))
|
|
179
|
+
return [];
|
|
180
|
+
const found = [];
|
|
181
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
182
|
+
if (!entry.isDirectory())
|
|
183
|
+
continue;
|
|
184
|
+
const path = join(directory, entry.name);
|
|
185
|
+
found.push(path, ...subdirectories(path, depth - 1));
|
|
186
|
+
}
|
|
187
|
+
return found;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PackageCompatibility } from './compatibility.js';
|
|
2
|
+
import type { DeprecationIndex } from './deprecations.js';
|
|
3
|
+
import type { SourceFileFacts, TemplateFact } from './scan.js';
|
|
4
|
+
/**
|
|
5
|
+
* Results that can be reused between runs on the same repository, for
|
|
6
|
+
* `--watch`. Passed in through `CheckupOptions.cache` and filled by
|
|
7
|
+
* `buildContext`; a single run without one behaves exactly as before.
|
|
8
|
+
*
|
|
9
|
+
* - Per file: the scanned facts, kept while the file's mtime and size are
|
|
10
|
+
* unchanged. Parsing is most of a run, so an edit to one file re-parses one.
|
|
11
|
+
* - Per dependency set: the deprecation index, the registry lookups and the
|
|
12
|
+
* latest Angular version, kept until `package.json`, a lockfile or
|
|
13
|
+
* `node_modules` changes. Re-fetching the registry on every keystroke would
|
|
14
|
+
* be slow and impolite.
|
|
15
|
+
*/
|
|
16
|
+
export interface AnalysisCache {
|
|
17
|
+
files: Map<string, {
|
|
18
|
+
stamp: string;
|
|
19
|
+
facts: SourceFileFacts | TemplateFact;
|
|
20
|
+
}>;
|
|
21
|
+
dependencies?: {
|
|
22
|
+
stamp: string;
|
|
23
|
+
deprecations: DeprecationIndex;
|
|
24
|
+
compatibility: PackageCompatibility[];
|
|
25
|
+
latestAngular: {
|
|
26
|
+
major: number;
|
|
27
|
+
source: 'registry' | 'bundled';
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** An empty cache. */
|
|
32
|
+
export declare function createAnalysisCache(): AnalysisCache;
|
|
33
|
+
/** `mtime:size` of a file, or an empty string when it cannot be read. */
|
|
34
|
+
export declare function fileStamp(path: string): string;
|
|
35
|
+
/** The stamp of everything that decides the installed dependencies. */
|
|
36
|
+
export declare function dependencyStamp(root: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* The cached value for `path` when its stamp still matches, else `compute()`
|
|
39
|
+
* stored under the new stamp. Without a cache it just computes.
|
|
40
|
+
*/
|
|
41
|
+
export declare function cachedFacts<T extends SourceFileFacts | TemplateFact>(cache: AnalysisCache | undefined, path: string, compute: () => T): T;
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** An empty cache. */
|
|
4
|
+
export function createAnalysisCache() {
|
|
5
|
+
return { files: new Map() };
|
|
6
|
+
}
|
|
7
|
+
/** `mtime:size` of a file, or an empty string when it cannot be read. */
|
|
8
|
+
export function fileStamp(path) {
|
|
9
|
+
try {
|
|
10
|
+
const stats = statSync(path);
|
|
11
|
+
return `${stats.mtimeMs}:${stats.size}`;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** The stamp of everything that decides the installed dependencies. */
|
|
18
|
+
export function dependencyStamp(root) {
|
|
19
|
+
return ['package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'node_modules/.package-lock.json'].map((name) => fileStamp(join(root, name))).join('|');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The cached value for `path` when its stamp still matches, else `compute()`
|
|
23
|
+
* stored under the new stamp. Without a cache it just computes.
|
|
24
|
+
*/
|
|
25
|
+
export function cachedFacts(cache, path, compute) {
|
|
26
|
+
if (!cache)
|
|
27
|
+
return compute();
|
|
28
|
+
const stamp = fileStamp(path);
|
|
29
|
+
const hit = cache.files.get(path);
|
|
30
|
+
if (hit && hit.stamp === stamp)
|
|
31
|
+
return hit.facts;
|
|
32
|
+
const facts = compute();
|
|
33
|
+
cache.files.set(path, { stamp, facts });
|
|
34
|
+
return facts;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +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;AA2BjC,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,CAA2C,KAAgC,EAAE,IAAY,EAAE,OAAgB;IACpI,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,IAAI,CAAC,CAAC;IAClC,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,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CheckResult } from '../types.js';
|
|
2
|
+
import { type AnalysisContext } from './context.js';
|
|
3
|
+
/**
|
|
4
|
+
* How the code is put together: lazy loading, oversized files, and the
|
|
5
|
+
* dependency-injection and guard style.
|
|
6
|
+
*
|
|
7
|
+
* Graded only on what makes an app slower to load or harder to change:
|
|
8
|
+
* routes that load everything up front, and files past a thousand lines,
|
|
9
|
+
* where every change touches code nobody fully reads any more. Constructor
|
|
10
|
+
* injection and class-based guards are reported but never grade, because
|
|
11
|
+
* they are a matter of style that works fine; `inject()` and functional
|
|
12
|
+
* guards are the current default and what the Angular migrations produce,
|
|
13
|
+
* which is all the findings say.
|
|
14
|
+
*
|
|
15
|
+
* Routes are recognised syntactically (object literals with `path` and
|
|
16
|
+
* `component` or `loadComponent`/`loadChildren`), so routes built by a
|
|
17
|
+
* helper function are not seen. A `component:` route inside a lazily loaded
|
|
18
|
+
* module is lazy, and is counted so: only files statically reachable from
|
|
19
|
+
* `main.ts` hold eager routes (`AnalysisContext.eagerFiles`).
|
|
20
|
+
*/
|
|
21
|
+
export declare function architectureCheck(context: AnalysisContext): CheckResult;
|