ng-checkup 0.3.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 +116 -10
- package/dist/angular-versions.d.ts +14 -0
- package/dist/angular-versions.js +157 -0
- package/dist/angular-versions.js.map +1 -0
- 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 +12 -0
- package/dist/cli.js +108 -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 +4 -0
- package/dist/config.js +10 -3
- 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 +15 -4
- package/dist/index.js +141 -13
- package/dist/index.js.map +1 -1
- package/dist/plan.d.ts +70 -0
- package/dist/plan.js +219 -0
- package/dist/plan.js.map +1 -0
- package/dist/report/html.js +63 -1
- package/dist/report/html.js.map +1 -1
- package/dist/report/markdown.js +29 -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 +39 -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 +37 -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,6 +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)
|
|
26
|
+
- [Upgrade plan](#upgrade-plan)
|
|
27
|
+
- [Monorepos](#monorepos)
|
|
25
28
|
- [What it checks](#what-it-checks)
|
|
26
29
|
- [Reports](#reports)
|
|
27
30
|
- [In CI](#in-ci)
|
|
@@ -100,6 +103,7 @@ npx ng-checkup init --ci gitlab # pick the CI instead of detecting it (github
|
|
|
100
103
|
| `offline` | skip the npm lookup of the latest Angular version | `--offline` |
|
|
101
104
|
| `ignore` | paths left out of the analysis; a path matches everything below it, `*` stays within a folder, `**` crosses folders | |
|
|
102
105
|
| `baseline` | the baseline file, relative to the config file (default `ng-checkup.baseline.json` in the root) | |
|
|
106
|
+
| `plan` | add the upgrade plan to every run | `--plan` |
|
|
103
107
|
|
|
104
108
|
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.
|
|
105
109
|
|
|
@@ -118,25 +122,95 @@ Baseline 2 worse
|
|
|
118
122
|
worse Type safety: Non-null assertions (x!) 0 → 1
|
|
119
123
|
```
|
|
120
124
|
|
|
121
|
-
- **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.
|
|
122
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.
|
|
123
127
|
- **It is a plain, sorted JSON file**, so lowering a number is a one-line diff in a merge request.
|
|
124
128
|
- **The gap to the latest Angular is not in it.** That grows when Angular ships, not when the code gets worse.
|
|
125
129
|
|
|
126
130
|
`ng add ng-checkup` and `npx ng-checkup init` create the first baseline and set CI to `--fail-on regression` for you.
|
|
127
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
|
+
|
|
148
|
+
## Upgrade plan
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx ng-checkup --plan --report plan.md
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`--plan` adds the way from your Angular version to the latest, one major at a time as `ng update` requires:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
Upgrade plan 12 → 22, 10 steps, one major at a time
|
|
158
|
+
Before you start
|
|
159
|
+
· Move from TSLint to ESLint ng add @angular-eslint/schematics
|
|
160
|
+
→ 13 ng update @angular/core@13 @angular/cli@13
|
|
161
|
+
Node.js ^12.20.0 || ^14.15.0 || >=16.10.0 · TypeScript >=4.4.2 <4.7 (now 4.3.5)
|
|
162
|
+
...
|
|
163
|
+
→ 16 ng update @angular/core@16 @angular/cli@16
|
|
164
|
+
Node.js ^16.14.0 || >=18.10.0 · TypeScript >=4.9.3 <5.2 · zone.js ~0.13.0 (now 0.11.8)
|
|
165
|
+
· Convert NgModule components to standalone (18) ng generate @angular/core:standalone
|
|
166
|
+
→ 17 ng update @angular/core@17 @angular/cli@17
|
|
167
|
+
· Move templates to built-in control flow (13) ng generate @angular/core:control-flow
|
|
168
|
+
...
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- **The same for everyone:** each step's `ng update` command (with Material and NgRx when you use them) and the Node.js, TypeScript, RxJS and zone.js ranges that major requires, from the versions' published package metadata. A range your project is outside of today is flagged at the step that has to move it.
|
|
172
|
+
- **Specific to your code:** the migrations your findings call for (standalone, built-in control flow, the esbuild builder, signal inputs, Vitest, zoneless), each scheduled at the first step where Angular ships it, with the number of places it touches. Migrations your code does not need are left out.
|
|
173
|
+
- **No time estimate.** Counts are measured; days would be a guess.
|
|
174
|
+
|
|
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`).
|
|
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
|
+
|
|
128
198
|
## What it checks
|
|
129
199
|
|
|
130
200
|
| Area | It looks at | Poor when |
|
|
131
201
|
| --- | --- | --- |
|
|
132
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 |
|
|
133
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 |
|
|
134
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 |
|
|
135
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% |
|
|
136
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 |
|
|
137
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 |
|
|
138
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` |
|
|
139
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) |
|
|
140
214
|
|
|
141
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)**
|
|
142
216
|
|
|
@@ -153,6 +227,7 @@ Defaults that changed between versions are applied per version: a component with
|
|
|
153
227
|
| `npx ng-checkup --report checkup.html` | self-contained page: light and dark mode, prints cleanly to PDF, loads nothing from the network |
|
|
154
228
|
| `npx ng-checkup --report checkup.md` | Markdown for a pull request, an issue or a wiki |
|
|
155
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 |
|
|
156
231
|
| `npx ng-checkup --json` | the JSON on stdout, for piping |
|
|
157
232
|
|
|
158
233
|
`--report` is repeatable, so one run can write all three files.
|
|
@@ -170,9 +245,17 @@ Arguments:
|
|
|
170
245
|
|
|
171
246
|
Options:
|
|
172
247
|
-r, --report <file> Write the full report; format from the extension:
|
|
173
|
-
.html, .md or .
|
|
248
|
+
.html, .md, .json or .sarif (GitHub code scanning).
|
|
249
|
+
Repeatable.
|
|
174
250
|
--json Print the report as JSON to stdout instead of the summary
|
|
175
251
|
-v, --verbose Print every finding with its files
|
|
252
|
+
--plan Add an upgrade plan to the latest Angular, one major
|
|
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
|
|
176
259
|
--fail-on <grade> Exit with code 1 if any area is graded <grade> or worse
|
|
177
260
|
(poor or fair), or with regression if a measure is
|
|
178
261
|
worse than the baseline (for CI)
|
|
@@ -228,6 +311,26 @@ ng-checkup:
|
|
|
228
311
|
paths: [checkup.md, checkup.html]
|
|
229
312
|
```
|
|
230
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
|
+
|
|
231
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.
|
|
232
335
|
|
|
233
336
|
## Programmatic API
|
|
@@ -247,11 +350,11 @@ for (const check of report.checks) {
|
|
|
247
350
|
await writeFile('checkup.md', renderMarkdown(report));
|
|
248
351
|
```
|
|
249
352
|
|
|
250
|
-
`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.
|
|
251
354
|
|
|
252
355
|
## What leaves your machine
|
|
253
356
|
|
|
254
|
-
|
|
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.
|
|
255
358
|
|
|
256
359
|
## How it works
|
|
257
360
|
|
|
@@ -269,16 +372,19 @@ One request: the latest `@angular/core` version from the npm registry, with a th
|
|
|
269
372
|
|
|
270
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.
|
|
271
374
|
|
|
272
|
-
**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.
|
|
273
380
|
|
|
274
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.
|
|
275
382
|
|
|
276
383
|
## Roadmap
|
|
277
384
|
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
-
|
|
281
|
-
- 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
|
|
282
388
|
|
|
283
389
|
Ideas and bug reports are welcome in [issues](https://github.com/stefankuburovic/ng-checkup/issues).
|
|
284
390
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** What one Angular major requires, from the npm registry. A null field was not published for that major. */
|
|
2
|
+
export interface AngularVersionInfo {
|
|
3
|
+
major: number;
|
|
4
|
+
/** The last released version of the major when the snapshot was taken. */
|
|
5
|
+
version: string;
|
|
6
|
+
/** Node.js range from @angular/core's `engines`. */
|
|
7
|
+
node: string | null;
|
|
8
|
+
/** TypeScript range from @angular/compiler-cli's peer dependency. */
|
|
9
|
+
typescript: string | null;
|
|
10
|
+
rxjs: string | null;
|
|
11
|
+
zoneJs: string | null;
|
|
12
|
+
}
|
|
13
|
+
/** Every Angular major from 4 to the newest, oldest first. */
|
|
14
|
+
export declare const ANGULAR_VERSIONS: AngularVersionInfo[];
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Generated by scripts/update-angular-versions.mjs on 2026-10-20. Do not edit by hand.
|
|
2
|
+
/** Every Angular major from 4 to the newest, oldest first. */
|
|
3
|
+
export const ANGULAR_VERSIONS = [
|
|
4
|
+
{
|
|
5
|
+
"major": 4,
|
|
6
|
+
"version": "4.4.7",
|
|
7
|
+
"node": null,
|
|
8
|
+
"typescript": "^2.0.2",
|
|
9
|
+
"rxjs": "^5.0.1",
|
|
10
|
+
"zoneJs": "^0.8.4"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"major": 5,
|
|
14
|
+
"version": "5.2.11",
|
|
15
|
+
"node": null,
|
|
16
|
+
"typescript": ">=2.4.2 <2.7",
|
|
17
|
+
"rxjs": "^5.5.0",
|
|
18
|
+
"zoneJs": "^0.8.4"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"major": 6,
|
|
22
|
+
"version": "6.1.10",
|
|
23
|
+
"node": null,
|
|
24
|
+
"typescript": ">=2.7.2 <2.10",
|
|
25
|
+
"rxjs": "^6.0.0",
|
|
26
|
+
"zoneJs": "~0.8.26"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"major": 7,
|
|
30
|
+
"version": "7.2.15",
|
|
31
|
+
"node": null,
|
|
32
|
+
"typescript": ">=3.1.1 <3.3",
|
|
33
|
+
"rxjs": "^6.0.0",
|
|
34
|
+
"zoneJs": "~0.8.26"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"major": 8,
|
|
38
|
+
"version": "8.2.14",
|
|
39
|
+
"node": null,
|
|
40
|
+
"typescript": ">=3.4 <3.6",
|
|
41
|
+
"rxjs": "^6.4.0",
|
|
42
|
+
"zoneJs": "~0.9.1"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"major": 9,
|
|
46
|
+
"version": "9.1.13",
|
|
47
|
+
"node": null,
|
|
48
|
+
"typescript": ">=3.6 <3.9",
|
|
49
|
+
"rxjs": "^6.5.3",
|
|
50
|
+
"zoneJs": "~0.10.3"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"major": 10,
|
|
54
|
+
"version": "10.2.5",
|
|
55
|
+
"node": null,
|
|
56
|
+
"typescript": ">=3.9 <4.1",
|
|
57
|
+
"rxjs": "^6.5.3",
|
|
58
|
+
"zoneJs": "~0.10.3"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"major": 11,
|
|
62
|
+
"version": "11.2.14",
|
|
63
|
+
"node": null,
|
|
64
|
+
"typescript": ">=4.0 <4.2",
|
|
65
|
+
"rxjs": "^6.5.3",
|
|
66
|
+
"zoneJs": "^0.10.2 || ^0.11.3"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"major": 12,
|
|
70
|
+
"version": "12.2.17",
|
|
71
|
+
"node": "^12.14.1 || >=14.0.0",
|
|
72
|
+
"typescript": ">=4.2.3 <4.4",
|
|
73
|
+
"rxjs": "^6.5.3 || ^7.0.0",
|
|
74
|
+
"zoneJs": "~0.11.4"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"major": 13,
|
|
78
|
+
"version": "13.4.0",
|
|
79
|
+
"node": "^12.20.0 || ^14.15.0 || >=16.10.0",
|
|
80
|
+
"typescript": ">=4.4.2 <4.7",
|
|
81
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
82
|
+
"zoneJs": "~0.11.4"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"major": 14,
|
|
86
|
+
"version": "14.3.0",
|
|
87
|
+
"node": "^14.15.0 || >=16.10.0",
|
|
88
|
+
"typescript": ">=4.6.2 <4.9",
|
|
89
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
90
|
+
"zoneJs": "~0.11.4 || ~0.12.0"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"major": 15,
|
|
94
|
+
"version": "15.2.10",
|
|
95
|
+
"node": "^14.20.0 || ^16.13.0 || >=18.10.0",
|
|
96
|
+
"typescript": ">=4.8.2 <5.0",
|
|
97
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
98
|
+
"zoneJs": "~0.11.4 || ~0.12.0 || ~0.13.0"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"major": 16,
|
|
102
|
+
"version": "16.2.12",
|
|
103
|
+
"node": "^16.14.0 || >=18.10.0",
|
|
104
|
+
"typescript": ">=4.9.3 <5.2",
|
|
105
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
106
|
+
"zoneJs": "~0.13.0"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"major": 17,
|
|
110
|
+
"version": "17.3.12",
|
|
111
|
+
"node": "^18.13.0 || >=20.9.0",
|
|
112
|
+
"typescript": ">=5.2 <5.5",
|
|
113
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
114
|
+
"zoneJs": "~0.14.0"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"major": 18,
|
|
118
|
+
"version": "18.2.14",
|
|
119
|
+
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
|
|
120
|
+
"typescript": ">=5.4 <5.6",
|
|
121
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
122
|
+
"zoneJs": "~0.14.10"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"major": 19,
|
|
126
|
+
"version": "19.2.25",
|
|
127
|
+
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
|
|
128
|
+
"typescript": ">=5.5 <5.9",
|
|
129
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
130
|
+
"zoneJs": "~0.15.0"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"major": 20,
|
|
134
|
+
"version": "20.3.34",
|
|
135
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
136
|
+
"typescript": ">=5.8 <6.0",
|
|
137
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
138
|
+
"zoneJs": "~0.15.0"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"major": 21,
|
|
142
|
+
"version": "21.2.25",
|
|
143
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
144
|
+
"typescript": ">=5.9 <6.1",
|
|
145
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
146
|
+
"zoneJs": "~0.15.0 || ~0.16.0"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"major": 22,
|
|
150
|
+
"version": "22.2.3",
|
|
151
|
+
"node": "^22.22.3 || ^24.15.0 || >=26.0.0",
|
|
152
|
+
"typescript": ">=6.0 <6.1",
|
|
153
|
+
"rxjs": "^6.5.3 || ^7.4.0",
|
|
154
|
+
"zoneJs": "~0.15.0 || ~0.16.0"
|
|
155
|
+
}
|
|
156
|
+
];
|
|
157
|
+
//# sourceMappingURL=angular-versions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-versions.js","sourceRoot":"","sources":["../src/angular-versions.ts"],"names":[],"mappings":"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"}
|
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
|