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.
Files changed (66) hide show
  1. package/README.md +83 -9
  2. package/dist/bundle.d.ts +58 -0
  3. package/dist/bundle.js +189 -0
  4. package/dist/bundle.js.map +1 -0
  5. package/dist/cache.d.ts +41 -0
  6. package/dist/cache.js +36 -0
  7. package/dist/cache.js.map +1 -0
  8. package/dist/checks/architecture.d.ts +21 -0
  9. package/dist/checks/architecture.js +108 -0
  10. package/dist/checks/architecture.js.map +1 -0
  11. package/dist/checks/bundle.d.ts +24 -0
  12. package/dist/checks/bundle.js +112 -0
  13. package/dist/checks/bundle.js.map +1 -0
  14. package/dist/checks/context.d.ts +13 -0
  15. package/dist/checks/context.js.map +1 -1
  16. package/dist/checks/dependencies.d.ts +18 -0
  17. package/dist/checks/dependencies.js +89 -0
  18. package/dist/checks/dependencies.js.map +1 -0
  19. package/dist/checks/deprecated-apis.js +23 -3
  20. package/dist/checks/deprecated-apis.js.map +1 -1
  21. package/dist/checks/security.d.ts +17 -0
  22. package/dist/checks/security.js +87 -0
  23. package/dist/checks/security.js.map +1 -0
  24. package/dist/cli.d.ts +10 -0
  25. package/dist/cli.js +102 -14
  26. package/dist/cli.js.map +1 -1
  27. package/dist/compatibility.d.ts +43 -0
  28. package/dist/compatibility.js +131 -0
  29. package/dist/compatibility.js.map +1 -0
  30. package/dist/config.d.ts +2 -0
  31. package/dist/config.js +4 -2
  32. package/dist/config.js.map +1 -1
  33. package/dist/deprecations.js +21 -8
  34. package/dist/deprecations.js.map +1 -1
  35. package/dist/graph.d.ts +20 -0
  36. package/dist/graph.js +30 -0
  37. package/dist/graph.js.map +1 -1
  38. package/dist/index.d.ts +13 -4
  39. package/dist/index.js +137 -14
  40. package/dist/index.js.map +1 -1
  41. package/dist/plan.d.ts +2 -1
  42. package/dist/plan.js +37 -1
  43. package/dist/plan.js.map +1 -1
  44. package/dist/report/html.js +32 -1
  45. package/dist/report/html.js.map +1 -1
  46. package/dist/report/markdown.js +11 -1
  47. package/dist/report/markdown.js.map +1 -1
  48. package/dist/report/sarif.d.ts +20 -0
  49. package/dist/report/sarif.js +83 -0
  50. package/dist/report/sarif.js.map +1 -0
  51. package/dist/report/summary.d.ts +11 -2
  52. package/dist/report/summary.js +12 -0
  53. package/dist/report/summary.js.map +1 -1
  54. package/dist/report/terminal.js +20 -1
  55. package/dist/report/terminal.js.map +1 -1
  56. package/dist/scan.d.ts +43 -0
  57. package/dist/scan.js +60 -0
  58. package/dist/scan.js.map +1 -1
  59. package/dist/types.d.ts +33 -0
  60. package/dist/watch.d.ts +47 -0
  61. package/dist/watch.js +107 -0
  62. package/dist/watch.js.map +1 -0
  63. package/dist/workspace.d.ts +41 -0
  64. package/dist/workspace.js +86 -3
  65. package/dist/workspace.js.map +1 -1
  66. 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. Eight areas get a grade, every finding names the files, and the HTML report is one self-contained page you can hand to your team lead.
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 [18 more](https://github.com/stefankuburovic/ng-checkup/blob/main/docs/checks.md#measures). Lower is better for every one.
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 .json. Repeatable.
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
- One request: the latest `@angular/core` version from the npm registry, with a three-second timeout. `--offline` skips it and compares against the newest major this release knows, and the report says so. No source, no file names, no telemetry. The tool reads files and writes only the reports you ask for.
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`. A workspace with several apps is reported as one codebase for now.
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 reports for Nx and multi-project workspaces
312
- - Bundle size from the build's esbuild metafile
313
- - SARIF output for GitHub code scanning
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
 
@@ -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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"}
@@ -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;