rolldown-metric-lab 0.0.0-stage → 0.1.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 (43) hide show
  1. package/AGENTS.md +323 -0
  2. package/LICENSE +25 -0
  3. package/README.md +617 -2
  4. package/harness.mjs +4704 -0
  5. package/lib/build.mjs +53 -0
  6. package/lib/cdp.mjs +214 -0
  7. package/lib/chunk-discovery.mjs +226 -0
  8. package/lib/coverage.mjs +498 -0
  9. package/lib/field/analysis.mjs +225 -0
  10. package/lib/field/browser.mjs +232 -0
  11. package/lib/field/build-match.mjs +86 -0
  12. package/lib/field/capture.mjs +501 -0
  13. package/lib/field/config.mjs +84 -0
  14. package/lib/field/run.mjs +612 -0
  15. package/lib/gen-app.mjs +399 -0
  16. package/lib/graph-diff.mjs +136 -0
  17. package/lib/interrupt.mjs +25 -0
  18. package/lib/measure.mjs +459 -0
  19. package/lib/min-cut.mjs +218 -0
  20. package/lib/module-graph.mjs +219 -0
  21. package/lib/network-evidence.mjs +84 -0
  22. package/lib/node/launch.mjs +351 -0
  23. package/lib/node/measure.mjs +289 -0
  24. package/lib/paint-diagnostics.mjs +76 -0
  25. package/lib/parity/anchor.mjs +186 -0
  26. package/lib/parity/capture.mjs +527 -0
  27. package/lib/parity/compare.mjs +813 -0
  28. package/lib/parity/png.mjs +166 -0
  29. package/lib/parity/run.mjs +145 -0
  30. package/lib/profile.mjs +281 -0
  31. package/lib/replay/cert.mjs +45 -0
  32. package/lib/replay/model.mjs +656 -0
  33. package/lib/replay/run.mjs +1140 -0
  34. package/lib/replay/server.mjs +499 -0
  35. package/lib/replay/shaper.mjs +158 -0
  36. package/lib/rolldown-support.mjs +163 -0
  37. package/lib/serve.mjs +142 -0
  38. package/lib/worker/attrib.mjs +125 -0
  39. package/lib/worker/config.mjs +119 -0
  40. package/lib/worker/launch.mjs +337 -0
  41. package/lib/worker/measure.mjs +296 -0
  42. package/package.json +38 -4
  43. package/replay.mjs +79 -0
package/AGENTS.md ADDED
@@ -0,0 +1,323 @@
1
+ # metrics-lab — agent contract
2
+
3
+ You are here to make a page load faster and prove it with measurements. This package
4
+ provides the measuring and diagnosis commands; you make the code changes and the
5
+ keep/revert decisions.
6
+
7
+ All commands work from any working directory (`node <this dir>/harness.mjs ...`).
8
+ The first `--app <appDir>` is remembered, so afterwards every command can be run
9
+ bare — no paths. Each target app keeps its own state (baseline, history, reports).
10
+
11
+ ## The two commands that matter
12
+
13
+ - `node harness.mjs scan --app <appDir>` — one browser session that does it all:
14
+ N throttled timed runs (FCP and LCP vs the pinned baseline), first-paint
15
+ coverage, boot CPU profile — then prints the fused verdict. The FIRST scan of a
16
+ target automatically pins the baseline. After a kept change, `scan --pin` re-pins.
17
+ - `node harness.mjs verdict` — re-print the fused OPEN / clear / UNKNOWN checklist
18
+ for the gathered signals, with staleness tracking against the current build.
19
+ **This is the only "done" that counts**: it refuses to conclude while a lead is
20
+ open or a signal is missing/stale, and even its all-clear states what the tools
21
+ cannot see.
22
+
23
+ Individual commands exist too (`measure [--pin]`, `coverage`, `profile`,
24
+ `baseline`, `target [<appDir>] [--demo]`) plus demo-app helpers (README.md).
25
+
26
+ On rolldown devtools-metrics builds (vite ≥ 8:
27
+ `build.rolldownOptions.devtools = { mode: "metrics" }`) several static queries skip the
28
+ import-chain archaeology entirely: `graph` ranks split candidates by **retained size**
29
+ (the dominator subtree a deferral removes from the initial load, with `via` naming the
30
+ one import chain to cut); `what-if <module> [<module>...] [--keep a,b]` lists the exact
31
+ modules and bytes one deferral frees (sentries in `--keep` stay eager) — pass **two or
32
+ more** modules and it prices them as one combined plan against the summed-individually
33
+ total, so shared internals dominated by no single module (a library split across imports,
34
+ sibling routes) show up as combined-exceeds-the-sum; `cut <module>` returns the **fewest
35
+ import statements** to make dynamic to detach a module reached by several paths (the
36
+ last-hop edges nearest it, the exact files to edit — a min edge cut, which dominator
37
+ retained-size can't answer once several paths reach the module; `--keep` edges are
38
+ protected; `--root a,b` keeps class modules eager with their closure so the cut lands
39
+ below them); and `graph-diff [--against <file>]` reports, deterministically and with no
40
+ browser run, what a change did to the initial load (the modules that ENTERED or LEFT the
41
+ eager set with byte deltas) against the pinned baseline graph or any saved sidecar. Rank
42
+ candidates statically with these, then verify the winner's real LCP effect with `scan`.
43
+ `scan`/`verdict` fold the graph in as the **statically retained imports** signal;
44
+ when the app builds with rolldown but the graph was never collected, the verdict
45
+ reports it UNKNOWN with the one config line that enables it — enable it and rebuild
46
+ rather than justifying the gap: it is the cheapest signal in the whole kit (no
47
+ browser run, and it prices every candidate at once).
48
+
49
+ ## If the target is a server, not a page
50
+
51
+ `node-scan --entry <builtServerScript> --ready <spec>` measures a process coming up
52
+ instead of a page loading. The loop below is unchanged — baseline, one change, measure,
53
+ accept or revert — with three differences that decide whether your work is worth anything:
54
+
55
+ - **Declare readiness once and never change it.** `--ready port:3000` /
56
+ `'stdout:listening on'` / `http://127.0.0.1:3000/health` / `exit`. It is pinned with the
57
+ target; two scans that measured different moments are not comparable, the same way two
58
+ browser scans under different net scales are not.
59
+ - **Read the ownership line before doing any work.** `node-scan` reports pre-ready CPU
60
+ split into app modules / runtime+native / engine. When app modules hold a small share,
61
+ the bundle is not the cost — startup is native addon loading, TLS/crypto init, or a
62
+ DB handshake — and deferring imports will not move the number no matter how good the
63
+ deferral is. Say that and stop, rather than shipping edits that cannot help.
64
+ - **Judge small startups by bytes, not milliseconds.** Readiness polling quantizes; when
65
+ `node-scan` prints the resolution note, the millisecond delta is not evidence and the
66
+ cold-at-ready byte count is.
67
+
68
+ `graph`, `what-if`, `cut` and `graph-diff` work exactly as below on a server build —
69
+ a top-level import and a render-blocking import are the same edge in the module graph.
70
+ Bun is not supported (JavaScriptCore, not V8) and edge runtimes can only be analyzed
71
+ statically; do not report a measured number for either.
72
+
73
+ ## If the target is a Cloudflare Worker
74
+
75
+ `worker-scan --app <appDir>` measures the time a fresh isolate spends in the Worker's
76
+ global scope, under the app's own workerd, and attributes it to modules. Build the app
77
+ first with the config `worker-config` writes (the report on every environment, sourcemaps
78
+ on the worker). The loop below is unchanged, with these differences:
79
+
80
+ - **Startup is the global scope.** `worker.app_startup_ms` is the import of the main module
81
+ and its static closure, timed from the host minus an empty-import floor. Isolate boot
82
+ sits outside it. Cloudflare's startup limit counts the same scope; the number to quote
83
+ in a PR comes from `wrangler check startup` on the same build, before and after.
84
+ - **A deferral moves work, it does not delete it.** The first request that needs a deferred
85
+ module pays its load, once per isolate. Pin the routes that matter with `--request` and
86
+ read the "cost MOVED here" line next to the startup gain. The lab does not know the
87
+ route mix; an isolate whose first request needs the module gains nothing.
88
+ - **Durable Object and Workflow classes are roots.** The runtime loads them by name, so their
89
+ modules stay eager with their whole closure. `cut <module> --root <class modules>` lands
90
+ the cut below them; without `--root`, every cut points at the entry's class exports,
91
+ which cannot become dynamic. The scan prints the class names from `wrangler.json`.
92
+ - **Three kinds of edge, three kinds of fix.** `why <module>` names the importers. An importer
93
+ under `node_modules` is upstream: when the library is only used in async code, a lazy shim
94
+ behind a Vite alias is the app-side fix. A module reached only from a synchronous
95
+ constructor or getter cannot be deferred by a dynamic import: say so and move on. A mixed
96
+ `import { type X } from 'm'` keeps a side-effect import of `m` under `verbatimModuleSyntax`:
97
+ change it to `import type`, no dynamic import needed.
98
+ - **Library top-level work is charged by the attribution build.** When the profile leaves
99
+ milliseconds in "(unmapped)" or in zod, run `worker-attrib` on the `METRICS_LAB_ATTRIB=1`
100
+ build: it names the module whose evaluation runs the library code, with the import chain.
101
+ - **The report needs this checkout's rolldown.** The npm release has no devtools metrics mode,
102
+ so with it no build writes `module-graph.json` and `graph`, `what-if`, `cut`, `why` and
103
+ `graph-diff` have nothing to read. `worker-config` names the app's rolldown when it lacks the
104
+ mode; `rolldown-install --app <appDir>` copies the build next to the lab (or `--from <dir>`)
105
+ over the copy the app's vite resolves. Rebuild and re-pin the baseline after that: the
106
+ bundler changed.
107
+ - **Read the two delta lines.** `worker-scan` prints `vs pinned baseline` (every change since
108
+ the pin, added up) and `vs previous scan` (the last change alone). Judge a change by the
109
+ second line, or re-pin with `--pin` after each kept change so the first line means the same.
110
+ - **The pinned routes must answer 2xx.** A 404 or 5xx never ran the handler, so no moved load
111
+ can show on it, and the scan says so. Pin routes the app serves. Do not invent a `.dev.vars`
112
+ to get past a 5xx: without the real variables the first-request numbers stay invalid.
113
+ - **The functional check is the app's own test suite, run the way the repo documents it.** When
114
+ the test pool fails for environment reasons (port exhaustion, `EADDRNOTAVAIL`), run it serially
115
+ or in batches. Never reinstall dependencies inside the loop: that undoes `rolldown-install`,
116
+ and the config shim then refuses to build. Never move or symlink packages inside `node_modules`.
117
+ - **Coverage comes from inside the isolate** and needs Wrangler 4.108.0 or later. When the
118
+ scan reports it unavailable, pass `--tools <dir>` with a newer wrangler for that run, or
119
+ work from the profile and the static graph; do not add the compatibility flags to the app.
120
+ - **Never upload, never deploy, never edit the app's config, variables or bindings to satisfy
121
+ the tool.** A 5xx on the first requests usually means the app needs its `.dev.vars`; startup
122
+ is unaffected, the first-request numbers are not valid until it has them.
123
+
124
+ ## If the lab and production disagree
125
+
126
+ `field --config <file>` measures the DEPLOYED site in the user's own logged-in Chrome
127
+ (README.md, "Field mode"). It does not replace the loop above; it checks where the lab
128
+ cannot see:
129
+
130
+ - **Read the critical path first.** When `after gate` is small and `gate ttfb` is most
131
+ of the LCP, the page waits on the server; only `gate start` (client time before the
132
+ request the LCP waits for) can move with a bundle change. Optimize what delays that
133
+ request, not what renders after it.
134
+ - **Judge by the paired deltas and their 90% intervals,** never by one load or by a
135
+ median of unpaired runs. An interval that crosses 0 is "no measured difference".
136
+ - **Compare `gate - doc`, not only `gate start`,** when the variants come from different
137
+ servers (a preview worker can answer the HTML hundreds of ms slower than production).
138
+ - **Module rows need a build match.** Read `buildMatch` before quoting a module; with no
139
+ match only the chunk-level rows are evidence.
140
+ - Field output contains real account traffic. Keep it where the tool writes it; never
141
+ copy it into a repo, a ticket or a merge request.
142
+
143
+ `record` + `replay` give field-like numbers for a build that is NOT deployed (README.md,
144
+ "Replay"):
145
+
146
+ - **Calibrate before any A/B.** `replay --calibrate` on the recorded build must PASS.
147
+ A FAIL means the model misses something real; find it in `timing.json` (the field
148
+ waterfalls) before you quote a replay delta.
149
+ - **Read the unrecorded requests.** A page request that the recording never saw gets a
150
+ 404; a candidate that adds an API call is then not measured as it would run.
151
+ - **Record again when the network changes.** Chrome's network rating decides which
152
+ requests wait; an archive from a fast network says nothing about a slow one.
153
+ - The archive holds real responses. It stays in `$TMPDIR`; never copy it anywhere.
154
+ - **Two agents: hand over the archive path only.** The recording agent runs
155
+ `node replay.mjs record ... --json` and passes the `archive` path. The replaying
156
+ agent runs `node replay.mjs inspect --archive <dir> --json` first, then
157
+ `replay ... --calibrate --json --exit-code`. Exit 2 or `usable: false` means record
158
+ again; do not edit `archive.json` by hand.
159
+ - **Act on the calibration verdict, not on PASS/FAIL alone.** `record-again` (exit 3,
160
+ `gaps` not empty): record again with a longer `--settle`, at most twice.
161
+ `model-error` (exit 1): the archive is complete; do NOT record again, report the rows
162
+ that are off. Recording again cannot fix a model error.
163
+ - **Keep `--storage warm` for a page that caches data in storage.** The field profile
164
+ has visited the page before; an empty context measures a first visit instead.
165
+ - **Find leads in the field runs, decide with a replay A/B.** `inspect` →
166
+ `lateChunksBeforeLcp` lists static chunks that started more than one recorded RTT
167
+ after a static ancestor finished, in the real field runs. Check in the source whether
168
+ paint needs them before you change a chunk boundary or a preload. Never quote a
169
+ discovery delay as a gain.
170
+
171
+ ## The rendered output must not change
172
+
173
+ The first scan of a target saves the build as the ORIGINAL — run it before you touch
174
+ any code, and pin the client-side routes your work can reach with it:
175
+ `scan --app <appDir> --pages /pricing,/settings` (entry HTML files are found
176
+ automatically; a shared chunk or stylesheet you change for `/` can break another page).
177
+
178
+ - **Optimize freely** — nothing is compared per change. Each scan only says whether the
179
+ LCP element is still the original's; when it is not, LCP is timing a cheaper paint
180
+ (a hidden hero, a placeholder) and that gain is probably not real — find out why
181
+ before you build on it.
182
+ - **When you have finished: `scan --parity`.** It takes the final measurement and
183
+ compares every pinned page with the original: first-screen and full-page pixels, page
184
+ height, visible text, accessible names, new runtime errors, the LCP element. The
185
+ verdict holds it as PENDING until then; you are not done without it.
186
+ - **A difference starts RECOVER MODE**: that build's LCP numbers do not count until it
187
+ matches again — often it was faster BECAUSE it broke (a stylesheet split, reordered or
188
+ made non-blocking without the swap; a font deferred until it never loads). The output
189
+ names what changed — the element and the property or font, the missing text, the new
190
+ error — and points at a diff image. Fix the change or revert it, and choose how you
191
+ re-check: `parity strategy each-tweak` (every scan re-checks and records a snapshot)
192
+ or `on-demand` (you run `parity`; `--only /page` re-checks just the broken pages).
193
+ `parity recover` lists the options, including bisecting your kept commits with
194
+ `git bisect run` and `parity --exit-code`; `parity history` shows every check. A fix
195
+ may give back part of the gain — report the number of the build that matches.
196
+ - **Never** run `parity approve`, `parity anchor --replace`, or add a `--mask`, to make a
197
+ difference go away. Those are human decisions and every verdict lists them. If you
198
+ believe a difference is intended, stop and say so, with the evidence path.
199
+ - UNKNOWN there means the original itself renders differently between two loads (a
200
+ human adds masks) or the LCP element was not compared yet (scan).
201
+
202
+ ## The optimization loop
203
+
204
+ 1. Build the app. `scan --app <appDir>` — the first scan is your baseline and saves
205
+ the original build.
206
+ 2. Read EVERY signal class in the scan output; each is a lead with a next-step:
207
+ - **render-blocking CSS gate** — FCP cannot precede the last render-blocking
208
+ stylesheet; when they land together, CSS is the paint gate and no amount of
209
+ JS work will move it. Fix FIRST, alongside render gap: inline the small
210
+ critical CSS, load the rest non-blocking (preload + media swap), split
211
+ styles only later routes need. (On pages loading many scripts, a plain
212
+ `media="print"` link gets fetch-deprioritized behind them — pair it with
213
+ `rel="preload"`.)
214
+ - **late chunk candidates before FCP or LCP** — inspect
215
+ `runtime-metrics.json` → `chunkDiscovery.fcp.groups` and
216
+ `chunkDiscovery.lcp.groups`. Each check follows static imports that start
217
+ more than one RTT after an ancestor finishes. The download must finish before
218
+ that paint. The check uses `chunk-graph.json` or the current Vite manifest.
219
+ Each example includes an import chain, request timings, and the available
220
+ source module and initiator. Request details also record Chrome's initial
221
+ priority and priority changes. Check them when testing preload priorities.
222
+ Repeated URLs and redirects omit ambiguous network evidence.
223
+ Follow the available source locations to the source. Check whether the
224
+ chunk renders the first visible content or the LCP element. The report marks
225
+ this dependency `unverified`; timing alone cannot prove it. If paint needs
226
+ the chunk, test an earlier preload or a different chunk boundary. If paint
227
+ does not need it, test deferring its load and work. Groups and paint windows
228
+ overlap. Never add their bytes or delays. A delay is not a predicted paint gain.
229
+ - **render gap** — LCP after the load event. Downloads can continue after
230
+ load. Inspect concurrent script, fetch/XHR, font, and image evidence together.
231
+ One candidate does not exclude another. Check which responses rendering
232
+ awaits before changing load order. Test moving optional data reads after
233
+ paint. Keep the LCP image request early. Check which fonts visible text needs.
234
+ Compare the candidates with the CPU profile. When these checks do not
235
+ explain the gap, check whether the LCP element **mounts invisible**: an entry
236
+ animation or fade-in wrapper that starts the hero at opacity 0 delays LCP
237
+ until the reveal — LCP counts the first frame the element paints VISIBLE.
238
+ Render the hero visible immediately and animate only decoration.
239
+ - **pre-paint CPU by module** — the profile maps all sampled same-origin
240
+ chunks through their sourcemaps, including dynamic imports. Inspect source
241
+ rows and their chunk filenames in `profile.json`. Missing maps, unmapped
242
+ positions, inline scripts, external scripts and mixed top-level lines
243
+ (`top-level`) stay unattributed. Do not
244
+ classify a generated filename as engine work. Old entry-only profiles must
245
+ be collected again. The profile stops at FCP observation; its polling tail
246
+ can include post-paint work. It does not cover the FCP-to-LCP interval.
247
+ Sampled time does not prove that work blocks paint or can be deferred.
248
+ Check warm-up caches, telemetry, and data-module evaluation. Defer what the first render does not need —
249
+ but only judge deferrals AFTER any render gap is fixed (CPU that overlaps a
250
+ blocked render is free, so deferring it can measure worse until then).
251
+ - **cold bytes at paint** — the unified byte view: weight fetched+parsed before
252
+ first paint but mostly unread by it, coldest first. Its middle band is the one
253
+ that hides everywhere else: a vendor SDK executing 10–50% at boot (firestore,
254
+ an analytics kit) is neither a "candidate" nor "large executed", yet one
255
+ boot-time init call is dragging the whole package. Defer the CALL, not just
256
+ the import. Framework runtimes (react-dom) are annotated — their cold bytes
257
+ rarely move; don't chase them.
258
+ - **defer candidates** — parsed but ~unexecuted at paint: classic lazy-load targets.
259
+ - **pre-paint sibling chunks** — a non-entry chunk fetched AND executed before
260
+ first paint is critical-path transfer, even if every import of it is dynamic.
261
+ Find what runs it at boot (a top-level or render-time `import()`, an eagerly
262
+ mounted component) and move that trigger to the actual interaction.
263
+ - **static pre-paint transfer** — chunks fetched before first paint (static
264
+ script tags, modulepreloads) but executed only after it. Execution timing
265
+ says "deferred"; the download still competed with the paint for bandwidth,
266
+ and when transfer dominates LCP this is the biggest lever. Load them on
267
+ demand: dynamic import, or drop them from the initial tags/preloads.
268
+ - **large modules executed at paint** — "executed" does NOT mean needed; top-level
269
+ data evaluates on import. Verify how much the first render reads; split the rest
270
+ behind a dynamic import.
271
+ - **sibling variant groups** — locales/themes/config families where one variant is
272
+ active per session: keep the default in the entry, load the active one dynamically.
273
+ - **statically retained imports** (rolldown builds only) — this view starts
274
+ at build entries. It does not price dynamic imports that startup immediately
275
+ executes. Check coverage and CPU evidence for those imports. The module graph's
276
+ dominator view: any non-framework module retaining ≥100KB on the initial load
277
+ is a priced split candidate; `what-if` shows the exact cut, `cut` the fewest
278
+ import edits when several paths reach it, and `what-if <a> <b>` the combined
279
+ free when a package repeats across rows. Retained is potential, not proof — the
280
+ first render may genuinely need it; if so, say why.
281
+ 3. Read the app source and find why the landing page pays for each lead.
282
+ 4. Change the app, without removing any feature. One small change at a time — render
283
+ gap first, then data/variant splits, then CPU deferrals.
284
+ 5. Rebuild. Run the app's own functional check (it must pass). `scan --no-early-stop` — or
285
+ `scan --quick` (1 run, no profile) as a cheap probe on slow apps; quick results
286
+ are indicative only. Early-stopped scans are probes too. Check both paint
287
+ metrics with a full scan of at least 3 runs before a keep/revert decision.
288
+ 6. Read `paintComparison` and both entries in `baselineDelta`. At least one of
289
+ FCP or LCP must improve beyond noise. Neither may regress beyond noise.
290
+ The threshold for each metric is max(30ms, 2% of its baseline).
291
+ Both metrics must appear in every run. If these checks and the functional check pass,
292
+ keep the change, `scan --pin` (or `baseline`), and commit. Otherwise, revert
293
+ the change and rebuild. Keep the same content and features.
294
+ (A deferral that measures worse while a render gap exists is worth retrying
295
+ after the gap is fixed.)
296
+ 7. Repeat. When you have finished optimizing, run `scan --parity` (above) and work any
297
+ difference in recover mode. Declare done ONLY when the verdict reports every signal
298
+ class clear and fresh — never because a single report looks empty. One tool's
299
+ silence only means that tool sees nothing; the verdict checks them all and tells
300
+ you which signals you haven't gathered yet.
301
+ 8. Report: baseline and final FCP and LCP, their % changes, entry size before/after, and one
302
+ sentence per kept change — **plus the final verdict checklist, copied verbatim.**
303
+ A re-pinned baseline records a gain; it does NOT close the checklist. If you stop
304
+ while leads are OPEN, say "stopping with N lead(s) OPEN" and justify each one:
305
+ a measurement (you tried it, the delta was sub-noise) or a concrete constraint
306
+ (framework dep, the first paint genuinely needs it, outside your declared scope).
307
+ Never describe OPEN leads as "confirmed done" — misquoting the verdict is the
308
+ one way to fail this task even with a good number.
309
+
310
+ ## Rules
311
+
312
+ - Judge speed ONLY by the `vs pinned baseline` verdict. Never by the chain
313
+ "vs previous measure" line, never by intuition.
314
+ - A faster page that breaks a feature, or renders differently from the original, is
315
+ a failure, not an optimization.
316
+ - Never edit this package, the app's build/check scripts, or any thresholds to move
317
+ the numbers.
318
+ - **The functional check is the contract — never modify it.** If it fails after your
319
+ change, your change is wrong: fix the app or revert. A check you edited proves
320
+ nothing.
321
+ - **Never change the app's build layout (outDir, file names) to satisfy this tool.**
322
+ Aim the tool at the app instead: `--app <appRoot>` resolves dist/build/out
323
+ automatically, `--dist <builtDir>` aims at any output dir directly.
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present VoidZero Inc. & Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ end of terms and conditions
24
+
25
+ The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE).